mybatis学习9

一、一级缓存

package cn.linst;

import cn.linst.mapper.UserMapper;
import cn.linst.model.SysUser;
import org.apache.ibatis.session.SqlSession;
import org.junit.Assert;
import org.junit.Test;


public class CacheTest extends BaseMapperTest {
    
    

    @Test
    public void testLlCache() {
    
    
        //获取 SqlSession

        SqlSession sqlSession = getSqlSession();
        SysUser user1 = null;
        try {
    
    
            //获取 UserMapper 接口
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调用 selectById 方法,查询 id =1 的用户
            user1 = userMapper.selectById(1L);
            //对当前获取的对象重新赋值
            user1.setUserName("New Name");
            //再次查询获取 id 相同的用户
            SysUser user2 = userMapper.selectById(1L);
            //虽然没有更新数据库,但是这个用户名和 user1 重新赋值的名字相同
            Assert.assertEquals("New Name", user2.getUserName());
            // 无论如何, user2和 user1 完全就是同一个实例
            Assert.assertEquals(user1, user2);
        } finally {
    
    
            //关闭当前的 sqlSession
            sqlSession.close();
        }
        System.out.println ("开启新的 sqlSession");
        // 开始另一个新的 session
        sqlSession = getSqlSession();
        try {
    
    
            //获 UserMapper 接口
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            //调 selectById 方法,查询 id =1 的用户
            SysUser user2 = userMapper.selectById(1L);
            // 第二个 session 获取的用户名仍然是 admin
            Assert.assertNotEquals("New Name", user2.getUserName());
            //这里的 user2 和前一个 session 查询的结果是两个不同的实例
            Assert.assertNotEquals(user1, user2);
            //执行删除操作
            userMapper.deleteById(2L);
            //获取 user3
            SysUser user3 = userMapper.selectById(1L);
            //这里的 user2 user3 是两个不同的实例
            Assert.assertNotEquals(user2, user3);
        } finally {
    
    
            //关闭 sqlSession
            sqlSession.close();
        }
    }
}

运行结果:

DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1, admin, 123456, admin_test@admin_test.email, <<BLOB>>, <<BLOB>>, 2020-01-01 01:11:12.0
DEBUG [main] - <==      Total: 1
开启新的 sqlSession
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1, admin, 123456, admin_test@admin_test.email, <<BLOB>>, <<BLOB>>, 2020-01-01 01:11:12.0
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: delete from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 2(Long)
DEBUG [main] - <==    Updates: 0
DEBUG [main] - ==>  Preparing: select * from sys_user where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
TRACE [main] - <==        Row: 1, admin, 123456, admin_test@admin_test.email, <<BLOB>>, <<BLOB>>, 2020-01-01 01:11:12.0
DEBUG [main] - <==      Total: 1

UserMapper.xml:加上flushCache=“true”。

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

二、二级缓存
MyBatis二级缓存不同于一级缓存只存在于 SqlSession 的生命周期中,而是可以理解为存在于 SqlSessionFactory 的生命周期中。
当存在多个 SqlSessioηFactory时,它们的缓存都是绑定在各自对象上的,缓存数据在一般情况下是不相通的。只有在使用如 Redis
这样的缓存数据库时,才可以共享缓存。
1、配置二级缓存
二级缓存配置,在 MyBatis 全局配置 settings 中有一个参数 cacheEnabled ,这个参数是二级缓存的全局开关,默认值是 true ,初始状态
为启用状态。如果把这个参数设置为 false ,即使有后面 二级缓存配置,也不会生效。由于这个参数值默认为 true ,所以不必配置,如果想要配置,可以在mybatis-config.xml加入:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 其他配置-->       
        <setting name="cacheEnabled" value="true"/>
    </settings>
</configuration>

MyBatis 二级缓存是和命名空间绑定的。即 二级缓存需要配置在 Mapper.xml 映射文件中。或者配置在Mapper.java接口中。在映射文件中 命名空间就是 XML 根节点 mapper的namespace 属性。在 Mapper 接口中,命名空间就是接口的全限定名称。
1)Mapper.xml 中配置二级缓存
在保证二级缓存的全局配置开启的情况下,给 RoleMapper.xml 开启二级缓存只需要在UserMapp.xml 中添加</cache>元素即可,添加后的 UserMapper.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="cn.linst.mapper.UserMapper">
    <cache/>
    <!-- 其他配置-->
</mapper>

默认二级有以下效果:

映射语句文件中的所有 SELECT 语句将会被缓存。
映射语句文件中的所有 INSERT UPDATE DETELE 语句会刷新缓存。
缓存会使用 Least Recently Used (LRU ,最近最少使用的)算法来收回。
根据时间表(如 no Flush Int rv ,没有刷新间隔),缓存不会以任何时间顺序来刷新。
缓存会存储集合或对象(无论查询方法返回什么类型的值)的 1024 个引用。
缓存会被视为 read/write (可读/可写)的 意味着对象检索不是共享的,而且可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

如:
UserMapper.xml:

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

这个配置创建了 FIFO 缓存,并每隔60秒刷新1次,存储集合或对象的 512个引用, 而且返回的对象被认为是只读的, 因此在不同线程中的调用者之间修改它们会导致冲突。
cache可配置的属性:

属性 描述
eviction LRU (最近最少使用的) 移除最长时间不被使用的对象,这是默认值;FIFO (先进先出〉 按对象进入缓存的顺序来移除它们;SOFT (软引用) 移除基于垃圾回收器状态和软引用规则的对象;WEAK (弱引用) 更积极地移除基于垃圾收集器状态和弱引用规则的对象。
flushInterval 。可以被设置为任意 正整数 而且它 代表 1个合理的毫秒形式的时间段 。默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新
size 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是 1024
readOnly 属性可以被设置为 true false,只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改, 这提供了很重要的性能优势。读写的缓存会通过序列化返回缓存对象的拷贝 ,这种方式会慢一些,但是安全, 因此默认是 false

2)Mapper 接口中配置二级缓存
当只使用注解方式配置二级缓存时,如果在 RoleMapper 接口中, 需要增加如下配置。
RoleMapper:

@CacheNamespace
public interface RoleMapper {
    
    
// 接口方法
}

@CacheNamespace中其他配置:
RoleMapper:

@CacheNamespace(
     eviction = FifoCache.class,
      flushInterval = 60000,
      size = 512,
      readWrite = true
)
public interface RoleMapper {
    
    
}

如果既接口配置又xml配置一起使用,会报错。需要同时配置,改成以下:
RoleMapper接口:

@CacheNamespaceRef(RoleMapper.class)	// 参照缓存配置RoleMapper.class,即 RoleMapper.xml 中配置的缓存。
public interface RoleMapper {
    
    
}

或者
RoleMapper.xml:

这样配置后, XML 就会引用 Mapper 接口中配置的二级缓存,同样可以避免同时配置二级缓存导致的冲突。
<cache-ref narnespace="cn.linst.mapper.RoleMapper"/>

参照缓存除了能够通过引用其他缓存减少配置外,主要的作用解决脏读。

2、使用二级缓存
Mabatis使用 SerializedCache(org.apache.ibatis.cache.decorators.SerializedCache) 序列化缓存来实现可读写缓存类,井通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个新的实例。因此,如果配置为只读缓存,Mabatis就会使用 Map 来存储缓存值, 这种情况下 ,从缓存中获取的对象就是同一个实例。
因为使用可读写缓存,可以使用 SerializedCache 序列化缓存,这个缓存类要求所有被序列化的对象必须实现 Serializable(java.io.Serializable)接口。
1)SysRole

package cn.linst.model;

import cn.linst.type.Enabled;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

@Setter
@Getter
public class SysRole implements Serializable {
    
    
    private static final long serialVersionUID = 5997519209500342536L;
    //其他属性
}

2)RoleMapper.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="cn.linst.mapper.RoleMapper">

    <!--二级缓存-->
    <cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="false"
    />
    
</mapper>

3)RoleMapper接口:

package cn.linst.mapper;


import cn.linst.model.SysRole;
import org.apache.ibatis.annotations.*;
import org.apache.ibatis.cache.decorators.FifoCache;

import java.util.List;

@CacheNamespaceRef(RoleMapper.class)
public interface RoleMapper {
    
    
//...
}

4)测试类:
RoleMapperTest:

package cn.linst;


import cn.linst.mapper.RoleMapper;
import cn.linst.model.SysPrivilege;
import cn.linst.model.SysRole;
import org.apache.ibatis.session.SqlSession;
import org.junit.Assert;
import org.junit.Test;

import java.util.List;

public class RoleMapperTest extends BaseMapperTest{
    
    
  @Test
    public void testL2Cache() {
    
    
        //获 sqlSession
        SqlSession sqlSession = getSqlSession() ;
        SysRole role1 = null;
        try {
    
    
            //获取 RoleMapper 接口
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            //调用 selectById 方法,查询 id =1
            role1 = roleMapper.selectById(1L);
            //对当前获取的对象重新赋值
            role1.setRoleName("New Name");
            //再次查询获取 id 相同的用户
            SysRole role2 = roleMapper.selectById(1L);
            //虽然没有更新数据库,但是这个用户名和 role1 重新赋值的名字相同
            Assert.assertEquals("New Name", role2.getRoleName());
            // 无论如何, role2 role1 完全就是同一个实例
            Assert.assertEquals(role1, role2);
        } finally {
    
    
            // 关闭当前的sqlsession
            sqlSession.close();
        }
        System.out.println("开启新的 sqlSession");
        // 开始另一个新的 session
        sqlSession = getSqlSession() ;
        try {
    
    
            //获取 RoleMapper 接口
            RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
            //调用 selectById 方法,查询 id =1 的用户
            SysRole role2 = roleMapper.selectById(1L);
            //第二个 session 获取的 用户 名是 New Name
            Assert.assertEquals("New Name", role2.getRoleName());
            //这里的 role2 和前 session 查询的结果是两个不同的实例
            Assert.assertNotEquals(role1, role2);
            //获 role3
            SysRole role3 = roleMapper.selectById(1L);
            //这里的 role2 role3 是两个不同的实例
            Assert.assertNotEquals(role2, role3);
        } finally {
    
    
            //关闭 sqlSession
            sqlSession.close();
        }
    }
}

运行结果:

DEBUG [main] - Cache Hit Ratio [cn.linst.mapper.RoleMapper]: 0.0
DEBUG [main] - ==>  Preparing: select id, role_name roleName, enabled, create_by createBy, create_time createTime from sys_role where id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <==    Columns: id, roleName, enabled, createBy, createTime
TRACE [main] - <==        Row: 1, 管理员, 1, 1, 2020-01-01 17:02:14.0
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [cn.linst.mapper.RoleMapper]: 0.0
开启新的 sqlSession
DEBUG [main] - Cache Hit Ratio [cn.linst.mapper.RoleMapper]: 0.3333333333333333
DEBUG [main] - Cache Hit Ratio [cn.linst.mapper.RoleMapper]: 0.5

MyBatis 默认提供的缓存实现是基于 Map 实现的内存缓存,己经可以满足基本的应用是当需要缓存大量的数据时,不能仅仅通过提高内存来使用 MyBatis 级缓存,还可以选择一些类 EhCache 的缓存框架或 Redis 缓存数据库等工具来保存 Mybatis 二级缓存数据。

三、集成 EhCache 缓存
1、添加依赖
pom.xml:

<!-- ehcache-->
 <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
 <dependency>
   <groupId>org.mybatis.caches</groupId>
   <artifactId>mybatis-ehcache</artifactId>
   <version>1.0.3</version>
 </dependency>

2、配置ehcache
ehcache.xml参考:

https://www.ehcache.org/ehcache.xml

在src/main/resources/目录下新建一个ehcache.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false"
         monitoring="autodetect"
         dynamicConfig="true">       
    <diskStore path="/Users/lst/Desktop/tmp"/>
    <!-- 默认配置-->
    <!--<defaultCache-->
           <!--maxEntriesLocalHeap="3000"-->
           <!--eternal="false"-->
           <!--copyOnRead="true"-->
           <!--copyOnWrite="true"-->
           <!--timeToIdleSeconds="3600"-->
           <!--timeToLiveSeconds="3600"-->
           <!--overflowToDisk="true"-->
           <!--diskPersistent="true"/>-->

	<!-- 针对一个,指定name属性-->
    <cache
            name="cn.linst.mapper.RoleMapper"
            maxEntriesLocalHeap="3000"
            eternal="false"
            copyOnRead="true"
            copyOnWrite="true"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="3600"
            overflowToDisk="true"
            diskPersistent="true"/>

</ehcache>

RoleMapper.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="cn.linst.mapper.RoleMapper">

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
     <!-- 其他配置-->
</mapper>

四、集成Redis缓存
1、引入依赖

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

2、配置redis
在src/main/resources/目录下新建一个redis.properties:

host=localhost
port=6379
connectionTimeout=5OOO
soTimeout=5OOO
password=
database=O
clientName=

3、修改 RoleMapper.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="cn.linst.mapper.RoleMapper">

    <cache type="org.mybatis.caches.redis.RedisCache"/>
    <!-- 其他配置-->
</mapper>

五、脏数据的产生和避免
MyBatis 二级缓存是和命名空间绑定的 ,所以通常情况下每个 Mapper 映射文件都拥有自己的二级缓存,不同 Mapper 二级缓存互不影响。

当某几个表可以作为一个业务整体时,通常是让几个会关联的表同时使用同一个二级缓存,这样就能解决脏数据问题。
修改为参照缓存后,再次执行测试,这时就会发现在第二次查询用户和关联角色信息时并没有使用 级缓存,而是重新从数据库获取了数据。
如:
UserMapper.xml:

<mapper namespace="cn.linst.mapper.UserMapper">
	<cache-ref namespace="cn.linst.mapper.RoleMapper" />
	<!-- 其他配置-->
</mapper>

六、二级缓存适用场景
以查询为主的应用中,只有尽可能少的增、删、改操作。
绝大多数以单表操作存在时。
可以按业务划分对表进行分组时, 如关联的表比较少,可以通过参照缓存进行配置。

猜你喜欢

转载自blog.csdn.net/tongwudi5093/article/details/114847833