mybatis缓存机制(查询缓存)主要是减轻数据压力,提供数据库性能。
mybatis提供一级缓存和二级缓存
(1)一级缓存是sqlsession对象级别,在操作数据库是需要构造sqlsession对象,此对象中有个HashMap用于存储缓存数据,多个sqlsession间互不影响。
mybatis默认支持一级缓存不需要配置。实现原理:
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。
得到用户信息,将用户信息存储到一级缓存中。
如果sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
(1.1)一级缓存的测试:
mybatis默认支持一级缓存,不需要在配置文件去配置。
//测试一级缓存
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper.findUserById(1);
System.out.println(user1.getUsername());
//第二次发起请求,查询id为1的用户
User user2 = userMapper.findUserById(1);
System.out.println(user2.getUsername());
sqlSession.close();
测试结果和输出日志:
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 19140890.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@124111a] //关闭自动提交
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
张三
张三 //没有sql语句执行,说明是从一级缓存中读取
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@124111a]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@124111a]
DEBUG [main] - Returned connection 19140890 to pool.
可以看到第二次取user2的时候没有发出sql查询,说明调用了一级缓存
接下来测试看看在中间执行了增删改查的时候会不会清空一级缓存
//测试一级缓存
@Test
public void testCache1() throws Exception{
SqlSession sqlSession = sqlSessionFactory.openSession();//创建代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper.findUserById(1);
System.out.println(user1.getUsername());
/*如果sqlSession去执行commit操作(执行插入、更新、删除),
* 清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
*/
//更新user1的信息
user1.setUsername("测试用户22");
userMapper.updateUser(user1);
//执行commit操作去清空缓存
sqlSession.commit();
//第二次发起请求,查询id为1的用户
User user2 = userMapper.findUserById(1);
System.out.println(user2.getUsername());
sqlSession.close();
}
测试结果和输出日志:
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 6778431.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
张三
DEBUG [main] - ==> Preparing: update user set username=?,birthday=?,sex=?,address=? where id=?
DEBUG [main] - ==> Parameters: 测试用户2(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)
DEBUG [main] - <== Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.Connection@676e3f] //提交后清空一级缓存,所以下面有执行sql语句查询
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
测试用户2
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@676e3f]
DEBUG [main] - Returned connection 6778431 to pool.
发现第二次取数据时发出了sql请求,说明执行增删改查的时候会清空一级缓存
(2)二级缓存
正式开发,是将mybatis和spring进行整合开发,事务控制在service中。
一个service方法中包括 很多mapper方法调用。
service{
//开始执行时,开启事务,创建SqlSession对象
//第一次调用mapper的方法findUserById(1)
//第二次调用mapper的方法findUserById(1),从一级缓存中取数据
//方法结束,sqlSession关闭,一级缓存清除
}
如果是执行两次service调用查询相同 的用户信息,不走一级缓存,因为service方法结束,sqlSession就关闭,一级缓存就清空。这个时候就需要使用二级缓存
(2)二级缓存是mapper级别的,多个sqlsession间共享二级缓存。
(2.1)二级缓存需要手动配置开启,首先在全局配置文件mybatis-config.xml中开启二级缓存
<setting name="cacheEnabled"value="true"/>
(2.2)在mapper.xml中开启二缓存,mapper.xml下的sql执行完成会存储到它的缓存区,如:
<cache></cache>
(2.3)让使用二级缓存的POJO类实现Serializable接口
测试二级缓存
//测试二级缓存
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1.getUsername());
sqlSession1.close();//不关闭SqlSession无法写进二级缓存区域中
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象
//第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user2.getUsername());
sqlSession2.close();
}
测试结果和输出日志:
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 26255574.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
张三
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@190a0d6]
DEBUG [main] - Returned connection 26255574 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
张三
我们可以发现,日志输出中有一句我们之前没有见过,是:
Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
这句的意思是缓存命中率为0.0,说明第一次是在缓存中找,可是没找到
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
这句的意思是缓存命中率为0.5,说明第一次是在缓存中找,可是没找到,第二次找到了。
这个测试证明了二级缓存的存在
下面证明第三方sqlSession执行增删改会清空缓存的事实:
//测试二级缓存
@Test
public void testCache2() throws Exception{
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);//创建代理对象
//下边查询使用一个SqlSession
//第一次发起请求,查询id为1的用户
User user1 = userMapper1.findUserById(1);
System.out.println(user1.getUsername());
//不关闭SqlSession无法写进二级缓存区域中
sqlSession1.close();
UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);//创建代理对象
User user=userMapper3.findUserById(1);
user.setUsername("张明明");
userMapper3.updateUser(user);
sqlSession3.commit();
sqlSession3.close();//执行提交,并关闭SqlSession,清空UserMapper二级缓存
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);//创建代理对象
//第二次发起请求,查询id为1的用户
User user2 = userMapper2.findUserById(1);
System.out.println(user2.getUsername());
sqlSession2.close();
}
输出结果和日志信息:
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 189219.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
张三
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.5
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 189219 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==> Preparing: update user set username=?,birthday=?,sex=?,address=? where id=?
DEBUG [main] - ==> Parameters: 张明明(String), 2015-06-07(Date), 男(String), 河南焦作(String), 1(Integer)
DEBUG [main] - <== Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.
DEBUG [main] - Cache Hit Ratio [cn.edu.hpu.mybatis.mapper.UserMapper]: 0.3333333333333333
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 189219 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - ==> Preparing: SELECT * FROM USER WHERE id=?
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <== Total: 1
张明明
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.Connection@2e323]
DEBUG [main] - Returned connection 189219 to pool.
发现第二次再去查1号用户的时候,又再一次查询了数据库,查到了最新的数据"张明明"。
二级缓存远远没有那么简单,他还有一些配置的参数。
根据以上,想要使用二级缓存时需要想好两个问题:
1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。
使用二级缓存带来了数据的实时性问题,因为容易发生脏读。
注意:开启缓存的弊端是数据没有实时性,当数据库中的数据一旦修改,查询的数据还是缓存中的数据没有实时性,对于某些需要实时性显示数据的接口我们可以设置useCache="false",设置该属性后,该接口每次查询出来都是去执行sql查询出实时性数据。
设置useCache=false可以禁用当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
总结:针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存。
②.清空缓存
<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
总结:一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。
注意:
(1)当为select语句时:
flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
useCache默认为true,表示会将本条语句的结果进行二级缓存。
(2)当为insert、update、delete语句时:
flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。
useCache属性在该情况下没有。
当为select语句的时候,如果没有去配置flushCache、useCache,那么默认是启用缓存的,所以,如果有必要,那么就需要人工修改配置
3.对应的pojo实现序列化(implements Serializable)