MyBatis技术全讲(全)

  • 配置
  • 简单查询
  • 高级查询
  • 代码生成器
  • 缓存配置

前言

这里要提到一个词:关注点分离。业务逻辑与数据逻辑相分离。没必要为每一个DAO对象的初始化过程编写大量代码,只需关注其SQL,并将SQL从代码中分离出来,mybatis就是这样一个实现关注点分离思路的框架。
本篇为mybatis学习笔记,设计到mybatis的环境搭建、配置详解、简单应用、代码生成器、高级查询、缓存配置。基本上是全了。
前身是iBatis,以接近JDBC的性能实现了Java代码与SQL语句的分离。但是mybatis有其自己不完美的地方:物理分页、缓存。
mybatis支持声明式缓存,当一条数据被标记为“可缓存”后,首次执行它所获取的数据会被缓存到高速缓存中,后面再次执行直接命中缓存,基于HashMap实现。

配置

配置文件
mybatis-config.xml
mapper.xml

查询

四个基础标签
<select>、<insert>、<update>、<delete>
select

单表


Model类
package com.abc.model;

public class User{
	public String name;
	public Integer age;
}

查询写法
<select id=”selectUserById”resultType=”com.abc.model.User”>
	select name,age from user where id=#{id}
</select>


调用
User user = selectUserById(1001);

第二个例子

public class User{
	public String name;
	public Integer age;
	public Role role;
}

public class Role{
	public String roleId;
	public String roleName;
}

<select id=”selectUserById”resultType=”com.abc.model.User”>

select name,age,role.Id,role.roleName 
from user u 
inner join role r on u.id=r.id 
where u.id=#{id}

</select>

第二个例子另一种写法

<resultMap id=”userMap” type=”com.abc.model.User”>
	<id property=”id” column=”id”/>
	<result property=”name” column=”name”/>
	<result property=”age” column=”age”>
</resultMap>


<select id=”selectUserById”resultMap=”userMap”>
select name,age 
from user u 
inner join role r on u.id=r.id 
where u.id=#{id}
</select>

多表

当返回值涉及一个model时,和单表一样。如果有多余返回值,可以嵌套model,在写xml写sql时,select u.user_name as “user.userName”

insert

获取主键值
自增方式的数据库:

<insert id="insert" userGenerateKeys="true" keyProperty="id">
insert into ..
</insert>

序列的:

<insert id="insert" userGenerateKeys="true" keyProperty="id">
insert into ..
<selectKey keyColume=="id" resultType="long" keyProperty="id" order="before"> //oracle的序列是先获取的,随意要before。mysql的自增主键是插入后才有的,要改成after。
//mysql  SELECT LAST_INSERT_ID()
//oracle SELECT SEQ_ID.nextval from dual
//这里的写法不同数据库不一样
</selectKey>
</insert>

用法

<update id="updateById">
	updatre sys_user
	set user_name=#{userName},
	user_password=#{userPassword},
	user_email=#{userEmail},
	user_info = #{userInfo}
	hear_img = #{headImg,jdbcType=BLOB},
	create_time=#{createTime,jdbcType=TIMESTAMP}
	where id=#{id}
</update>

在UserMapper中添加接口
int updateById(SysUser sysUser);
测试类:

public void testUpdateById(){
	Reader reader = Resource.GetResourceAsReader("mybatis-config.xml");
	SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().builder(reader);
	SqlSession sqlSession = sqlSessionFactory.openSession();
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	userMapper.updateById.......//大概流程如此
}

多查询条件,接口需要修改为:

List<SysRole> selectRolesByUserIdAndRoleEnable(@Param("userId")Long userId,@Param("enable")Integer enabled);

如果入参为对象

List<SysRole> selectRolesByUserIdAndRoleEnable(@Param("user")User user,@Param("role")SysRole role);

在xml使用的时候,要用user.userId和role.enable了。和上面查询一样。

delete update略
Mapper动态代理实现

首先声明一个接口

public interface TestMapper{
	List<User> selectAll();
}

创建一个代理类

public class MyMapperProxy<T> implements InvocationHandler{
	private Class<T> myMapper; 
	private SqlSession sqlSession;
		public MyMapperProxy(Class<T> myMapper,SqlSession sqlSession){
		  this.myMapper = myMapper;
		  this.sqlSession = sqlSession;
	}
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
		List<T> list = sqlSeesion.selectList(mapperInterface.getCanonicalName()+"."+method.getName());
		return list;
	}
}

这样就获取了interface的全限定名,所以mybatis是一种约定大于配置的方式简化了操作。

Mybatis注解的用法

Insert注解,不需要返回主键的情况:

@Insert("insert into sys_role(id,name) values(#{id},#{name,jdbcType=String})")

需要返回主键的情况(自增主键)

@Insert("insert into sys_role(name) values(#{name,jdbcType=String})")
@Options(useGeneratedKsys=true,keyProperty="id")

需要返回主键的情况(非自增主键),xml里用的selectKey标签

@Insert("insert into sys_role (name,enable,create_time),values(#{name},#{enable},#{createTime,jdbcType=TIMESTAMP})")
@SelectKey(statement="SELECT LAST_INSERT_ID()",keyProperty="id",resultType=Long.class,before=false)

Update和Delete注解
同上,没什么特殊的。
Provider注解
除了上述增删改查意外,mybatis还提供了四种Provider注解。分别为:@SelectProvider @InsertProvider @UpdateProvider @DeleteProvider。
就是将SQL封装到代码里。

@SelectProvider(type=PrivilegeProvider.class,method="selectById")
SysPrivilege selectById(Long id);

然后PrivilegeProvider类如下

public class PrivilegeProvider{
	public String selectById(final Long id){
		return new SQL(){
			SELECT("id,privilege_name,privilege_url");
			FROM("sys_privilege");
			WHERE("id=#{id}");
		}.toString();
	}
}

由于MyBatis注解方式不是主流,仅做了解。

动态SQL

MyBatis强大特性之一便是这一节的东西。MyBatis使用OGNL表达式,用于xml配置中。支持如下几种标签:

<if />		判断
<choose />	选择
<where />	条件
<set />		更新设值
<trim />	按规则替换
<foreach />
<bind />

其中where、set、trim这三个标签解决了类似问题,并且where、set都属于trim的一种具体用法。

if标签

有如下查询场景:当用户传入name时,根据name查,当输入的是email时,根据email查。如何用OGNL表达式来写这条sql呢?

<select id="selectByUser" resultType="com.abc.simple.model.user">
 select id,
	user_name userName,
	user_password userPassword,
	user_email userEmail,
	user_info userInfo
from sys_user
where 1=1
<if test="userName != null and userName!=">
	and user_name like concat('%',#{userName},'%')  //concat是mysql语法
</if>
<if test="userEmail !=null and userEmail!=">
	and user_email = #{userEmail}
</if>
</select> //注意首先判断非空,然后再判断值。两个if标签可同时为真。

if标签有一个必填的属性test,test的属性值一个符合OGNL要求的判断表达式,里面就是一个条件判断语句,支持and or,分组判断,嵌套判断。

update中使用if标签

<update id="updateById">
 update sys_user
set
<if test="userName!=null and userName!=">
	user_name = #{userName},
</if>
<if test="userPassword!=null and userPassword!=">
	user_password = #{userPassword},
</if>
	id=#{id}
	where id=#{id}
</update> //1、注意逗号 2、注意where条件前的语句id=#{id},这条语句可以最大限度的保证不出错。

insert中使用if标签

<insert id="insert1" useGeneratedKey="true" keyProperty="id">
insert into sys_user(
 user_name,user_password,<if test="userEmail !=null and userEmail!=">user_email,</if>userinfo
)values(#{userName},#{userPassword},<if test="userEmail !=null and userEmail!=">#{userEmail},</if>)#{userInfo}
</insert>

字段和值的条件判断要一致。

choose标签

choose的作用就如同if…else,语句为choose when otherwise

<select id=”selectByUser” resultType=”com.abc.model.User”>
    select * from user where 1=1
    from user 
    where 1=1
    <choose>
	<when test=”id!=null”>	
		and id =#{id}
	</when>
	<when test=”userName!=null and userName!='' ”>
		and userName=#{userName}
	<otherwise>
		and 1=2
	<otherwise>
    </choose>
</select>

最后这个1=2是为了当什么都没有输入的时候,返回为空。因为这个条件肯定是false的。

where标签

where标签的作用:如果标签包含的元素中有返回值,那么就插入一个where,如果where后面的字符串以and和or开头,就删除它。

<select id=”selectUser” resultType=”com.abc.model.User”>
	select * from user
	<where>
		<if test=”userName!=null and userName!='' ”>
		and user_name like concat('%',#{userName},'%')
		</if>

		<if>
		and user_email = #{userEmail}
		</if>
	</where>
</select>
set标签

set标签作用:如果该标签包含的元素中有返回值,就插入一个set,如果set后面的字符串是以逗号结尾的,就将逗号去掉。

<update id=”updateUserById”>
	update user
	<set>
		<if test=”userName!=null and userName!='' ”>
			user_name=#{userName},
		</if>
		<if test=”userEmail!=null and userEmail!='' ”>
			user_email=#{userEmail},
		</if>
		<if test=”headImg!=null”>
			head_img=#{headImg,jbdcType=BLOB},
		</if>
		where id=#{id}
	</set>
</update>
trim标签

where和set标签的功能都是通过trim来实现的

where对应的是:

<trim prefix=”WHERE”prefixOverrides=”AND |OR ”>
</trim>

set对应的是:

<trim prefix=”SET” suffixOverrides=”,”>
</trim>

prefixOverrides:当trim元素内包含内容时,会把内容中匹配的前缀字符串去掉
suffixOverrides:当trim元素内包含内容时,会把内容中匹配的后缀字符串去掉

foreach标签

批量查询,在SQL中会使用类似 id in (1,2,3)

<select id=”selectByIdList”resultType=”com.abc.model.User”>
	select * from user
	where id in
	<foreach collection=”list”
		open=”(” 
		close=”)” 
		separator=”,”
		item=”id” 
		index=”i”>
		#{id}	
	</foreach>
</select>

批量插入是SQL-92新增特性,目前支持的数据库有DB2、SQL Server2008、MySQL、SQLite3.7.11以上版本。(2017年11月再版)

INSERT INTO tablename (column-a,column-b,column-c) values (value-a’,value-b’,‘value-c’);

<insert id="insertList">
insert into sys_user(user_name,user_password,user_email)
values
<foreach collection="list" item="user" separator=",">
(
	{user.userName},#{user.userPassword},#{user.userEmail}
)
</foreach>
</insert>

foreach插入map类型

<foreach collection="_parameter" item="val" index="key" separator=",">
(
	${key}=#{val}
)
</foreach>

注:$取值和#取值的区别。
1.取值会将输入当成字符串,比如输入123,输出"123",可以防sql注入
$取值将输入原样输出,比如输出123,输出123。

bind标签

bind可以绑定变量,避免更换数据库造成的修改sql,当然只是在一定语法程度上。

<if test="userName!=null">
<bind name="nameLike" value="'%'+userName+'%'">
	and user_name like #{nameLike}
</if>

多数据支持

提供了一个databaseId的属性值,增删改查、selectKey,sql标签都包含这个属性。
在mybatis-config.xml中配置,并给对应的数据库id起了一个别名。

<databaseIdProvider type="DB_VENDOR">
<property name="SQL Server" value="sqlserver">
<property name="DB2" value="db2">
...
</databaseIdProvider>

然后在写select标签时,加上

<select id=”selectUserById” databaseId=”mysql”></select>
<select id=”selectUserById” databaseId=”oracle”></select>

比如针对oracle和mysql的like查询

<select id="selectByUser" databaseId="mysql" resultType="com.abc.model.user">
	select * from sys_user where user_name like concat('%',#{userName},'%')
</select>
<select id="selectByUser" databaseId="oracle" resultType="com.abc.model.user">
	select * from sys_user where user_name like '%'||#{userName}||'%'
</select>

就可以根据不同的数据库进行操作,比如mysql有自增主键,oralce有序列。

OGNL语法

a or b
a and b
a==b 或 a eq b
a!=b 或 a neq b
a lt b 小鱼
a lte b 小鱼等鱼 gt大鱼 gte大鱼等鱼
a+b a*b a/b a-b a%b
!a not a
a.method(args) 调用对象方法 比如判断 list.size()>0
a.property 对象属性值 比如user.address.name,比如保证存在这个字段,不然会报错
a[index]按索引取值
@class@method(args)调用静态类方法 比如test="@com.abc.utils.StringUtils@isNotEmpty(userName)"
@class@field调用类的静态字段

MyBatis代码生成器

MyBatis代码生成器又叫MyBatis Generator,缩写MBG,这东西是用来根据数据库来自动生成mapper文件、model文件、dao文件。
运行方式1:下载mybatis-generatorjar文件,然后编写一个xml文件。用

java -jar mybatis-generator-core-1.3.2.jar -configfile generatorConfig.xml -overwrite

运行,还要准备一个数据库连接的jar。
运行方式2:通过maven获取

<dependency>
	<groupId>org.mybatis.generator</groupId>
	<artifactId>mybatis-generator-core</artifactId>
	<version>1.3.3</version>
</dependency>

然后创建Generator.java类

public static void main(String args[]){
	 List<String> warnings = new ArrayList<String>();
	 boolean override = true;
	 InputStream is = Generator.class.getResourceAsStream("/generator/generatorConfig.xml");
	 ConfigurationParser cp = new ConfigurationParser(warnings);
	 Configuration config = cp.parseConfiguration(is);
	 is.close();
	 DefaultShellCallBack callback = new DefaultShellCallBack(override);
	 //创建MBG
	 MyBatisGenerator myBatisGenerator = new MyBatisGenerator(conifg,callback,warnings);
	 //执行生成代码
	 myBatisGenerator.generate(null);
	 //输出警告信息
	 for(String warning : warnings){
	   syso(warning);
	 }
}

使用java代码好处是,可以在当前项目中找一些自定义类,比如里配置的自定义注释类。
运行方式3:maven plugin方式
运行方式4:eclipse插件方式 安装mybatis-generator的eclipse插件
MBG有丰富的配置可供使用,所以首先来看MBG的xml配置。首先在xml文件中引入MBG的dtd文件。dtd是用来定义标签的。mybatis-generator-config_1_0.dtd
然后所有的配置要写在

<generatorConfiguration>
	<properties></properties> 最多配置一个
	<classPathEntry></classPathEntry> 选填
	<context></context> 至少配置一个
</generatorConfiguration>

MyBatis高级查询

高级结果映射
一对一
<select>
	select u.id,
	u.user_name userName,
	u.user_info userInfo,
	r.id "role.id",
	r.role_name "role.roleName"
	from sys_user u 
	inner join sys_user_role ur on u.id=ur.user_id
	where u.id=#{id}
</select>

User这样写

public User{
	@setter @getter public UserRole role;
}

就可以实现嵌套结果映射,mybatis就将查询结果映射到两个类里了。这样写的好处就是降低数据库的访问次数,缺点是要写复杂的sql。
要实现上述功能,mybatis还可以用reusltMap来实现啊。其实这俩是一个东西。

<resultMap>
	<result property="role.roleName" column="role_name" jdbcType="String"/>
</resultMap>

如果有十来个属性,那么写起来比较费劲。这时候mybatis的resultMap的继承就起作用了。
将通用的写一个resultMap,需要inner join查其他表的属性,就继承一下。

<resultMap id="userRoleMap" extends="userMap" type="com.abc.model.user">
</result>

在此基础上,继续做修改,resultMap有一个子标签用来配置复杂映射关系

<resultMap id="userRoleMap" extends="userMap" type="com.abc.model.user">
  <association property="role" columnPrefix="role_" javaType="com.abc.mode.role">
    <result />
  </association>
</resultMap>

在inner join的时候,和sys_role相关的要加上role_前缀。

<association resultMap="">功能也可以将association内的result标签独立出来。
<association>还实现了一个重要的功能:嵌套查询的延迟加载。
<association
	 property="role"
	 fetchType="lazy"
	 select="com.abc.mybatis.mapper.RoleMapper.selectRoleById"
	 column="{id=role_id}"
/>

在RoleMapper.xml中加入

<select id="selectRoleById" resultMap="roleMap">
	select * from sys_role where id=#{id}
</select>

然后在mybatis-config.xml中配置

<settings>
	<setting name="aggressiveLazyLoading" value="false" />
</settings>

即可。但是注意,在和Sping集成时,要确保只能在Service中使用延迟加载属性,因为SqlSession的生成周期交给了Spring来管理,当对象的生命周期超出了SqlSession的生命周期时,就会报错。在Controller查询会因为SqlSession已经关闭。
当我们想主动触发延迟加载时,mybatis提供了另一个参数来解决lazyLoadingTriggerMethods,当调用默认方法“equals、clone、hashCode、toString”时会触发。

一对多

就是将结果映射成一个List,如下:
@setter @getter List roleList;

<resutMap>
<result />
...
<collection property="roleList" columnPrefix="role_" ofType="com.abc.model.SysRole">
		<result property="id" column="id" />
		<result property="roleName" column="role_name">
</collection>
</resultMap>

简而言之就是把association变成了collection。
原理:mybatis会根据
主键,来讲查出来的数据,userMap id相同的合并。
利用resultMap嵌套的特性,可以实现多层model的嵌套查询。

collection集合的嵌套查询

association关联的嵌套查询这种方式会执行额外的SQL查询,映射配置会简单很多。下面是collection的嵌套映射。
首先新增PriMapper.xml

<select id="selectPriByRoleId" resultMap="priMap">
	select p.* from sys_pri p where role_id=#{id}
</select>
<resultMap id="rolePriListMapSelect" extends="roleMap" type="com.abc.model.Role">
<collection property="priList"
        fetchType="lazy"
        column="{roleId=id}"
        select="com.abc.simple.mapper.PriMapper.selectPriByRoleId" />
</resultMap>
<select id="selectRoleByUserId" resultMap="rolePriListMapSelect">
	select r.id,r.role_name,r.role_enable,r.create_time from sys_role r inner join sys_user_role ur on ur.role_id=r.id where ur.user_id=#{userId}
</select>

最顶层用户信息

<resultMap id="userRoleListMapSelect" extends="userMap" type="com.abc.model.User">
<collection property="roleList"
        fetchType="lazy"
        select="com.abc.simple.mapper.RoleMapper.selectRoleByUserId"
        column="{userId=id}" />
</resultMap>
<select id="selectAllUserAndRolesSelect" resultMap="userRoleListMapSelect">
select u.id,
    u.user_name,
    u.user_info,
    u.create_time
from sys_user u where u.id=#{id}
</select>

这样就实现了多层collection嵌套循环
单元测试

@Test
public void testSelectAllUserAndRolesSelect(){
	SqlSession sqlSession = getSession();
	//省略try catch
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	SysUser user = userMapper.selectAllUserAndRolesSelect(1L);
	syso(user.getUserName);
	//开始延迟加载
	foreach(SysRole role : user.getRoleList()){
		syso(role.getRoleName);
		//继续延迟加载
		for(SysPri pri : role.getPriList()){
			syso(pri.getPriName());
		}
	}
}
鉴别器映射

有时一个单独数据库查询会返回很多不同类型的数据,类似Java中的switch语句。这个功能很强大。
比如当我们查询一个分类的科目时,如果该分类的状态为不可用状态,那么就不去查他的科目了。下面这个例子逻辑一样。

<resultMap id="rolePriListMapChoose" type="com.abc.model.SysRole">
   <discriminator column="enable" javaType="int">
       <case value="1" resultMap="rolePriListMapSelect" />
       <case value="0" resultMap="roleMap" />
    </discriminator>
</resultMap>

根据enbale的值,来映射不同的resultMap。

存储过程
<select id="selectUserById" statementType="CALLABLE" useCache="false">
{
  call select_user_by_id(
   #{id,mode=IN},
   #{userName,mode=OUT,jdbcType=VARCHAR},
   #{userInfo,mode=OUT,jdbcType=VARCHAR},
   #{createTime,mode=OUT,jdbcType=TIMESTAMP},
   #{headImg,mode=OUT,jdbcType=BLOB,javaType=_byte[]}
)}
</select>

使用mybatis调用存储过程,不支持mybatis二级缓存,所以直接把缓存useCache=“false”,一定要注意基本类型的映射。比如上面的headImg。这个存储过程没有返回值,使用出参的方式获得了,相当于c#中的
out user
使用出参方式获取返回值,必须保证所有出参在JavaBean中都存在。因为JavaBean对象中不存在出参对应的setter方法。
下面一个存储过程场景:分页查询
需要返回两个值:一个是List 一个是int total。返回值用resultMap接,total用上述的方式接。
插入、删除也是

<insert id="insert" statementType="CALLABLE">
{call insert_user(
  #{user.id,mode=OUT,jdbcType=BIGINT},
  #{user.userName,mode=IN},
  #{user.headImg,mode=IN,jdbcType=BLOB},
  #{roleIds,mode=IN}
)}
</insert>
 
<delete id="delete" statementType="CALLABLE">
{call delete_user(
	{id,mode=IN}
)}
</delete>

oracle带游标的存储过程
游标其实对应多个sql语句。创建两个参数的游标:

create or replace procedure SELECT_COUNTRY(ref_cur1 out sys_refcursor,ref_cur2 out sys_refcursor)
is begin
open ref_cur1 for select * from country where id<3;
open ref_cur1 for select * from country where id>=3;
end SELECT_COUNTRY;

CountryMapper.xml写法

<select id="selectCountry" statementType="CALLABLE" useCache="false">
{
call SELECT_COUNTRY(
 #{list1,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=BaseResultMap},
 #{list2,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=BaseResultMap},
)
}
</select>
对枚举值的映射

MyBatis在遇到枚举值时,默认使用TypeHandler处理,这个处理器会将枚举值转换为字符串类型的字面值并使用,对Enable而言便是disable和enable字符串
所以不能使用默认的,使用另一个EnumOrdinalTypeHandler,这个处理器使用枚举的索引进行处理,在mybatis-config.xml中添加

<typeHandlers>
	<typeHandler javaType="com.abc.type.Enable这个是枚举类" handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</typeHandlers>

还可以实现自己的类型处理器,继承实现TypeHandler接口。1.6对Java 8日期(JSR-310)的支持,在pom.xml中引入依赖

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-typehandlers-jsr310</artifactId>
	<version>1.0.2</version>
</dependency>

MyBatis缓存配置

一级缓存

在一个SqlSession中,如果查询的方法和参数都一样,那么第二次查的就不会从数据库里查了,第一次查询的结果会以Map形式存在缓存里。这时候如果对第一次查询的结果进行修改操作,也会影响第二次查询的结果。因为这俩的引用完全一样。如何避免这种情况?在查询语句上新增一个属性

<select id="selectById" flushCache="true" resultMap="userMap">
	select * from user where id =#{id}
</select>

flushCache="true"会保证每次查询都从数据库里查,这时候如果查两次,那么返回的实例对象的引用就不同了。要避免这么做。还有一种刷新缓存的方式,就是执行insert update delete的时候,会自动清空一级缓存。如果在一个SqlSession中,先查id=1的User1,再查一次id=1的User2,那么User1和User2是同一个东西。如果先查id=1的User1,然后随便删除或更新一个id=2的User,然后再查id=1的User2,那么User1和User2就不是同一个引用了。因为缓存被清空了。
刚才说到,是在一个SqlSession中。如果打开了两个SqlSession,那么第一个SqlSession查出来的User和第二个SqlSession查出来的User对象,尽管值一样,但是是不同的引用对象。
这就是MyBatis的一级缓存。

二级缓存

我们常说的MyBatis缓存其实是值的它的二级缓存,因为一级缓存是默认不可被控制的。
MyBatis二级缓存非常强大。在存在于比SqlSession范围更大的SqlSessionFactory生命周期中。

二级缓存配置

mybatis-config.xml中添加配置,开启二级缓存

<settings>
<setting name="cacheEnabled" value="true" />
</settings>
xml中添加缓存配置

保证上面缓存全局开启的情况下进行,在UserMapper.xml中追加cache标签即可

<mapper namespace="com.abc.mapper.UserMapper">
<cache />
</mapper>

添加这句话有什么效果呢?
1、所有SELECT语句会被缓存
2、所有insert update delete会刷新缓存
3、使用LRU算法来回收 least recently used
4、根据时间表,缓存不会以任何时间来刷新,可以设置
5、缓存会存储集合或对象的1024个引用
6、缓存是可读可写的

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readonly="true"
/>

这个配置更为高级,创建了一个FIFO缓存,每60秒刷新一次,存储512个引用,只读的。
cache的eviction回收策略属性值如下:
LRU 最近最少使用
FIFO 按进入缓存的顺序
SOFT 移除基于GC规则的软引用
WEAK 移除基于GC规则的弱引用

接口里配置

在使用注解方式时,如果想对注解方法启用二级缓存,还需要在Mapper接口中建配置。如果Mapper接口也存在对应的zml映射文件,两者同时开启缓存时,还需要特殊配置。只在Mapper接口中配置的话,在UserMapper的interface中

@CacheNamespace(
eviction=FifoCache.class,
flushInterval=60000,
size=512,
readWrite=true
)

如果接口不存在使用注解式的方法,可以只在xml中配置,否则要在

@CacheNamespaceRef(UserMapper.class)
public interface UserMapper{
}

因为想让接口方法和xml文件使用相同的缓存,就要这样。然后在UserMapper.xml中添加

<cache-ref namespace="com.abc.mapper.UserMapper"/>

这样配置后,xml就会引用Mapper接口中配置的二级缓存,同样可以避免同时配置二级缓存导致的冲突。
MyBatis中很少同时使用Mapper接口注解方式和xml映射文件,所以参照缓存不是为了解决这个问题而设计的。参照缓存除了能够通过引用减少配置外,主要作用是用来解决脏读。

使用二级缓存

如果缓存配置的是只读的,那么无所谓,从缓存里读的对象就是同一个实例。
如果配置成可读可写的,mybatis使用SerializedCache来序列化可读可写缓存类。被序列化的对象要实现Serializable接口。
二级缓存什么时候会有数据呢?看下面的流程
1、开启SqlSession
2、查询id=1的用户
3、再次查询id=1的用户
这时候2和3查出来的是一个
4、关闭SqlSession
5、开启新的SqlSession
6、查询id=1的用户
7、再次查询id=1的用户
这时候虽然6和2查出来的不是一个引用,但是6并没有去查数据库,而是去二级缓存里查了。因为是可读可写的情况,6查出来的和7查出来的都是经过反序列化的,不是同一个实例。但6和7都没有去数据库查,都到二级缓存里去查了。二级缓存是当这个查询所在的SqlSession执行close()方法后,才将数据刷到二级缓存里。
这里可能会有疑问,如果我在第2步查出来数据后,修改其信息,那么后来查出来的不就都出问题了吗?Yes,所以不要做无意义的修改,避免人为增加脏数据。
MyBatis是通过Map来实现缓存的。少量数据可以,大量数据怎么办?
可以通过其他工具或框架来保存。

集成EhCache缓存

EhCache是一个纯Java进程内缓存,快速、简单、多种策略、内存和磁盘两级、可通过RMI实现分布式。该项目由MyBatis官方提供,叫ehcache-cache。https://github.com/mybatis/ehcache-cache
依赖

<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-ehcache</artifactId>
	<version>1.0.3</version>
</dependency>
集成Redis

MyBatis还提供了二级缓存的Redis支持,命名为redis-cache。https://github.com/mybatis/redis-cache。

<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-redis</artifactId>
	<version>1.0.0-beta2</version>
</dependency>

集成基本上的套路都是引用依赖,在src/main/resources新增配置文件,在mybatis-config.xml中配置缓存标签。

脏数据

二级缓存虽然能提高效率,但是也容易产生脏数据,由于多标联查时。要掌握避免脏数据的技巧。比如参照缓存。

二级缓存适用场景

1、以查询为主,少的增删改
2、绝大多数单表操作
3、按照业务对表进行分组时,如果关联表比较少,可以使用参照缓存。
在使用二级缓存时,一定要考虑脏数据对系统的影响,在任何情况下,都可以考虑在业务层使用可控制的缓存来代替二级缓存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值