springboot+mybatis集成一级缓存不生效问题
一级缓存的要求是:1、必须是相同的SQL和参数2、必须是相同的会话3、必须是相同的namespace 即同一个mapper4、必须是相同的statement 即同一个mapper 接口中的同一个方法5、查询语句中间没有执行session.clearCache() 方法6、查询语句中间没有执行 insert update delete 方法(无论变动记录是否与缓存数据有无关系)但是,当springb
一级缓存的要求是:
1、必须是相同的SQL和参数
2、必须是相同的会话
3、必须是相同的namespace 即同一个mapper
4、必须是相同的statement 即同一个mapper 接口中的同一个方法
5、查询语句中间没有执行session.clearCache() 方法
6、查询语句中间没有执行 insert update delete 方法(无论变动记录是否与缓存数据有无关系)
但是,当springboot+mybatis集成时:
Mapper:
@Repository
public interface UserMapper {
public User Sel(int id);
}
namespace:
<mapper namespace="com.fen.dou.mapper.UserMapper">
UserService:
@Service
public class UserService {
@Autowired
UserMapper userMapper;
public User Sel(int id){
userMapper.Sel(id);
userMapper.Sel(id);
return userMapper.Sel(id);
}
}
@MapperScan("com.fen.dou.mapper") //扫描的mapper
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext ac = SpringApplication.run(DemoApplication.class, args);
UserService userService = ac.getBean(UserService.class);
userService.Sel(3);
}
}
当我们执行上述main方法的时候,根据上述要求,我们预期是只打印一条sql:
但是看执行结果,却打印出三条,说明一级缓存没有生效了,如下图所示:

1、必须是相同的SQL和参数 其sql都是select * from user where id = ? 参数都是3 满足
2、必须是相同的会话 只知道时同一个userMapper,暂时不知道是不是同一个sqlSession
3、必须是相同的namespace 即同一个mapper,都是com.fen.dou.mapper.UserMapper,满足
4、必须是相同的statement 即同一个mapper 接口中的同一个方法 都是UserMapper 中的Sel方法,满足
5、查询语句中间没有执行session.clearCache() 方法 满足
6、查询语句中间没有执行 insert update delete 方法(无论变动记录是否与缓存数据有无关系)满足
所以得出一个结论同一个userMapper不是共用一个sqlSession
SqlSessionUtils.getSqlSession是获取sqlSession的方法:
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;
}
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);这句主要是从resources 中根据key去取sqlSession
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
如果取不到就去Creating a new SqlSession
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)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
其实sqlSession用了动态代理,当去执行具体的方法的时候,都会经过这层代理,看finally中有个
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
又碰到了SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);这句,这句的主要是要在service中加 事务才有值
大致逻辑是:
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@Transactional
public User Sel(int id){
userMapper.Sel(id);
userMapper.Sel(id);
return userMapper.Sel(id);
}
}
如果在server中加了 @Transactional,则会把sqlSession暂存在threadLocal中,则当第二次执行相同的mapper ,sql,参数的时候就会取threadLocal中去取有没有,如果有直接去ThreadLocal中去取sqlSession,执行完后,sqlSession并不会关闭,只会释放


总结:springboot集成mybatis,如果不开启事务,则每一个请求,都会开启一个sqlSession,执行完成后,sqlSession就会close,则在并发的请求下,虽然mapper是单例,但是能保证线程安全,当用了事务之后,当执行完service方法后,sqlSeesion才会close,所以一个请求service中多次调用,第二次调用可以从缓存中读取
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)