【mybatis源码系列1】 二级缓存
面試mybatis必問二級緩存,都知道有二級緩存,那mybatis是怎么實現的,本系列文章以mybatis-3.5.6版本為例。啰里啰唆寫了,想直接看緩存的跳到緩存部分。
一、mybatis流程
寫mybatis代碼的時候,逃不過這幾步
// 1.獲取配置文件 InputStream in =Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml"); //2.開啟SqlSession工廠? ? ? ? SqlSessionFactory build = new SqlSessionFactoryBuilder().build(in); //3.開啟SqlSession? ? ? ? SqlSession sqlSession = build.openSession(); //4.執行sql并返回? // 4.1 直接用 ? sqlSession.selectOne("com.faith.mybatiscode.mapper.ProductConfigMapper.selectOne", 1); // 4.2 用mapper代理ProductConfigMapper mapper = sqlSession.getMapper(ProductConfigMapper.class);List<ProductConfigVo> productConfigVos = mapper.selectProductConfigs(Arrays.asList(1));各種寫法大同小異,區別主要是兩點,第3步的時候是用單例sqlSession還是多實例的,第4步直接用sqlSession進行操作還是用mapper代理的方式。
1.結構
還記得我們寫jdbc代碼的時候怎么寫的嗎?大概這么幾步
String url = "jdbc:mysql://127.0.0.1:3306/Supermarket?characterEncoding=utf-8";String username = "root";String password = "123";try{// 1.加載驅動程序Class.forName("com.mysql.jdbc.Driver");// 2.獲得數據庫鏈接Connection conn = DriverManager.getConnection(url, username, password);// 3.通過數據庫的連接操作數據庫,實現增刪改查(使用Statement類)String name="張三";String sql="select * from product_config where id_no=?";PreparedStatement statement = conn.prepareStatement(sql);statement.setString(1, name);// 4.處理數據庫的返回結果(使用ResultSet類)ResultSet rs = statement.executeQuery();while (rs.next()) {System.out.println(rs.getString("UserName") + " " + rs.getString("Password"));}// 5.關閉資源rs.close();statement.close();conn.close(); }catch(SQLException | ClassNotFoundException se){System.out.println("數據庫連接失敗!");se.printStackTrace() ;}那mybatis是怎么幫我們做這些步驟的呢?先看這幾個對象:
1.SqlSessionFactory
public interface SqlSessionFactory {SqlSession openSession();SqlSession openSession(boolean autoCommit);SqlSession openSession(Connection connection);SqlSession openSession(TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType);SqlSession openSession(ExecutorType execType, boolean autoCommit);SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType, Connection connection);Configuration getConfiguration();}看SqlSessionFactory接口,做了兩件事,獲取SqlSession和Configuration,看他的默認實現類DefaultSqlSessionFactory持有一個Configuration對象,看這個Configuration里有什么
?要了命了,這么多變量,冷靜下來看下,基本上包括我們在Mybatis-config.xml里配置的所有信息,其他的不認識,碰到再說。看 new SqlSessionFactoryBuilder().build(in) 做了什么,主要是
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}所以就是解析配置文件為Configuration對象。
openSession
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}只是返回一個DefaultSession對象,注意這里openSession一次就new一個SqlSession,緩存分級就從這里區別開來了。從而我們知道每個SqlSession持有相同的Configuration對象,不同的Executor對象,即
?
sqlSession.selectOne @Overridepublic <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);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();}}從代碼我們可知,SqlSession是用到了Configuration對象組裝好MappedStatement,然后用executor執行得到結果,對比jdbc,似乎有這種感覺。
1.configuration在解析xml的時候就組裝好帶占位符的sql語句了。在這里取出來給executor用。
2.executor管理了參數的拼裝,即PreparedStatement的實例化和參數設置,獲取連接,執行,返回ResultSet和解析ResultSet到封裝的對象。
如果設這樣的話,緩存一定在executor里了。
緩存
1.在mybatis-config.xml里配置<cache/>標簽,表示開啟二級緩存,一級緩存是默認開啟的。看CachingExecutor里的代碼邏輯。
2.二級
@Overridepublic <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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578 and #116}return list;}}return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}先從緩存里取,取不到再執行查詢,這一級的cache是MappedStatement里的,這一步還沒到事務呢,所以是跨事務的。實際上代碼里的tcm就是一個TransactionalCacheManager,
真正的數據緩存是這個map的value,TransactionalCache,
主要關注delegate和entriesToAddOnCommit,delegate實際上和一級緩存一樣,是一個
?無論一級緩存還是二級緩存,最終都是存進這個cache的Map對象里。
而entriesToAddOnCommit這個map,看名字就知道是一組entries,調用SqlSession.commit()了之后才會刷到PerpetualCache的cache里,這里頭才是真正的緩存數據。其實SqlSession.close()也會刷。
3.一級
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 {list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);} finally {localCache.removeObject(key);}localCache.putObject(key, list);if (ms.getStatementType() == StatementType.CALLABLE) {localOutputParameterCache.putObject(key, parameter);}return list;}?這個localCache就是上邊提到的 PerpetualCache,所以只要是查詢都會緩存。
問題
1.一級緩存二級緩存都存到一個Map里,而且key一樣,所有如果沒有配置外部的二級緩存,內存里只存了一份緩存吧?
2.網上說二級緩存不要輕易開啟,容易導致內存撐爆,這是什么道理?就算開啟了二級緩存,內存里只有一份緩存數據,怎么會因為開啟二級緩存而導致內存占用變大呢?除非是一直SqlSession不commit(),entriesToAddOnCommit這個map把內存撐爆,或者是一次SqlSession查出來的數據太大,在commit之前就爆了(如果這中情況的話一級緩存也爆)。
3.就算配置了外部二級緩存,要想使用上二級緩存,第一次必須查數據庫,意味著一級緩存里也被存進去了。所以一級緩存該占用的內存還是占用啊,那二級緩存的意義在哪里?難道是為了多應用的架構,讓別的應用直接去外部的二級緩存里取,而不用查數據庫?
?
?
總結
以上是生活随笔為你收集整理的【mybatis源码系列1】 二级缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 配置双mysql数
- 下一篇: 在Linux系统下防火墙开放所需要访问的