架構設計
我們可以把Mybatis的功能架構分為三層:
1.API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
Mybatis和數據庫的交互有兩種方式:
- 使用傳統的Mybatis提供API
- 使用Mapper代理的方式
2.數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。他主要的目的是根據調用的請求完成一次數據庫操作。
3.基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來最為基礎組件。為上層的數據處理層提供最基礎的支撐。
Mybatis主要構件
構件 | 描述 |
---|---|
SqlSession | 作為Mybatis工作的主要頂層API,表示和數據庫交互的會話,完成必要數據庫增刪查改功能 |
Executor | Mybatis執行器,是Mybatis調度的核心,負責SQL語句的生成和查詢緩存的維護 |
StatementHandler | 封裝了JDBC Statement操作,負責對JDBC statement的操作,如設置參數、將Statement結果集轉換為List集合 |
ParameterHandler | 負責對用戶傳遞的參數轉換為JDBC Statement所需要的參數 |
ResultSetHandler | 負責將JDBC返回的ResultSet結果集對象轉換為List類型的集合 |
TypeHandler | 負責java數據類型和jdbc數據類型之間的映射和轉換 |
MappedStatement | MappedStatement維護了一條<select、 update 、 delete 、insert >節點的封裝 |
SqlSource | 負責根據用戶傳遞的parameterObject,動態的生成SQL語句,將信息封裝到BoundSql對象中 |
BoundSql | 表示動態生成的SQL語句以及相應的參數信息 |
總體流程:
1.加載配置并初始化
配置來源于兩個地方,一個是配置文件(conf.xml,mapper*.xml),一個是java代碼中的注解,將配置文件內容封裝到Configuration,將sql的配置信息加載成為一個mappedstatement對象,存儲在內存中。
2. 接收調用請求
觸發條件:調用Mybatis提供的API
傳入參數:為SQL的ID和傳入的參數
將請求傳遞給下層的請求處理層進行處理
3.處理操作請求
- 根據SQL的ID查找對應的MappedStatement對象
- 根據傳入參數對象解析,得到最終要執行的SQL和執行傳入參數
- 獲取數據庫連接,將最終SQL語句和參數給到數據庫執行,并得到執行結果
- 根據MappedStatement對象中的結果映射配置對得到的執行結果進行轉換處理,并得到最終的處理結果
- 釋放連接資源
4.返回處理結果
Mybatis緩存
Mybatis有一級緩存和二級緩存。Mybatis收到查詢請求后首先會查詢二級緩存,若二級緩存未命中,再去查詢一級緩存,一級緩存沒有,再查詢數據庫。
一級緩存
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."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { 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; }
這個localCache是BaseExecutor里面的一個屬性
public abstract class BaseExecutor implements Executor { protected PerpetualCache localCache;
PerpetualCache類
public class PerpetualCache implements Cache { private final String id; private Map<Object, Object> cache = new HashMap<Object, Object>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); }
二級緩存
啟用二級緩存步驟:
1.開啟cacheEnabled(默認打開)
<settings> <setting name="cacheEnabled" value="true"/> </settings>
2.需要在二級緩存的Mapper配置文件中加入
<cache></cache>
3.注意,二級緩存要想生效,必須要調用sqlSession.commit或close方法
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, parameterObject, 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); }
注意Cache cache = ms.getCache();,這個cache是從MappedStatement中獲取到的,由于MappedStatement存在全局配置中,可以多個CachingExecutor獲取到,這樣就會出現線程安全問題。除此之外,若不加以控制,多個事務共用一個緩存實例,會導致臟讀的存在。
那么mybatis是怎么解決臟讀的呢?借用了上面的tcm這個變量,也就是TransactionalCacheManager類來解決的。
TransactionalCacheManager類維護了Cache,TransactionalCache的關系,真正的數據還是交由TransactionalCache處理的。
結構如圖:
public class TransactionalCacheManager { private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); public void clear(Cache cache) { getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { getTransactionalCache(cache).putObject(key, value); } public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } }
接下來看一下TransactionalCache的代碼
public class TransactionalCache implements Cache { private static final Log log = LogFactory.getLog(TransactionalCache.class); // 真正的緩存對象 private final Cache delegate; private boolean clearOnCommit; //在事務被提交前,所有從數據庫中查詢的結果將緩存在此集合中 private final Map<Object, Object> entriesToAddOnCommit; //在事務被提交前,當緩存未命中時,CacheKey 將會被存儲在此集合中 private final Set<Object> entriesMissedInCache; public TransactionalCache(Cache delegate) { this.delegate = delegate; this.clearOnCommit = false; this.entriesToAddOnCommit = new HashMap<Object, Object>(); this.entriesMissedInCache = new HashSet<Object>(); } @Override public String getId() { return delegate.getId(); } @Override public int getSize() { return delegate.getSize(); } @Override public Object getObject(Object key) { // issue #116 //獲取緩存的時候從delegate里獲取的 Object object = delegate.getObject(key); if (object == null) { //緩存未命中,將key存入entriesMissedInCache. entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; } } @Override public ReadWriteLock getReadWriteLock() { return null; } @Override public void putObject(Object key, Object object) { //put的時候只是將數據庫的數據放入到了entriesToAddOnCommit entriesToAddOnCommit.put(key, object); } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { clearOnCommit = true; entriesToAddOnCommit.clear(); } public void commit() { if (clearOnCommit) { delegate.clear(); } //刷新未緩存的結果到delegate中去 flushPendingEntries(); reset(); } public void rollback() { unlockMissedEntries(); reset(); } private void reset() { clearOnCommit = false; entriesToAddOnCommit.clear(); entriesMissedInCache.clear(); } private void flushPendingEntries() { for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) { delegate.putObject(entry.getKey(), entry.getValue()); } for (Object entry : entriesMissedInCache) { if (!entriesToAddOnCommit.containsKey(entry)) { delegate.putObject(entry, null); } } } private void unlockMissedEntries() { for (Object entry : entriesMissedInCache) { try { delegate.removeObject(entry); } catch (Exception e) { log.warn("Unexpected exception while notifiying a rollback to the cache adapter." + "Consider upgrading your cache adapter to the latest version. Cause: " + e); } } } }
我們存儲二級緩存的時候是放入到TransactionalCache.entriesToAddOnCommit這個map中,但是每次查詢的時候是從delegate查詢的,所以這個二級緩存查詢數據庫后,緩存是沒有立刻生效的。只有當執行了sqlSession的commit或close方法后,它會調用到tcm的commit,在調用到transactionlCache的commit,刷新緩存到delegate了。
總結:
二級緩存的設計上,大量運用了裝飾器模式,如SynchronizedCache、LoggingCache。
二級緩存實現了Sqlsession之間的緩存數據共享,屬于namespace級別
二級緩存的實現由CachingExecutor和一個事務型預緩存TransactionlCache完成。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注服務器之家的更多內容!
原文鏈接:https://www.cnblogs.com/javammc/p/15497530.html