Mybatis----Mybatis使用
查询结果拼接
SELECT H.*,
A.XQDFS,
B.YQDFS,
C.WQDFS,
<if test="ZydyList != null and ZydyList.size > 0 ">
<foreach collection="ZydyList" item="Zydy" separator=",">
D.${Zydy.ZYDM} as D.${Zydy.ZYDM}WQD
</foreach>
</if>
,E.JZXJS,
E.RDXJS,
E.DYXJS,
mapper.xml解释
<mapper namespace="com.zy.Dao.UserDao"> //namespace:命名空间
//id:表示映射文件的sql,将sql语句封装到mappedStatement对象中,所以又叫statement的id,parmeterType(可以不指定):输入类型的参数,如果指定类型是简单类型(int,String等),参数名可以任意
<select id="selectById" resultType="User" parameterType="int">
SELECT
*
FROM
t_user
WHERE id = #{id} ;
</select>
</mapper>
传入参数
使用 User user = tbUserDao.getUserById(1); user.setName="dd"; tbUserDao.updata(user);
MyBatis 使用
增删改查
增
<insert id="insert">
INSERT INTO tb_user (
id,
username,
password,
phone,
email,
created,
updated
)
VALUES
(
#{id},
#{username},
#{password},
#{phone},
#{email},
#{created},
#{update}
)
</insert>
mysql自增主键:返回主键(主键被设置到了传入参数的user对象上,通过user.getId())
注意: keyProperty="id" 中的id 如果传入的参数有多个值得话,就需要修改 id,比如, keyProperty= xx.id
<insert id="insetone"> //方法一
<selectKey keyProperty="id" order="AFTER" resultType="int"> //order:表示这个方法在插入之后执行
select LAST_INSERT_ID();
</selectKey>
INSERT INTO t_user (username, PASSWORD)
VALUES
(#{username}, #{password});
</insert>
<insert id="insertUser" keyProperty="id" useGeneratedKeys="true"> //方法二(也是通过User.getId(),来获取id)
采用方法二也可以使用返回的int值,insert 方法总是返回一个 int 值 , 这个值代表的是插入的行数。
删
<delete id="delete">
DELETE FROM tb_user WHERE id = #{id}
</delete>
改
<update id="update">
UPDATE
tb_user
SET
username = #{username},
password = #{password},
phone = #{phone},
email = #{email},
created = #{created},
updated = #{update}
WHERE id = #{id}
</update>
<update id="deleteByScheduleId">
update course_student_selection
<set >
<if test="status != null" >
status = #{status,jdbcType=INTEGER},
</if>
<if test="schoolId != null" >
schoolId = #{schoolId,jdbcType=BIGINT},
</if>
</set>
where id = #{id,jdbcType=BIGINT}
</update>
查
模糊查询
在java代码中添加sql语句的通配符
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
</select>
在sql语句中处理
<select id="selectByName" resultType="TbUser">
SELECT
a.id,
a.username,
a.password,
a.phone,
a.email,
a.created,
a.updated AS "update"
FROM
tb_user AS a
WHERE
a.username LIKE CONCAT ('%', #{username}, '%') <!--相当于SQL语句中 a.username LIKE "%zy%"-->
</select>
排序
order by ${xxx} //可能#{}不行
MyBatis 传递多个参数
public UserselectUser(String name,String area);
对应的 xml,#{0}代表接收的是 dao 层中的第一个参数,#{1}代表 dao 层中第二参数,更多参数一致往后加即可。
public interface usermapper {
User selectuser(@param(“username”) string username,@param(“password”) string hashedpassword);
}
对应的 xml,#{username},#{password}
建议使用Map封装多个参数
MyBatis 动态 SQL
在 mapper 的动态 SQL 中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换为实体符号。否则,XML 可能会出现解析出错问题。
特别是对于小于号(<),在 XML 中是绝对不能出现的。否则,一定出错。

if 标签
对于该标签的执行,当 test 的值为 true 时,会将其包含的 SQL 片断拼接到其所在的 SQL 语句中。
为了解决两个条件均未做设定的情况,在 where 后添加了一个“1=1”的条件。这样就不至于两个条件均未设定而出现只剩下一个 where,而没有任何可拼接的条件的不完整 SQL 语句。
xml映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lusifer.mybatis.dao.DynamicStudentDao">
<!-- if -->
<select id="selectByIf" resultType="com.lusifer.mybatis.entity.Student">
SELECT
id,
name,
age,
score
FROM
student
WHERE 1 = 1
<if test="name != null and name != ''"> //要规范写,name前面确实需要加上user对象
AND name LIKE concat('%', #{name}, '%')
</if>
<if test="age != null and age > 0">
AND age > #{age}
</if>
</select>
</mapper>
where 标签
<if/> 标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where后的所有 <if/> 条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL 出错。所以,在 where 后,需要添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。
xml映射文件
<!-- where-->
<select id="selectByWhere" resultType="com.lusifer.mybatis.entity.Student">
SELECT
id,
name,
age,
score
FROM
student
<where> //会自动去掉条件中的第一个AND,所以我们就不需要写where 1=1;
<if test="name != null and name != ''">
AND name LIKE concat('%', #{name}, '%') //AND 可以加也可以不加,都会被去掉;
</if>
<if test="age != null and age > 0">
AND age > #{age}
</if>
</where>
</select>
示例:模糊查询(组合搜索引擎)
<select id="fuzzysearch" resultType="TbUser">
SELECT <include refid="Usercolumns"/> FROM tbuser As u <where>
<if test="username != null and username != ''">
OR u.username LIKE concat('%', #{username}, '%')
</if>
<if test="phone != null and phone !=''">
OR u.phone LIKE concat('%', #{phone}, '%')
</if>
<if test="email != null and email !=''">
OR u.email LIKE concat('%', #{email}, '%')
</if>
</where>
</select>
示例:模糊查询(单个搜索引擎)
<select id="fuzzysearch" resultType="TbUser">
SELECT <include refid="Usercolumns"/> FROM tbuser As u <where>
<if test="username != null and username != ''">
AND u.username LIKE concat('%', #{username}, '%')
</if>
<if test="phone != null and phone !=''">
AND u.phone LIKE concat('%', #{phone}, '%')
</if>
<if test="email != null and email !=''">
AND u.email LIKE concat('%', #{email}, '%')
</if>
</where>
</select>
choose 标签
该标签中只可以包含 <when/> <otherwise/>,可以包含多个 <when/> 与一个 <otherwise/>。它们联合使用,完成 Java 中的开关语句 switch..case 功能。
本例要完成的需求是,若姓名不空,则按照姓名查询;若姓名为空,则按照年龄查询;若没有查询条件,则没有查询结果。
映射文件
<!-- choose -->
<select id="selectByChoose" resultType="com.lusifer.mybatis.entity.Student">
SELECT
id,
name,
age,
score
FROM
student
<where>
<choose>
<when test="name != null and name != ''">
AND name LIKE concat('%', #{name}, '%')
</when>
<when test="age != null and age > 0">
AND age > #{age}
</when>
<otherwise>
AND 1 != 1
</otherwise>
</choose>
</where>
</select>
foreach 标签(遍历数组)
<foreach/> 标签用于实现对于数组与集合的遍历。对其使用,需要注意:
-
collection表示要遍历的集合类型(或者对象的属性名),这里是数组,即 array。 -
open、close、separator为对遍历内容的 SQL 拼接(注意拼接的含义)。
映射文件
动态 SQL 的判断中使用的都是 OGNL 表达式。OGNL 表达式中的数组使用 array 表示,数组长度使用 array.length 表示。
注意:如果传入的直接是一个数组或者集合,下面代码中的名字不能乱写,如果传入一个pojo,pojo对象属性是集合或者数组,那么下面的名字就是属性名;
1、sql:SELECT * FROM t_user WHERE id IN (3,4); open:"(",close:")",separator(分割这些数据)是","

<!-- foreach -->
<select id="selectByForeach" resultType="com.lusifer.mybatis.entity.Student">
<!-- select * from student where id in (2, 4) -->
SELECT
id,
name,
age,
score
FROM
student
<if test="array != null and array.length > 0">
WHERE id IN
<foreach collection="array" open="(" close=")" item="id" separator=",">
#{id} //如果list<Interget>,#{id}:就是每一个值,如果list<pojo>,#{id}:就是属性
</foreach>
</if>
</select>
<foreach collection="selectionIdList" item="id">
update course_student_selection css
LEFT JOIN course_schedule AS cs ON css.scheduleId = cs.id
LEFT JOIN user_info AS ui ON css.`stuId` = ui.id
<set>
totalCount = totalCount-1, lastLim = lastLim+1,
femaleCount = if(ui.userSex=0,femaleCount-1,femaleCount),
maleCount = if(ui.userSex=1,maleCount-1,maleCount)
</set>
WHERE css.id =
#{id};
</foreach>
如果sql:SELECT * FROM t_user WHERE (id=3 OR id =4); open:"(",close:")",separator(分割这些数据)是"OR"
<select id="selectByForeach" resultType="com.lusifer.mybatis.entity.Student">
<!-- select * from student where id in (2, 4) -->
SELECT
id,
name,
age,
score
FROM
student
<if test="array != null and array.length > 0">
WHERE
<foreach collection="array" open="(" close=")" item="id" separator="OR">
id=#{id}
</foreach>
</if>
</select>
foreach 标签(遍历集合)
遍历集合的方式与遍历数组的方式相同,只不过是将 array 替换成了 list
sql 标签
<sql/> 标签用于定义 SQL 片断,以便其它 SQL 标签复用。而其它标签使用该 SQL 片断, 需要使用 <include/> 子标签。该 <sql/> 标签可以定义 SQL 语句中的任何部分,所以 <include/> 子标签可以放在动态 SQL 的任何位置。
映射文件
<sql id="select">
SELECT
id,
name,
age,
score //结尾不能有逗号,注意
FROM
student
</sql>
使用
<!-- foreach -->
<select id="selectByForeachWithListCustom" resultType="com.lusifer.mybatis.entity.Student">
<!-- select * from student where id in (2, 4) -->
<include refid="select" /> //如果指定的sql不在同一个Mapper中,那么select前面需要加上指定sql的namespace
<if test="list != null and list.size > 0">
WHERE id IN
<foreach collection="list" open="(" close=")" item="student" separator=",">
#{student.id}
</foreach>
</if>
</select>

foreach批量插入mysql数据
当其中一条插入不成功时,不会进行整体回滚
<insert id="insertBatch" >
insert into person ( <include refid="Base_Column_List" /> )
values
<foreach collection="list" item="item" index="index" separator=",">
(null,#{item.name},#{item.sex},#{item.address})
</foreach>
</insert>
foreach批量插入oralc数据
方式1
<insert id="addList" parameterType="java.util.List" useGeneratedKeys="false">
INSERT ALL
<foreach item="item" index="index" collection="list">
INTO T_APPLAUD
(
ID,
USER_ID,
BUSINESS_TYPE,
PRODUCT_ID,
CREATE_TIME
) VALUES
(
#{item.id, jdbcType=NUMERIC},
#{item.userId, jdbcType=VARCHAR},
#{item.businessType, jdbcType=VARCHAR},
#{item.productId, jdbcType=VARCHAR},
#{item.createdTime, jdbcType=NUMERIC}
)
</foreach>
SELECT 1 FROM DUAL
</insert>
方式2
<insert id="addList" parameterType="java.util.List" useGeneratedKeys="false">
INSERT INTO T_APPLAUD
(
ID,
USER_ID,
BUSINESS_TYPE,
PRODUCT_ID,
CREATE_TIME
)
<foreach item="item" index="index" collection="list" separator="union all">
(
SELECT
#{item.id},
#{item.userId},
#{item.businessType},
#{item.productId},
#{item.createdTime}
FROM DUAL
)
</foreach>
</insert>
Mybatis批量插入数据(使用 mybatis ExecutorType.BATCH)
Mybatis内置的ExecutorType有3种,默认的是simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql;而batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优;
批量提交只能应用于 insert, update, delete
这时我们手动控制事务。
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public void exeBatch() {
//新获取一个模式为BATCH,自动提交为false的session
SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH, false);//可以把这段代码进行配置,配置一个批量执行的sqlsession
PersonModelMapper mapper = sqlSession.getMapper(PersonModelMapper.class);//获取mapper接口,PersonModelMapper接口里面有insert(单条插入的接口)
try {
for (int i = 0; i < 10; i++) {
//或者使用
//sqlSession.insert("com.example.demo.db.dao.PersonModelMapper.insertSelective", new PersonModel());
//主意这时候不能正确返回影响条数了
mapper.insertSelective(new PersonModel());
}
sqlSession.commit();
//清理缓存,防止溢出
//sqlSession.clearCache();
} catch (Exception e) {
//异常回滚
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
补充
<!-- 配置一个可以批量执行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
resultMap
使用场景1
当我们查询的结果中的字段和pojo中的属性不一致,这就导致返回映射不成功;
我们假设User中的id字段是id_和username_,而数据库中的返回的字段是id和username;
方式1:返回值还是resultType,通过别名,修改查询到的数据字段;
<select id="selectById" resultType="User">
SELECT
id AS id_,
username AS username_
FROM
t_user
WHERE id = #{id};
</select>
方式2:使用resultMap
<!--id:唯一标识,type:返回的数据类型-->
<resultMap id="userResultMap" type="com.zy.pojo.User">
<!--<id/>是修改特殊(唯一标识)字段,如果多个字段组成唯一标识,就需要配置多个id-->
<id column="id" property="id_"></id> //pojo属性是id_
<!--<result/>是修改普通的字段-->
<result column="username" property="username_"></result>
</resultMap>
<select id="selectById" resultMap="userResultMap">
SELECT
id,
username
FROM
t_user
WHERE id = #{id};
</select>
使用场景2
如果一个pojo对象中有属性是一个对象,我们可以利用resultMap,将值传入对象中
方式1:不使用association(这个需要sql语句进行连表操作,一次性将多个表的数据都查出来)
<resultMap id="userResultMap" type="com.zy.pojo.User">
<!--<id/>是修改特殊(唯一标识)字段-->
<id column="id" property="id"></id>
<!--<result/>是修改普通的字段-->
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--用户中关联的订单-->
<result column="order_id" property="order.id"></result>
<result column="ordername" property="order.orderName"></result>
</resultMap>
使用场景3
一对多查询
比如我们需求,查询user对象中所有的user_detail数据;
SELECT t_user.*, user_detail.`id` AS user_detail_id, //注意:这需要些别名,防止和id冲突 user_detail.`desc`, user_detail.`user_id` FROM t_user, user_detail WHERE t_user.id=user_detail.`user_id`

使用ResultMap中的<collection />属性,来将数据装载到集合中;
<resultMap id="userResultMap" type="com.zy.pojo.User">
<!--<id/>是修改特殊(唯一标识)字段-->
<id column="id" property="id"></id>
<!--<result/>是修改普通的字段-->
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--集合中定义的pojo类型需要使用ofType-->
<collection property="userDetailList" ofType="com.zy.pojo.UserDetail">
<id column="user_detail_id" property="id"></id> //需要有唯一标识
<result column="desc" property="desc"></result>
</collection>
</resultMap>
<select id="select" resultMap="userResultMap">
/*重点:user_detail中的id需要使用别名,防止和user中的id冲突*/
SELECT
t_user.*,
user_detail.`id` AS user_detail_id,
user_detail.`desc`,
user_detail.`user_id`
FROM
t_user,
user_detail
WHERE t_user.id=user_detail.`user_id`
</select>
多对多查询【多个一对多】(就是简单的在集合中套一个集合):注意理解集合中的每一个元素都是一个pojo,相当于给每一个pojo设值
<collection property="userDetailList" ofType="com.zy.pojo.UserDetail">
<collection property=""></collection>
</collection>
补充理解:多对多,本质来说就是一对多,因为分析数据的时候,是按照某一张表(可以看做POJO)分析的,比如说A表和B表,知道一个A对应多个B,ok,我们就够了,如果我们需求从A中获取对应的所有的B,我们就不需要知道B对A是一对一还是一对多,没有意义,如果需求,我们需要从B中获取所有的A,那么我们就可以直接分析B对A的关系了,更不就不需要分析A对B的关系。
我们所谓的多对多是两张表之间的关系,但是真正在进行写sql语句的时候,我们只需要关注一条线,起点A-->终点B。或者A--->B----->C(B的数据放在A存,C的数据放在B存,注意上面的A,B,C,都是POJO)
使用场景4
resultMap的继承,rusultMap可以继承另一个resultMap,然后就不需要写在resultMap中写继承的代码了(注意继承,是全部继承的,如果继承中的某些东西不需要,那么就不要继承了)
<resultMap id="userResultMap" type="com.zy.pojo.User" extends="xx">
association使用1(一对一)
public class User{
//身份证对象信息
private Card card_one;
}
需求:将需要sql语句进行连表操作,一次性将多个表的数据都查出来,association不需要有select字段和column
<resultMap id="BaseResultMap" type="com.zy.p2p.base.domain.VedioAuth">
<id column="id" jdbcType="BIGINT" property="id"/>
<result column="state" jdbcType="TINYINT" property="state"/>
<result column="remark" jdbcType="VARCHAR" property="remark"/>
<result column="auditTime" jdbcType="TIMESTAMP" property="auditTime"/>
<result column="applyTime" jdbcType="TIMESTAMP" property="applyTime"/>
<association property="applier" column="applier_id" javaType="com.zy.p2p.base.domain.Logininfo"> //将下面这些字段信息保存到VedioAuto中的applier(LoginInfo)对象中
<id column="ap_id" property="id"></id>
<result column="username" property="username"></result>
</association>
<association property="auditor" column="auditor_id" javaType="com.zy.p2p.base.domain.Logininfo">
<id column="au_id" property="id"></id>
<result column="username" property="username"></result>
</association>
</resultMap>
<select id="selectByPrimaryKey" parameterType="java.lang.Long" resultMap="BaseResultMap">
SELECT v.id, v.state, v.remark, v.auditTime, v.applyTime,au.id AS au_id,au.`username`,ap.`id` AS ap_id,ap.`username`
FROM vedioauth AS v INNER JOIN logininfo AS ap ON v.`applier_id`=ap.`id` INNER JOIN logininfo AS au ON v.`auditor_id`=au.`id`
WHERE v.id = #{id};
</select>
如果association中的字段在另一个mapper.xml配置有
columnPreofix最大的用处在于,我们select 字段可能前面有b_name,b_id,等等,但是BaseResultMap只有<id column="id" property="id"></id>,所以加上columnPrefix就可以把BaseResultMap变成<id column="b_id" property="id"></id>
<association property="bankInfo" javaType="PlatformBankInfo" columnPrefix="b_" resultMap="com.zy.p2p.business.mapper.PlatformBankInfoMapper.BaseResultMap" />
association使用2
需求:延迟加载,这个需要sql语句不需要进行关联表的操作。association需要有select字段
适用场景,第一次查询的数据非常少(因为会对第一次查询到的信息,在进行查询其他的数据),比如分页就不建议使用延迟加载(sql语句过多)
延迟加载:先从单表查询,需要是在从关联表去关联查询,大大提高数据库性能,如果我们不使用下面案例这个框架,用最简单的方法实现延迟加载本质,就是假如我们只需要用户表中的数据,不需要订单表中的数据,我们就写一个sql查询用户表数据即可,如果突然又需要user表中关联的order信息,我们就在用之前user查询的结果在去查询order信息,这就是延迟加载的本质。
resultMap中的association,collection 可以实现延迟加载
环境配置
mybatis的配置文件
<setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/>
Mapper.xml
<!--根据用户信息查询订单,如果不需要,dao可以不用写接口-->
<select id="selectOrderById" resultType="com.zy.pojo.Order">
select * FROM u_order where id=#{id};
</select>
<!--延迟加载resultMap-->
<resultMap id="orderUserResultMap" type="com.zy.pojo.User">
<id column="id" property="id"></id>
<result column="username" property="username"></result>
<result column="password" property="password"></result>
<!--
对订单信息进行延迟加载,用户关联order(订单),所以user对order,是一对一,所以我们使用association
select:指定我们延迟加载需要执行的sql语句(statement Id),这个select可以不用再dao写接口(如果不需要的话)
column:通过数据库查询出来的user信息中得order_id列(所以sql语句中查询的字段不能将order_id字段忽略掉),order_id数据会被传入selectOrderById中
-->
<association property="order" select="selectOrderById" column="order_id">
</association>
</resultMap>
<!--查询用户关联的相关订单-->
<select id="selectUserById" resultMap="orderUserResultMap">
SELECT * FROM t_user where id=#{id};
</select>
测试
dao
public interface UserDao {
public User selectUserById(int id);
}
测试
@Test
public void userTest(){
//注意如果是Debug模式测试,延迟加载就会失效(Debug会直接将order数据查询出来赋值给user)
User user = userDao.selectUserById(3);
System.out.println(user.getId());
System.out.println("------------------------");
/*当执行下面的语句的时候,控制台才出现查询订单的sql,所有如果不是必要,不要打印user,一旦打印了,延迟加载的sql也会被执行(原因需要获得user所有的信息)*/
System.out.println(user.getOrder());
}
collection实现延迟加载
一个domian中有一个List字段,那么数据库中是没有该字段的。他这种是其他表关联这张表的。
将数据的结果返回成一个集合
<collection property="bids" column="id" ofType="com.zy.p2p.business.domain.Bid"
select="com.zy.p2p.business.mapper.BidMapper.selectByBidRequest"/>
查询缓存
Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能
Mybatis提供了一级缓存和二级缓存
查询的流程是二级缓存>一级缓存>数据库

为什么使用缓存?
如果sql对应的数据在缓存中存在,那么我们就不需要在查询数据库了,直接将缓存中的数据返回给用户。提高系统的性能
4.1 一级缓存(默认开启)
一级缓存:是Sqlsession级别的缓存。在操作数据库时需要构造soglsession对象,在对象中有一个localCache,他是一个(HashMap)用于存储缓存数据。HaspMap的key就是封装了namespace和statement id 以及拼接好了的sql,value是缓存的数据;
不同的sglsession之间的缓存数据区域(HashMap)是互相不影响的。

原理:第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。如果sqlsession去执行commit 操作(执行插入、更新、删除),清空Sqlsession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。第二次发起查询用户id为1的用户信息,先去找缓存中是否有d为1的用户信息,缓存中有,直接从缓存中获取用户信息。没有就去读取数据库,然后将数据写入一级缓存。
测试一级缓存
我使用的是sping整合mybatis,这样sqlsession自动生成的(如果手动创建sqlSession,使用sqlsession进行操作就可以利用缓存了),我测试的时候,每次执行完毕sql后,通过打印日志看到sqlsession就会自动关闭,下一个请求后,就又自动生成一个sqlsession,所以每次的sqlsession都不一样,这样的话,就利用不了一级缓存了,因为一级缓存是在一个sqlsession里面。但是我们开启事务后,就可以利用一级缓存了。原因是开启事务后,事务控制在service(mvc三层结构),当调用service方法后,就开启了事务,会创建一个sqlsession,并且整个service方法这个过程中都是使用的这一个sqlsession。但是一旦service方法执行完毕,sqlsession就关闭了。所以一级缓存时间很短的,service方法调用完毕后,就清空了。如果我们就需要使用二级缓存长时间缓存数据。
参考spring一级缓存的详细说明:https://blog.csdn.net/ctwy291314/article/details/81938882
4.2 二级缓存(默认不开启)
二级缓存:Mybatis中SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession共享其缓存,多个Sqlsession.可以共用一个二级缓存,二级缓存是跨Sqlsession的
二级缓存应用场景:1、对访问的请求次数多,但是查询的结果的实时性不高的数据。,2、像一些特别耗时的统计分析sql(查询一条sql可能要几十分钟),实现方法:(设置flushInterval,每隔一段时间,自动清空缓存)
二级缓存局限性?二级缓存对细粒度的数据级别的缓存实现不好,比如,一大批用户查询了1万条的商品信息,放在缓存中,但是某一个商品信息被修改了,就会造成缓存数据被清空,1万条数据清空。
所以我们需要是,改了哪一个商品,就把那一个商品的缓存数据改变,Mybatis实现不了这种需求,需要我们手动实现业务层缓存,就是所谓的三级缓存(概念)。

二级缓存区的划分?
是按照namespace划分的(也就是按照dao接口,namespace就是dao接口的全限定路径):每一个namespace有自己的二级缓存空间。不同的sqlsession访问同一个namespace,才数据共享。
比如下面两个dao,sqlsession之后访问同一个dao中相同或者不同的方法,才会共享二级缓存(依旧是HashMap存储数据)。如果mapper文件的namespace(命名空间)一样,那样,他们就可以共享一个mapper缓存。
为什么要划分呢?我自己也不太理解,把所有的数据都放到一个缓存空间不一样吗?等到后续的查阅资料。
UserDao
@Repository
public interface UserDao {
public User selectUserById(int id);
public List<User> selectAllUser(int id);
}
OrderDao
@Repository
public interface OrderDao {
public Order selectOrderById(int id);
}
使用二级缓存
开启二级缓存
1、mybtis-config.xml
<setting name="cacheEnabled" value="true"/> //设置为true
2、mapper.xml
<mapper namespace="com.zy.Dao.UserDao">
<cache></cache> //开启缓存(UserDao调用的所有的方法,公用这一个二级缓存)
</mapper>
3、将查询的pojo实现序列化接口
测试
模拟多个sqlsession;缓存是长时间存在内存中的。
public void sqlsession(UserDao userDao){
User user = userDao.selectUserById(3);
System.out.println(user.getId());
}
public void sqlsession2(UserDao userDao){
User user = userDao.selectUserById(3); //只有调用同一个方法,传入同一个参数,用同一个namespace,sql才会一样,此时就不会再去查询数据库了(如果没有执行增删改操作)
System.out.println(user.getId());
}
//注意测试的时候不用开启事务,否则就使用了一级缓存了(同一个sqlsession),测试不出来结果了
//@Transactional
@Test
public void userTest(){
new DemoTest().sqlsession(userDao); //当调用这个方法结束后,sqlsession就自动关闭了(注意:一定需要关闭,否则查询的数据不会写入到二级缓存中)
new DemoTest().sqlsession2(userDao);
}
补充
<select id="select" resultMap="userResultMap" useCache="false"> //表示这个查询禁用二级缓存 <insert id="" flushCache="true"></insert> //表示刷新缓存(清空缓存),一般插入数据后,都是需要刷新的,所有默认就是true <cache type="org.mybatis.caches.ehcache.EhcacheCache" flushInterval="30"></cache> //type:使用第三方缓存(ehcache),flushInterval:设置刷新频率(单位应该是秒,不太清楚)
Mybatis逆向工程
mybatis需要程序员自己编写sql语句,mybatis官方提供逆向工程,可以针对单表自动生成mybatis执行所需要的代码(mapper.java、mapper.xml、pojo…),可以让程序员将更多的精力放在繁杂的业务逻辑上。
通过数据库生成java代码
使用方法:参考:https://www.cnblogs.com/yanxiaoge/p/11043295.html
Mybatis整合ehcache
Mybatis实现不了分布式缓存,即使不谈分布式,Mybatis也不适用于做数据缓存,所以必须和其他框架整合,对缓冲的数据进行额外处理,保证对内存空间利用率,而Mybatis仅仅就是简单的将数据存放到内存中,没有对他们进行修饰。
ehcache:分布式缓存框架,如果不用分布式缓存,可以就需要每台服务器都需要保存用户登录的所有的数据。而ehcache可以对缓存数据进行集中管理。
额外的分布式缓存框架:redis,memcached

整合方法
Mybatis提供了一个cache接口,如果需要实现自己的缓存逻辑,就实现擦车接口开发即可;(MyBatis无论和那个缓存框架整和,第一反应就行想这个cache接口)

Mybatis中的默认实现类;(可以仿照他自己写一个类,继承cache接口即可)

Mybatis和ehcache整合,Mybatis 和 ehcache整合包中提供了一个cache接口的实现类型。
导入jar包,获取这个实现类
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
mapper.xml配置
<mapper namespace="com.zy.Dao.OrderDao">
<!--type默认类型就是PerpetualCache-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> //ehcache还有其他的cache实现类
</mapper>
配置ehcache配置文件(ehcache.xml,Hibernate可以修改文件位置,Mybatis不知道怎么修改)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!--IDEA:xsi如果报红 打开settings->languages&frameworks->Ignored schemas and dtds ,添加地址 http://ehcache.org/ehcache.xsd-->
<diskStore path="F:\testehcache" />
<!--
以下属性是必须的:
maxElementsInMemory:在内存中缓存的element的最大数目
maxElementsOnDisk : 在磁盘上缓存的element的最大数目,若是0表示无穷大
eternal : 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断
overflowToDisk : 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上
以下属性是可选:
timeToIdleSeconds : 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大
timeToLiveSeconds : 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大
diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。
diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.
diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
Mybatis实现乐观锁
首先数据表需要自已定义一个version表
插入操作(初始值设置0)
INSERT INTO employee(VERSION) VALUES(0)
更新操作,必须要有一个AND VERSION = #{version},#{version},是更新的时候先获取版本号
UPDATE employee SET VERSION = VERSION+1 WHERE id = 1 AND VERSION = #{version};
association/collection使用案例
举例为collection,association的使用基本是和collection一样
Vo对象(使用延迟加载的方式将schoolSportStages等信息存放到List中)
public class CourseScheduleVo {
private String id;
private int weekTimes;
private int beginTimes;
private int endTimes;
private Long courseId;
private String courseName;
private String userName;
private Long userInfoId;
private int beginWeek;
private int endWeek;
private int biweekly;
private Long schoolVenueId;
private String venueName;
private int lastLim;
private int totalLim;
private int rebuildLim;
private int genderLim;
private int maleScale;
private int femaleScale;
private List<SchoolSportStage> schoolSportStages;
private List<SchoolDepartment> schoolDepartmentList;
private List<SchoolMajor> schoolMajorList;
}
Dao
List<CourseScheduleVo> selectByTime(@Param(value = "beginTime") int beginTime, @Param(value = "endTime") int endTime);
mapper.xml(因为使用延迟加载,所以不要关联SchoolSportStage等表)
<resultMap id="CourseScheduleVoMap" type="net.microcarystal.sport.vo.CourseScheduleVo">
<id column="id" property="id"/>
<result column="weekTimes" property="weekTimes"/>
<result column="beginTimes" property="beginTimes"/>
<result column="endTimes" property="endTimes"/>
<result column="courseId" property="courseId"/>
<result column="courseName" property="courseName"/>
<result column="userName" property="userName"/>
<result column="userInfoId" property="userInfoId"/>
<result column="beginWeek" property="beginWeek"/>
<result column="endWeek" property="endWeek"/>
<result column="biweekly" property="biweekly"/>
<result column="schoolVenueId" property="schoolVenueId"/>
<result column="lastLim" property="lastLim"/>
<result column="totalLim" property="totalLim"/>
<result column="rebuildLim" property="rebuildLim"/>
<result column="genderLim" property="genderLim"/>
<result column="maleScale" property="maleScale"></result>
<result column="femaleScale" property="femaleScale"></result>
<collection property="schoolSportStages" column="id" select="getSchoolSportStageList">
</collection>
<collection property="schoolDepartmentList" column="id" select="getSchoolDepartmentList">
</collection>
<collection property="schoolMajorList" column="id" select="getSchoolMajorList">
</collection>
</resultMap>
<select id="selectByTime" resultMap="CourseScheduleVoMap">
select course_schedule.id as id,course_schedule.weekTimes,course_schedule.beginTimes,course_schedule.endTimes,course_schedule.maleScale as maleScale,course_schedule.femaleScale as femaleScale,
course_info.id as courseId,course_info.courseName,user_info.id as userInfoId,user_info.userName,course_schedule.beginWeek,course_schedule.endWeek,
course_schedule.biweekly,school_venue.id as schoolVenueId,school_venue.venueName,course_schedule.lastLim,course_schedule.totalLim,course_schedule.rebuildLim,course_schedule.genderLim
from course_schedule
LEFT JOIN course_info on course_schedule.courseId=course_info.id
LEFT JOIN user_info on course_schedule.teacherId= user_info.id
LEFT JOIN school_venue on course_schedule.venueId = school_venue.id
where course_schedule.`status`=1 and course_schedule.beginTimes>=#{beginTime} and course_schedule.endTimes<=#{endTime}
</select>
foreach遍历对象
dao
int insertList(@Param(value = "item") List<CourseScheduleDepartKey> item);
mapper.xml
<insert id="insertList" parameterType="java.util.ArrayList">
insert into course_schedule_depart (scheduleId, departId)
values
<foreach collection="item" index="index" item="item" separator=","> //collection:就是dao中的item,index好像没用,可以删,
(#{item.scheduleId},#{item.departId}) //我们将"(",")",直接放到了下面,并没有使用open和close
</foreach>
</insert>

浙公网安备 33010602011771号