亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Mybatis一級緩存和結(jié)合Spring Framework后失效的源碼探究

 更新時間:2021年04月28日 09:31:55   作者:碼猿手  
這篇文章主要介紹了Mybatis一級緩存和結(jié)合Spring Framework后失效的源碼探究,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

  1.在下面的案例中,執(zhí)行兩次查詢控制臺只會輸出一次 SQL 查詢:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/hrh/mapper/PersonMapper.xml"/>
    </mappers>
</configuration>
PersonMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.hrh.mapper.PersonMapper">
    <resultMap id="BaseResultMap" type="com.hrh.bean.Person">
        <id column="id" property="id" jdbcType="BIGINT"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="age" property="age" jdbcType="BIGINT"/>
    </resultMap>
    <sql id="Base_Column_List">
    id, name, age
    </sql>
    <select id="list" resultType="com.hrh.bean.Person">
        select
        <include refid="Base_Column_List"/>
        from tab_person
    </select>
</mapper>
public interface PersonMapper {
     List<Person> list();
}
String resource = "mybatis-config2.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();//開啟會話
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        mapper.list();
        mapper.list();

  之所以會出現(xiàn)這種情況,是因為 Mybatis 存在一級緩存導致的,下面 debug 探究下內(nèi)部流程:

  (1)mapper.list() 會進入 MapperProxy#invoke():參數(shù)proxy是一個代理對象(每個 Mapper 接口都會被轉(zhuǎn)換成一個代理對象),里面包含會話 sqlSession、接口信息、方法信息;method是目標方法(當前執(zhí)行的方法),它里面包含了所屬的哪個類(接口)、方法名、返回類型(List、Map、void 或其他)、參數(shù)類型等;args是參數(shù);

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //從方法緩存methodCache中獲取到方法的信息:比如方法名、類型(select、update等)、返回類型
    //如果獲取中沒有MapperMethod,則創(chuàng)建一個并放入methodCache中
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //執(zhí)行查詢SQL并返回結(jié)果
    return mapperMethod.execute(sqlSession, args);
  }

  

  cacheMapperMethod:MapperMethod 包含方法名、類型(select、update等)、返回類型等信息

private MapperMethod cachedMapperMethod(Method method) {
    //緩存中獲取
    MapperMethod mapperMethod = methodCache.get(method);
    //沒有則創(chuàng)建一個對象并放入緩存中供下次方便取用
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

 ?。?)MapperMethod#execute()根據(jù) SQL 類型進入不同的查詢方法

public Object execute(SqlSession sqlSession, Object[] args) {
    //返回結(jié)果
    Object result;
    //判斷語句類型
    switch (command.getType()) {
      case INSERT: {//插入語句
      Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {//更新語句
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {//刪除語句
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT://查詢語句
        //返回空的查詢
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
          //返回List的查詢
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
          //返回Map的查詢
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
          //返回游標的查詢
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  (3)上面的案例是 select 語句,返回結(jié)果是List集合,所以進入 MapperMethod#executeForMany():

private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    //獲取參數(shù)
    Object param = method.convertArgsToSqlCommandParam(args);
    //是否有分頁查詢
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    //如果list中的泛型跟結(jié)果類型不一致,進行轉(zhuǎn)換
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

 ?。?)selectList執(zhí)行了DefaultSqlSession#selectList():

 public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //SQL執(zhí)行的信息:resource(xxMapper.xml)、id、sql、返回類型等
      MappedStatement ms = configuration.getMappedStatement(statement);
      //執(zhí)行查詢
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 ?。?)接下來調(diào)用緩存執(zhí)行器的方法:CachingExecutor#query()

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //獲取到執(zhí)行SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    //將SQL包裝成一個緩存對對象,該對象和結(jié)果集組成鍵值對存儲到緩存中,方便下次直接從緩存中拿而不需要再次查詢
    //createCacheKey:調(diào)用BaseExecutor#createCacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //獲取緩存
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //沒有緩存連接查詢
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

 ?。?)接下來執(zhí)行 BaseExecutor#query():從下面可以看到將結(jié)果緩存到localCache 中了

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //如果不是嵌套查詢(默認為0),且 <select> 的 flushCache=true 時清空緩存
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
     //嵌套查詢層數(shù)+1
      queryStack++;
      //從localCache緩存中獲取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //連接查詢
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    //下面是延遲加載邏輯
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //緩存中添加占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //連接查詢獲取到數(shù)據(jù)結(jié)果    
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      //刪除占位符
      localCache.removeObject(key);
    }
    //將結(jié)果緩存起來
    localCache.putObject(key, list);
    //處理存儲過程
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  2.但當 Spring Framework + Mybatis 時,情況就不一樣了,每次查詢都會連接數(shù)據(jù)庫查詢,控制臺都會打印 SQL 出來,如下案例:

@Service
public class PersonService  {
    @Autowired
    PersonMapper personMapper;

    public List<Person> getList() {
        personMapper.list();
        personMapper.list();
        return personMapper.list();
    }
}
@Configuration
@ComponentScan("com.hrh")
@MapperScan("com.hrh.mapper")
public class MyBatisConfig {
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setMapperLocations(resolveMapperLocations());
        return factoryBean;
    }

    public Resource[] resolveMapperLocations() {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        List<String> mapperLocations = new ArrayList<>();
        mapperLocations.add("classpath*:com/hrh/mapper/*Mapper*.xml");
        List<Resource> resources = new ArrayList();
        if (mapperLocations != null) {
            for (String mapperLocation : mapperLocations) {
                try {
                    Resource[] mappers = resourceResolver.getResources(mapperLocation);
                    resources.addAll(Arrays.asList(mappers));
                } catch (IOException e) {
                    // ignore
                }
            }
        }
        return resources.toArray(new Resource[resources.size()]);
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUsername("xxx");
        driverManagerDataSource.setPassword("xxx");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
        return driverManagerDataSource;
    }
}
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
        PersonService bean = context.getBean(PersonService.class);
        bean.getList();

  下面debug進入的步驟跟上面的(1)、(2)、(3)是一致的,但第四步卻是進入SqlSessionTemplate#selectList()中【SqlSessionTemplate是mybatis-spring-xx.jar的,上文的DefaultSqlSession是屬于mybatis-xx.jar的】:

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  接下來的selectList() 會被方法攔截:method.invoke() 會執(zhí)行到 DefaultSqlSession#selectList(),重新回到上文的第四步并且繼續(xù)下去,也就是在上文的(1)~(6)中插入了前后文,在其中做了關(guān)閉會話的操作;

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 {
        //執(zhí)行方法查詢
        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);//在關(guān)閉會話前提交和回滾
        }
        return result;
        
      } catch (Throwable t) {//有異常拋出異常并結(jié)束會話
        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 {
        //關(guān)閉會話
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

總結(jié):

  Mybatis 的一級緩存是會話級別的緩存(單線程的,特別雞肋),Mybatis 每創(chuàng)建一個 SqlSession 會話對象,就表示打開一次數(shù)據(jù)庫會話,在一次會話中,應(yīng)用程序很可能在短時間內(nèi)反復執(zhí)行相同的查詢語句,如果不對數(shù)據(jù)進行緩存,則每查詢一次就要執(zhí)行一次數(shù)據(jù)庫查詢,這就造成數(shù)據(jù)庫資源的浪費。又因為通過 SqlSession 執(zhí)行的操作,實際上由 Executor 來完成數(shù)據(jù)庫操作的,所以在 Executor 中會建立一個簡單的緩存,即一級緩存;將每次的查詢結(jié)果緩存起來,再次執(zhí)行查詢的時候,會先查詢一級緩存(默認開啟的),如果命中,則直接返回,否則再去查詢數(shù)據(jù)庫并放入緩存中。

  一級緩存的生命周期與 SqlSession 的生命周期相同,因此當 Mybatis 和Spring Framework 的集成包中擴展了一個 SqlSessionTemplate 類(它是一個代理類,增強了查詢方法),所有的查詢經(jīng)過 SqlSessionTemplate 代理攔截后再進入到 DefaultSqlSession#selectList() 中,結(jié)束查詢后把會話SqlSession 關(guān)了,所以導致了緩存失效。

  那為什么要這么操作呢?

  原始的 Mybatis 有暴露 SqlSession 接口,因此有 close 方法暴露出來供你選擇使用,你可以選擇關(guān)與不關(guān),但在Mybatis 和Spring Framework 的集成包中,SqlSession 是交給了Spring Framework 管理的,沒有暴露出來,為了穩(wěn)妥決定,直接給你關(guān)了。

到此這篇關(guān)于Mybatis一級緩存和結(jié)合Spring Framework后失效的源碼探究的文章就介紹到這了,更多相關(guān)Mybatis一級緩存Spring Framework失效內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多線程編程實戰(zhàn)之模擬大量數(shù)據(jù)同步

    Java多線程編程實戰(zhàn)之模擬大量數(shù)據(jù)同步

    這篇文章主要介紹了Java多線程編程實戰(zhàn)之模擬大量數(shù)據(jù)同步,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-02-02
  • 如何使用jmeter錄制瀏覽器Https請求過程圖解

    如何使用jmeter錄制瀏覽器Https請求過程圖解

    這篇文章主要介紹了基于jmeter錄制瀏覽器Https請求過程圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-04-04
  • 全面解析Spring Security 過濾器鏈的機制和特性

    全面解析Spring Security 過濾器鏈的機制和特性

    這篇文章主要介紹了Spring Security 過濾器鏈的機制和特性,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • Java基于接口實現(xiàn)模擬動物聲音代碼實例

    Java基于接口實現(xiàn)模擬動物聲音代碼實例

    這篇文章主要介紹了Java基于接口實現(xiàn)模擬動物聲音代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • Spring循環(huán)依賴正確性及Bean注入的順序關(guān)系詳解

    Spring循環(huán)依賴正確性及Bean注入的順序關(guān)系詳解

    這篇文章主要給大家介紹了關(guān)于Spring循環(huán)依賴的正確性,以及Bean注入的順序關(guān)系的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。
    2018-01-01
  • Java中使用異或語句實現(xiàn)兩個變量的互換

    Java中使用異或語句實現(xiàn)兩個變量的互換

    這篇文章主要介紹了Java中使用異或語句實現(xiàn)兩個變量的互換,本文直接給出代碼實例以及運行結(jié)果,需要的朋友可以參考下
    2015-06-06
  • Java靜態(tài)和非靜態(tài)成員變量初始化過程解析

    Java靜態(tài)和非靜態(tài)成員變量初始化過程解析

    這篇文章主要介紹了Java靜態(tài)和非靜態(tài)成員變量初始化過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • java如何實現(xiàn)多線程的順序執(zhí)行

    java如何實現(xiàn)多線程的順序執(zhí)行

    多線程是java的一種重要技術(shù),但是多線程的運行是沒有絕對的順序的,那么java如何實現(xiàn)多線程的順序執(zhí)行,下面就一起來了解一下
    2021-05-05
  • MyBatis中#號與美元符號的區(qū)別

    MyBatis中#號與美元符號的區(qū)別

    #{變量名}可以進行預(yù)編譯、類型匹配等操作,#{變量名}會轉(zhuǎn)化為jdbc的類型。很多朋友不清楚在mybatis中#號與美元符號的不同,接下來通過本文給大家介紹兩者的區(qū)別,感興趣的朋友參考下吧
    2017-01-01
  • MyBatis-Plus模糊查詢特殊字符串轉(zhuǎn)義的實現(xiàn)

    MyBatis-Plus模糊查詢特殊字符串轉(zhuǎn)義的實現(xiàn)

    使用MyBatis中的模糊查詢時,當查詢關(guān)鍵字中包括有_、\、%時,查詢關(guān)鍵字失效,本文主要介紹了MyBatis-Plus模糊查詢特殊字符串轉(zhuǎn)義的實現(xiàn),感興趣的可以了解一下
    2024-06-06

最新評論