一级缓存的要求是:

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中多次调用,第二次调用可以从缓存中读取

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐