
本文共 14837 字,大约阅读时间需要 49 分钟。
第七部分:Mybatis缓存
缓存就是内存中的数据,常常来自于对数据库查询结果的保存,使用缓存,我们可以避免频繁地与数据库进行交互,
进而提高响应速度。
mybatis也提供对缓存的支持,分为一级缓存和二级缓存,可以通过以下的图进行解释
1.一级缓存是SqlSession级别的缓存,在操作时需要构造SqlSession对象,对象中有一个数据结构
HashMap用户存储数据,不同SqlSession的存储区域HashMap互不影响。
2.二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用一个
mapper缓存,二级缓存是跨SqlSession的
7.1 ⼀级缓存
@Testpublic void test1 (){// 根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession = sessionFactory . openSession ();UserMapper userMapper = sqlSession . getMapper ( UserMapper . class );// 第⼀次查询,发出 sql 语句,并将查询出来的结果放进缓存中User u1 = userMapper . selectUserByUserId ( 1 );System . out . println ( u1 );// 第⼆次查询,由于是同⼀个 sqlSession, 会在缓存中查询结果// 如果有,则直接从缓存中取出来,不和数据库进⾏交互User u2 = userMapper . selectUserByUserId ( 1 );System . out . println ( u2 );sqlSession . close ();}
一级缓存模型
@Testpublic void firstLevelCache() { //第一次查询id为1的用户 User user1=iUserMapper.findUserById(1); User user = new User(); user.setId(1); user.setUsername("zhangsan"); iUserMapper.updateUser(user); //第二次查询 User user2=iUserMapper.findUserById(1); System.out.println(user1==user2); }
控制台结果
缓存模型



CacheKey cacheKey = new CacheKey (); //MappedStatement 的 id// id 就是 Sql 语句的所在位置包名 + 类名 + SQL 名称cacheKey . update ( ms . getId ());// offset 就是 0 ,rowBounds是分页参数cacheKey . update ( rowBounds . getOffset ());// limit 就是 Integer.MAXVALUEcacheKey . update ( rowBounds . getLimit ());// 具体的 SQL 语句cacheKey . update ( boundSql . getSql ());// 后⾯是 update 了 sql 中带的参数cacheKey . update ( value );...if ( configuration . getEnvironment () != null ) {// issue #176cacheKey . update ( configuration . getEnvironment (). getId ());}

<environments default = "development" ><environment id = "development" ><transactionManager type = "JDBC" /><dataSource type = "POOLED" ><property name = "driver" value = "${jdbc.driver}" /><property name = "url" value = "${jdbc.url}" /><property name = "username" value = "${jdbc.username}" /><property name = "password" value = "${jdbc.password}" /></dataSource></environment></environments>
Overridepublic < E > List < E > query ( MappedStatement ms , Object parameter , RowBoundsrowBounds , ResultHandler resultHandler ) throws SQLException {BoundSql boundSql = ms . getBoundSql ( parameter );// 创建缓存CacheKey key = createCacheKey ( ms , parameter , rowBounds , boundSql );return query ( ms , parameter , rowBounds , resultHandler , key , boundSql );}@SuppressWarnings ( "unchecked" )Overridepublic < E > List < E > query ( MappedStatement ms , Object parameter , RowBoundsrowBounds , ResultHandler resultHandler , CacheKey key , BoundSql boundSql )throws SQLException {...list = resultHandler == null ? ( List < E > ) localCache . getObject ( key ) : null ;if ( list != null ) {// 这个主要是处理存储过程⽤的。handleLocallyCachedOutputParameters ( ms , key , parameter , boundSql );} else {list = queryFromDatabase ( ms , parameter , rowBounds , resultHandler , key ,boundSql );}...}// queryFromDatabase ⽅法private < E > List < E > queryFromDatabase ( MappedStatement ms , Object parameter ,RowBounds rowBounds , ResultHandler resultHandler , CacheKey key , BoundSqlboundSql ) throws SQLException {List < E > list ;localCache . putObject ( key , EXECUTION_PLACEHOLDER );try {list = doQuery ( ms , parameter , rowBounds , resultHandler , boundSql );} finally {localCache . removeObject ( key );}localCache . putObject ( key , list );if ( ms . getStatementType () == StatementType . CALLABLE ) {localOutputParameterCache . putObject ( key , parameter );}return list ;}
private Map < Object , Object > cache = new HashMap < Object , Object > ();@Overridepublic void putObject ( Object key , Object value ) { cache . put ( key , value );}
7.2 ⼆级缓存

<!-- 开启⼆级缓存 --><settings><setting name = "cacheEnabled" value = "true" /></settings>
如果是使用mapper配置文件进行开发的就是用以下配置
<!-- 开启⼆级缓存 --><cache></cache>
@CacheNamespacepublic interface IUserMapper {
@CacheNamespace(implementation = PerpetualCache.class)

public class PerpetualCache implements Cache {private final String id ;private MapcObject , Object > cache = new HashMapC );public PerpetualCache ( St ring id ) { this . id = id ;}
我们可以看到⼆级缓存底层还是HashMap结构
public class User implements Serializable (// ⽤户 IDprivate int id ;// ⽤户姓名private String username ;}
@Test public void testTwoCache (){// 根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sessionFactory . openSession ();SqlSession sqlSession2 = sessionFactory . openSession ();UserMapper userMapper1 = sqlSession1 . getMapper ( UserMapper . class );UserMapper userMapper2 = sqlSession2 . getMapper ( UserMapper . class );// 第⼀次查询,发出 sql 语句,并将查询的结果放⼊缓存中User u1 = userMapper1 . selectUserByUserId ( 1 );System . out . println ( u1 );sqlSession1 . close (); // 第⼀次查询完后关闭 sqlSession// 第⼆次查询,即使 sqlSession1 已经关闭了,这次查询依然不发出 sql 语句User u2 = userMapper2 . selectUserByUserId ( 1 );System . out . println ( u2 );sqlSession2 . close ();
可以看出上⾯两个不同的sqlSession,第⼀个关闭了,第⼆次查询依然不发出sql查询语句
@Testpublic void testTwoCache (){// 根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sessionFactory . openSession ();SqlSession sqlSession2 = sessionFactory . openSession ();SqlSession sqlSession3 = sessionFactory . openSession ();String statement = "com.lagou.pojo.UserMapper.selectUserByUserld" ;UserMapper userMapper1 = sqlSession1 . getMapper ( UserMapper . class );UserMapper userMapper2 = sqlSession2 . getMapper ( UserMapper . class );UserMapper userMapper3 = sqlSession2 . getMapper ( UserMapper . class );// 第⼀次查询,发出 sql 语句,并将查询的结果放⼊缓存中User u1 = userMapperl . selectUserByUserId ( 1 );System . out . println ( u1 );sqlSessionl . close (); // 第⼀次查询完后关闭 sqlSession// 执⾏更新操作, commit()u1 . setUsername ( "aaa" );userMapper3 . updateUserByUserId ( u1 );sqlSession3 . commit ();// 第⼆次查询,由于上次更新操作,缓存数据已经清空 ( 防⽌数据脏读 ) ,这⾥必须再次发出 sql 语User u2 = userMapper2 . selectUserByUserId ( 1 );System . out . println ( u2 );sqlSession2 . close ();
查看控制台情况:
<select id = "selectUserByUserId" useCache = "false"resultType = "com.lagou.pojo.User" parameterType = "int" >select * from user where id=#{id}</select>
注解开发方式,在对应的接口方法上加上注解:
@Options(useCache = true)//启用二级缓存@Select("select * from user where id=#{id}")public User findUserById(Integer id);
<select id = "selectUserByUserId" flushCache = "true" useCache = "false"resultType = "com.lagou.pojo.User" parameterType = "int" >select * from user where id=#{id}</select>
⼀般下执⾏完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏
7.3 ⼆级缓存整合redis
上⾯我们介绍了 mybatis⾃带的⼆级缓存,但是这个缓存是单服务器⼯作,⽆法实现分布式缓存。 那么


<dependency><groupId> org.mybatis.caches </groupId><artifactId> mybatis-redis </artifactId><version> 1.0.0-beta2 </version></dependency>
<?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.lagou.mapper.IUserMapper" ><cache type = "org.mybatis.caches.redis.RedisCache" /><select id = "findAll" resultType = "com.lagou.pojo.User" useCache = "true" >select * from user</select>
注解配置:
@CacheNamespace(implementation = RedisCache.class) //开启二级缓存public interface IUserMapper {
redis.host = localhostredis.port = 6379redis.connectionTimeout = 5000redis.password =redis.database = 0
4.测试
@Testpublic void SecondLevelCache (){SqlSession sqlSession1 = sqlSessionFactory . openSession (); SqlSession sqlSession2 = sqlSessionFactory . openSession ();SqlSession sqlSession3 = sqlSessionFactory . openSession ();IUserMapper mapper1 = sqlSession1 . getMapper ( IUserMapper . class );lUserMapper mapper2 = sqlSession2 . getMapper ( lUserMapper . class );lUserMapper mapper3 = sqlSession3 . getMapper ( IUserMapper . class );User user1 = mapper1 . findUserById ( 1 );sqlSession1 . close (); // 清空⼀级缓存User user = new User ();user . setId ( 1 );user . setUsername ( "lisi" );mapper3 . updateUser ( user );sqlSession3 . commit ();User user2 = mapper2 . findUserById ( 1 );System . out . println ( user1 == user2 );}
public final class RedisCache implements Cache {public RedisCache ( final String id ) {if ( id == null ) {throw new IllegalArgumentException ( "Cache instances require anID" );}this . id = id ;RedisConfig redisConfig =RedisConfigurationBuilder . getInstance (). parseConfiguration ();pool = new JedisPool ( redisConfig , redisConfig . getHost (),redisConfig . getPort (),redisConfig . getConnectionTimeout (),redisConfig . getSoTimeout (), redisConfig . getPassword (),redisConfig . getDatabase (), redisConfig . getClientName ());}
public class RedisConfig extends JedisPoolConfig {private String host = Protocol . DEFAULT_HOST ;private int port = Protocol . DEFAULT_PORT ;private int connectionTimeout = Protocol . DEFAULT_TIMEOUT ;private int soTimeout = Protocol . DEFAULT_TIMEOUT ;private String password ;private int database = Protocol . DEFAULT_DATABASE ;private String clientName ;
RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要⽅法:
public RedisConfig parseConfiguration ( ClassLoader classLoader ) {Properties config = new Properties ();InputStream input =classLoader . getResourceAsStream ( redisPropertiesFilename );if ( input != null ) {try {config . load ( input );} catch ( IOException e ) {throw new RuntimeException ("An error occurred while reading classpath property '"+ redisPropertiesFilename+ "', see nested exceptions" , e );} finally {try {input . close ();} catch ( IOException e ) {// close quietly}}}RedisConfig jedisConfig = new RedisConfig ();setConfigProperties ( config , jedisConfig );return jedisConfig ;}
核⼼的⽅法就是parseConfiguration⽅法,该⽅法从classpath中读取⼀个redis.properties⽂件
host = localhostport = 6379connectionTimeout = 5000soTimeout = 5000password = database = 0 clientName =
private Object execute ( RedisCallback callback ) {Jedis jedis = pool . getResource ();try {return callback . doWithRedis ( jedis );} finally {jedis . close ();}}
模板接⼝为RedisCallback,这个接⼝中就只需要实现了⼀个doWithRedis⽅法⽽已:
public interface RedisCallback {Object doWithRedis ( Jedis jedis );}
@Overridepublic void putObject ( final Object key , final Object value ) {execute ( new RedisCallback () {@Overridepublic Object doWithRedis ( Jedis jedis ) {jedis . hset ( id . toString (). getBytes (), key . toString (). getBytes (),SerializeUtil . serialize ( value ));return null ;}});}@Overridepublic Object getObject ( final Object key ) {return execute ( new RedisCallback () {@Overridepublic Object doWithRedis ( Jedis jedis ) {return SerializeUtil . unserialize ( jedis . hget ( id . toString (). getBytes (),key . toString (). getBytes ()));}});}
可以很清楚的看到,mybatis-redis在存储数据的时候,是使⽤的hash结构,把cache的id作为这个hash
发表评论
最新留言
关于作者
