一级缓存 先声明一个测试类FirstCacheTest,然后我们就可以在里面写测试方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Before;import org.junit.Test;public class FirstCacheTest { private SqlSession sqlSession; @Before public void init () { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder (); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(UserMapper.class.getResourceAsStream("/mybatis-config.xml" )); sqlSession = sqlSessionFactory.openSession(); } }
一级缓存的命中场景 一张图了解下一级缓存的命中场景
运行时参数相关 SQL和参数必须相同 1 2 3 4 5 6 7 8 @Test public void test1 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = userMapper.selectUserById(2L ); System.out.println(userPO == userPO1); }
必须是相同的statementID 第一个查询的staementID是: org.example.mybatis_reader.mybatis.selectUserById
第二个查询的staementID是: org.example.mybatis_reader.mybatis.selectUserById1
1 2 3 4 5 6 7 8 @Test public void test2 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = userMapper.selectUserById1(1L ); System.out.println(userPO == userPO1); }
必须是同一个SqlSession 1 2 3 4 5 6 7 8 @Test public void test3 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = sqlSession.selectOne("org.example.mybatis_reader.mybatis.UserMapper.selectUserById" , 1L ); System.out.println(userPO == userPO1); }
RowBounds返回行范围必须相同 分页信息必须相同
1 2 3 4 5 6 7 8 9 10 @Test public void test4 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); List<UserPO> userPO1 = sqlSession.selectList("org.example.mybatis_reader.mybatis.UserMapper.selectUserById" , 1L , new RowBounds (0 ,5 )); System.out.println(userPO == userPO1.get(0 )); }
操作与配置相关 未手动清空缓存 1 2 3 4 5 6 7 8 9 10 @Test public void test5 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); sqlSession.clearCache(); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); }
未调用flushCache=true的查询 即未设置flushCache = Options.FlushCachePolicy.TRUE
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void test6 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); userMapper.selectUserById1(1L ); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); } @Select("SELECT * FROM T_USER WHERE id = #{id}") @Options(flushCache = Options.FlushCachePolicy.TRUE) UserPO selectUserById1 (Long id) ;
未执行update操作 1 2 3 4 5 6 7 8 9 10 @Test public void test7 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); userMapper.updateUserNameById1(1L , "张三改名" ); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); }
缓存作用域不是STATEMENT 默认的缓存作用域是SESSION,如果要修改就在mybatis-config.xml中的中添加一个配置
1 2 3 <settings > <setting name ="localCacheScope" value ="STATEMENT" /> </settings >
1 2 3 4 5 6 7 8 @Test public void test8 () { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); }
一级缓存源码流程
一级缓存特性
与会话相关
与参数条件相关
提交、修改会清空缓存
一级缓存的失效场景 在Spring的体系下,Mybatis的一级缓存是失效的。根本原因是Spring代理的Mapper类在发起调用时是新构建一个会话的,而一级缓存恰好是会话级别的,不同会话级别的缓存肯定是不通用的,这也是为什么一级缓存会失效;在我们添加了事务后,所有的请求都在一个会话下,一级缓存也就再次生效了。我们可以验证一下。
我们需要将程序改造一下,首先在resources目录下新建spring.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" > <!-- DataSource configuration --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" > <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/blog_db" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <!-- SqlSessionFactory configuration --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean" > <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> </bean> <!-- MapperScannerConfigurer configuration --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name="basePackage" value="org.example.mybatis_reader.mybatis" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <!-- TransactionManager configuration --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="dataSource" /> </bean> </beans>
然后执行下测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test public void test9 () { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("spring.xml" ); UserMapper userMapper = context.getBean(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); } @Test public void test10 () { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext ("spring.xml" ); DataSourceTransactionManager trxManager = (DataSourceTransactionManager) context.getBean("transactionManager" ); trxManager.getTransaction(new DefaultTransactionAttribute ()); UserMapper userMapper = context.getBean(UserMapper.class); UserPO userPO = userMapper.selectUserById(1L ); UserPO userPO1 = userMapper.selectUserById(1L ); System.out.println(userPO == userPO1); }
那么问题来了,同样是用UserMapper发起的调用,结果为什么不同呢?关键就在于UserMapper的构造上。在前面的例子中,我们使用的是UserMapper本身,但是在test9()和test10()中,Spring通过代理的方式对UserMapper进行了增强,所以它的骨骼是这样的:
现在可以看下为什么test09()中一级缓存会失效,但是在test10()中会生效了。
在会话拦截器SqlSessionInterceptor构建SqlSession时,是使用的SqlSessionUtils工具类的,看下源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this .executorType, SqlSessionTemplate.this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this .sqlSessionFactory)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { } finally { if (sqlSession != null ) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } } }
看下getSqlSession方法的实现,发现它是通过sessionHolder方法去构建SqlSession,然后判断sessionHolder方法的返回值是否是null,不是的话就直接返回这个sqlSession。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static SqlSession getSqlSession (SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null ) { return session; } LOGGER.debug(() -> "Creating a new SqlSession" ); session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
重点就在sessionHolder()方法中,这里分为两种情况
如果holder不为NULL并且holder的事务标识为TRUE,就从holder中获取SqlSession;否则返回NULL值。这也是为什么开启了事务的时候走了一级缓存的原因。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 private static SqlSession sessionHolder (ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null ; if (holder != null && holder.isSynchronizedWithTransaction()) { if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException ( "Cannot change the ExecutorType when there is an existing transaction" ); } holder.requested(); LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction" ); session = holder.getSqlSession(); } return session; }