一级缓存
Session级别的缓存,默认是开启的。为了减轻数据库的压力,每次查询的结果都会放到缓存中,如果后续有同样的查询,那么可以直接去缓存中取数据。
User user = userMapper.findById(1);
System.out.println(user);
User user2 = userMapper.findById(1);
System.out.println(user2);
查看debug日志记录可以看出缓存的踪影:
DEBUG 2018-06-30 11:26:42,621 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select * from user where id=?;
DEBUG 2018-06-30 11:26:42,674 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: 1(Integer)
DEBUG 2018-06-30 11:26:42,727 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1
这里进行了两次查询,从日志上来看 只查询了一次数据库。也就是说第二次查询使用了缓存。这样也提高里查询的速度。
但是依然有一个很明显的问题:
我们知道,当执行插入以及更新操作时,只有会话提交之后数据才持久化到数据库,否则数据会存在于缓存当中,使用时会产生脏数据。如下:
User user = userMapper.findById(1);
System.out.println(user);//假如此时user:name wojiushiwo age 20
userMapper.update(1,”test”);//name=”test”==>更新后 name 为test 此时并没有提交事务
User user2 = userMapper.findById(1);//查询缓存中id=1的数据
System.out.println(user2);//输出 name:test age:20 但这个数据并不在数据库 因此是脏数据
因此如果两次查询之间 有更新操作,由于更新数据未写入到数据库,只是存在于缓存中。因为缓存中存在要查找的数据,所以第二次查询依然去使用缓存,这也就导致了两次查询的name字段数据不一致。也就产生了脏数据。
因此为了避免这种情况的发生,更新操作后要及时清理缓存,即session.commit();
二级缓存
二级缓存默认是关闭的,不建议使用,一般都以第三方缓存工具如redis代替
二级缓存是基于namespace的,也即是基于某个mapper的。只要是对该mapper的查询都走二级缓存
二级缓存的开启方式:
- Java config方式
factory.getConfiguration().isCacheEnabled(); - xml方式:
<settings>
<setting name="cacheEnabled" value="true" />
<!-- 启用延迟加载 -->
<!-- <setting name="lazyLoadingEnabled" value="true" /> -->
<!-- 关闭层级加载 -->
<!-- <setting name="aggressiveLazyLoading" value="false"/> -->
</settings>
在具体的mapper文件上 声明<cache/>标签
注意:一级缓存和二级缓存是可以同时存在的
验证二级缓存:
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getInstance();
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> list = userMapper.findAll();
System.out.println(list);
session.commit();
session.close();
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
List<User> list2 = userMapper2.findAll();
System.out.println(list2);
session2.commit();
session2.close();
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select * from user;
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 2
[User [id=1, name=love---, age=25, sex=null, name1=null, name2=null, course=null], User [id=3, name=test, age=22, sex=null, name1=null, name2=null, course=null]]
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@39c0f4a]
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@39c0f4a]
org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 60559178 to pool.
org.apache.ibatis.cache.decorators.LoggingCache: Cache Hit Ratio [com.wojiushiwo.mybatis_demo.dao.UserMapper]: 0.5
[User [id=1, name=love---, age=25, sex=null, name1=null, name2=null, course=null], User [id=3, name=test, age=22, sex=null, name1=null, name2=null, course=null]]
可以发现 两次查询实际上只发出一条sql语句,第二次走的二级缓存,且缓存命中概率0.5
如果上面的第一个session.commit();session.close();注释掉(第二个在程序的末尾 注释与否不影响了)
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select * from user;
DEBUG 2018-06-30 12:46:27,344 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 2
[User [id=1, name=love---, age=25, sex=null, name1=null, name2=null, course=null], User [id=3, name=test, age=22, sex=null, name1=null, name2=null, course=null]]
org.apache.ibatis.cache.decorators.LoggingCache: Cache Hit Ratio [com.wojiushiwo.mybatis_demo.dao.UserMapper]: 0.0
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 832947102.
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31a5c39e]
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select * from user;
DEBUG 2018-06-30 12:46:27,407 org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 2
[User [id=1, name=love---, age=25, sex=null, name1=null, name2=null, course=null], User [id=3, name=test, age=22, sex=null, name1=null, name2=null, course=null]]
会发现并没有走二级缓存。
个人理解如下:
即使开启了二级缓存,不同会话之间的缓存数据也不是能够相互访问的。必须一级缓存关闭了,一级缓存中的数据才写入二级缓存
两个会话查询的数据均存放在一级缓存中,而会话尚未关闭,那么并没有数据写入二级缓存,因此会再次执行SQL 去查询数据库
以一个例子分析下二级缓存存在的问题和使用时需要注意的地方:

UserMapper中查询User(包含course信息)的数据。
CourseMapper中更新course数据
//查询user信息
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getInstance();
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> list = userMapper.findAll();
System.out.println(list);
session.commit();
session.close();
//更新course信息
SqlSession session3 = sqlSessionFactory.openSession();
CourseMapper courseMapper = session3.getMapper(CourseMapper.class);
courseMapper.update(1, "math1");
session3.commit();
//查询user信息
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
List<User> list2 = userMapper2.findAll();
System.out.println(list2);
session2.commit();
session2.close();
在UserMapper查询的过程中 更新CourseMapper中的数据。因为二级缓存的原因。第二次UserMapper查询时走的是二级缓存。而实际上数据库中course的数据已经发生了变化,因此就导致了第二次查询得到了脏数据。
这种脏数据来源方式与一级缓存很类似。
在mapper查询操作的过程中,执行同一mapper中的更新方法,会使二级缓存被清空。
//查询user信息
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getInstance();
SqlSession session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
List<User> list = userMapper.findAll();
System.out.println(list);
session.commit();
session.close();
//更新user信息
SqlSession session3 = sqlSessionFactory.openSession();
UserMapper userMapper3 = session3.getMapper(UserMapper.class);
userMapper3.update(1, "abcd1111");
session3.commit();
//查询user信息
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
List<User> list2 = userMapper2.findAll();
System.out.println(list2);
session2.commit();
session2.close();
这里缓存被清空,第二次查询的是数据库
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1794d431]
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select a.id as id,a.name as name,a.age as age, b.object as object,b.score as score,b.id as cid from user a,course b where a.id=b.nameId;
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1
[User [id=1, name=abcd, age=25, sex=null, name1=null, name2=null, course=Course [object=math1, score=90, id=1, nameId=null]]]
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1794d431]
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1794d431]
org.apache.ibatis.datasource.pooled.PooledDataSource: Returned connection 395629617 to pool.
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
org.apache.ibatis.datasource.pooled.PooledDataSource: Checked out connection 395629617 from pool.
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1794d431]
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: update user set name=? where id=?;
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters: abcd1111(String), 1(Integer)
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Updates: 1
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1794d431]
org.apache.ibatis.cache.decorators.LoggingCache: Cache Hit Ratio [com.wojiushiwo.mybatis_demo.dao.UserMapper]: 0.0
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Opening JDBC Connection
org.apache.ibatis.datasource.pooled.PooledDataSource: Created connection 540585569.
org.apache.ibatis.transaction.jdbc.JdbcTransaction: Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@2038ae61]
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Preparing: select a.id as id,a.name as name,a.age as age, b.object as object,b.score as score,b.id as cid from user a,course b where a.id=b.nameId;
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: ==> Parameters:
org.apache.ibatis.logging.jdbc.BaseJdbcLogger: <== Total: 1
[User [id=1, name=abcd1111, age=25, sex=null, name1=null, name2=null, course=Course [object=math1, score=90, id=1, nameId=null]]]
至此缓存部分分析完毕。
以上,如有问题请大家提出,谢谢~~~~~~
网友评论