mybatis学习笔记二

一、Mybatis缓存

mybatis提供了对缓存的支持,分为一级缓存和二级缓存,可以通过对下图来理解

1、一级缓存是SqlSession级别的,在操作数据库是需要构造SqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SqlSession之间的缓存数据区域(HashMap)是相互不影响的。

HashMap中的key包含:statementId(namespace.id)、params(sql参数)、boundSql(要执行的sql)、rowBounds(分页查询)

测试:

(1)我们在一个SqlSession中,对User表通过id查询两次,看他们发出sql的情况

@Test
public void test1(){
//根据 sqlSessionFactory 产生 session
SqlSession 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();
}

查看控制台打印情况:

(2)同样是对User表进行两次查询,但查询之间进行一次update操作

@Test
public void test2(){
//根据 sqlSessionFactory 产生 session
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次查询,发出sql,将结果放入缓存
User u1 = userMapper.selectUserByUserId( 1 );
System.out.println(u1);
//进行更新操作sqlSession.commit()
u1.setSex("女");
userMapper.updateUserByUserId(u1);
sqlSession.commit();
第二次查询,由于是同一个sqlSession,会在缓存中查询结果
User u2 = userMapper.selectUserByUserId(1);
System.out.println(u2);
sqlSession.close();
}

控制台打印情况:

总结:①第一次查询先去缓存中查找,如果没有则去数据库中查找并将信息放入缓存中

           ②如果中间Sqlsession去执行commit操作(新增、删除、修改),则怀清空Sqlsession中的一级缓存,避免脏读

           ③第二次发起查询时,先去缓存中查找,有直接从缓存中获取

1.1,mybatis一级缓存源码剖析:

(1)一级缓存是什么,一级缓存什么时候被创建的,一级缓存工作流程是什么样的?我们带着这几个问题翻看mybatis源码。从上面可以看出,我们一讲到一级缓存就离不开Sqlsession,所以我们从Sqlsession入手,看看有没有与缓存相关的方法或属性。idea打开Sqlsession接口:

我们发现这个接口里只有clearCache()这个方法和缓存有关,再找到这个实现方法,一直顺着往下点。

最后在PerpetualCache这个类里面会调用:

这里我们就可以看出一级缓存其实就是一个HashMap,每一个Sqlsession都会存放一个map对象的引用,那么这个cache是何时被创建的呢?

上篇文章我们讲到自定义mybatis框架,其中Excutor是一个执行器,去执行sql代码的,而缓存就是在执行完sql查询后将查询结果放到缓存中,所以,我们去翻阅Excutor这个接口。

点进去看看,发现createCacheKey是由BaseExecutor执行的,代码如下:

CacheKey cacheKey = new CacheKey();
//MappedStatement􁌱id
// id就是namespace.id
cacheKey.update(ms.getId());
// offset 就是0 分页起始
cacheKey.update(rowBounds.getOffset());
// limit 就是 Integer.MAXVALUE
cacheKey.update(rowBounds.getLimit());
// 具体sql语句
cacheKey.update(boundSql.getSql());
//后面是update了sql中带的参数
cacheKey.update(value);
...
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}

创建缓存key会经过一系列update方法,update方法由CacheKey这个对象执行,这个update方法最终由updateList把这六个值存进去,这六个值如下:

这里需要注意的是最后一个值,configuration.getEnvironment().getId()是什么?其实就是核心配置文件中sqlMapconfig.xml中的标签:

<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>

(2)创建完的缓存什么时候被使用了,我们都知道第二次查询的时候会去查询缓存,所以,我们找到Executor中的query方法,实现方法也是BaseExecutor中的query方法

@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 先创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds
rowBounds, 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, BoundSql
boundSql) 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;
}

2、二级缓存是Mapper级别的。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

和一级缓存默认开启不一样,二级缓存是需要手动开启的

(1)首先在全局配置文件中sqlMapConfig.xml文件中配置:

<!--开启二级缓存 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>

(2)在UserMapper.xml文件中开启缓存

<!-- 开启缓存 -->
<cache></cache>

我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置PerpetualCache类,这个类是mybatis默认实现缓存功能的类。我们不写type就是使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。

(3)开启二级缓存后,还需将要缓存的pojo实现Serializable接口(因二级缓存数据存储介质多种多样,不一定只在内存中,也可能在硬盘中),所以需要反序列化

(4)测试

@Test
public` `void` `testTwoCache(){
//根据 sqlSessionFactory 产生 session
SqlSession 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,二级缓存是跨SqlSession。但和一级缓存一样,如果两次查询之间有(增删改),则也会清空二级缓存。

(5)useCache和flushCache

我们可以在单个查询语句来之配置这两个值

useCache用来配置是否开启二级缓存,默认为true(开启),false为禁用。flushCache用来配置是否刷新缓存,默认为true(开启 )

3,二级缓存整合Redis

上面讲的mybatis自带二级缓存,但只适用于单台服务器,如果有多台服务器就会有问题。用户1访问了服务器把缓存信息存在服务器1上,而用户2刚好访问了服务器2,这时就拿不到缓存,得重新去数据库查询,失去了二级缓存意义。所以,mybatis提供了Redis来实现分布式缓存。

实现:

(1)添加pom文件

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

(2)配置Mapper.xml文件

<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>
</mapper>

(3)配置redis.properties

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

(4)测试

@Test
public void SecondLevelCache(){
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
IUserMapper 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);
}

二级缓存整合Redis源码分析:

我们可以看到,上面我们Mapper.xml配置中cache标签<cache type="org.mybatis.caches.redis.RedisCache" />,所以我们找到RedisCache这个类,

RedisCache是在mybatis初始化(启动)时,调用其构造方法 public RedisCache(final String id){}创建的

从上面代码我们可以看到:通过parseConfiguration()去读取我们配置文件redis.properties得到RedisConfig对象(如果没有配置文件将使用RedisConfig中默认配置),然后构建了一个JedisPool去操作redis

什么时候把数据放到redis中和什么时候取的值呢,我们继续翻阅可以看到RedisCache中有putObject和getObject两个方法

我们可以看到,使用的数据结构是hash,将namespace.id作为key、CacheKey作为field、缓存值序列化作为value存入

总结:我们在实现mybatis缓存时,都要去实现Cache接口

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值