zoukankan      html  css  js  c++  java
  • java-mybaits-014-数据库缓存设计【querycache、mybatis一级缓存、二级缓存】

    一、概述

      一般来说,可以在5个方面进行缓存的设计:

        1、最底层可以配置的是数据库自带的query cache,

        2、mybatis的一级缓存,默认情况下都处于开启状态,只能使用自带的PerpetualCache,无法配置第三方缓存

        3、mybatis的二级缓存,可以配置开关状态,默认使用自带的PerpetualCache,但功能比较弱,能够配置第三方缓存,

        4、service层的缓存配置,结合spring,可以灵活进行选择

        5、针对实际业务情况,直接缓存部分html页面,直接返回给客户端。

    1.1、一级缓存失效【结合spring后失效,service加上@Transactional后可以使用】

      在测试过程中,发现mybatis的一级缓存没有起作用,失效了。经过调研,发现是由于以下原因引起的:

        1.mybatis的一级缓存生效的范围是sqlsession,是为了在sqlsession没有关闭时,业务需要重复查询相同数据使用的。一旦sqlsession关闭,则由这个sqlsession缓存的数据将会被清空。

        2.spring对mybatis的sqlsession的使用是由template控制的,sqlSessionTemplate又被spring当作resource放在当前线程的上下文里(threadlocal),spring通过mybatis调用数据库的过程如下:

    1、我们需要访问数据
    2、spring检查到了这种需求,于是去申请一个mybatis的sqlsession(资源池),并将申请到的sqlsession与当前线程绑定,放入threadlocal里面
    3、sqlSessionTemplate从threadlocal获取到sqlsession,去执行查询
    4、查询结束,清空threadlocal中与当前线程绑定的sqlsession,释放资源
    5、我们又需要访问数据
    6、返回到步骤2

      通过以上步骤后发现,同一线程里面两次查询同一数据所使用的sqlsession是不相同的,所以,给人的印象就是结合spring后,mybatis的一级缓存失效了。

      而在spring中一般都是用sqlSessionTemplate,如下

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:configuration.xml" />
        <property name="mapperLocations">
            <list>
                <value>classpath*:com/hejb/sqlmap/*.xml</value>
            </list>
        </property>
    </bean>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg ref="sqlSessionFactory" />
    </bean>

    在SqlSessionTemplate中执行SQL的session都是通过sqlSessionProxy来,sqlSessionProxy的生成在构造函数中赋值,如下:

    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());

    sqlSessionProxy通过JDK的动态代理方法生成的一个代理类,主要逻辑在InvocationHandler对执行的方法进行了前后拦截,主要逻辑在invoke中,包好了每次执行对sqlsesstion的创建,commit,关闭

    代码如下:

    private class SqlSessionInterceptor implements InvocationHandler {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       // 每次执行前都创建一个新的sqlSession
       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的缓存了.

    对于开启了事务为什么可以用上呢, 跟入getSqlSession方法

    如下:

    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);
      // 首先从SqlSessionHolder里取出session
      SqlSession session = sessionHolder(executorType, holder);
      if (session != null) {
       return session;
      }
      if (LOGGER.isDebugEnabled()) {
       LOGGER.debug("Creating a new SqlSession");
      }
      session = sessionFactory.openSession(executorType);
      registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
      return session;
     }

    在里面维护了个SqlSessionHolder,关联了事务与session,如果存在则直接取出,否则则新建个session,所以在有事务的里,每个session都是同一个,故能用上缓存了

    发的

  • 相关阅读:
    专家视角 | 小荷的 Oracle Database 18c 新特性快速一瞥
    java.lang.ClassCastException: com.xx.User cannot be cast to com.xx.User
    上传单个文件
    极速体验:Oracle 18c 下载和Scalable Sequence新特性
    开工大吉:Oracle 18c已经发布及新特性介绍
    CentOS7编译安装NodeJS
    .NET 同步与异步之锁(ReaderWriterLockSlim)(八)
    .NET 同步与异步之锁(ReaderWriterLockSlim)(八)
    .NET 同步与异步之锁(ReaderWriterLockSlim)(八)
    .NET 同步与异步之锁(ReaderWriterLockSlim)(八)
  • 原文地址:https://www.cnblogs.com/bjlhx/p/11398581.html
Copyright © 2011-2022 走看看