Java Mybatis架構(gòu)設(shè)計深入了解
架構(gòu)設(shè)計

我們可以把Mybatis的功能架構(gòu)分為三層:
1.API接口層:提供給外部使用的接口API,開發(fā)人員通過這些本地API來操縱數(shù)據(jù)庫。接口層一接收到調(diào)用請求就會調(diào)用數(shù)據(jù)處理層來完成具體的數(shù)據(jù)處理。
Mybatis和數(shù)據(jù)庫的交互有兩種方式:
- 使用傳統(tǒng)的Mybatis提供API
- 使用Mapper代理的方式
2.數(shù)據(jù)處理層:負責(zé)具體的SQL查找、SQL解析、SQL執(zhí)行和執(zhí)行結(jié)果映射處理等。他主要的目的是根據(jù)調(diào)用的請求完成一次數(shù)據(jù)庫操作。
3.基礎(chǔ)支撐層:負責(zé)最基礎(chǔ)的功能支撐,包括連接管理、事務(wù)管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來最為基礎(chǔ)組件。為上層的數(shù)據(jù)處理層提供最基礎(chǔ)的支撐。
Mybatis主要構(gòu)件
| 構(gòu)件 | 描述 |
|---|---|
| SqlSession | 作為Mybatis工作的主要頂層API,表示和數(shù)據(jù)庫交互的會話,完成必要數(shù)據(jù)庫增刪查改功能 |
| Executor | Mybatis執(zhí)行器,是Mybatis調(diào)度的核心,負責(zé)SQL語句的生成和查詢緩存的維護 |
| StatementHandler | 封裝了JDBC Statement操作,負責(zé)對JDBC statement的操作,如設(shè)置參數(shù)、將Statement結(jié)果集轉(zhuǎn)換為List集合 |
| ParameterHandler | 負責(zé)對用戶傳遞的參數(shù)轉(zhuǎn)換為JDBC Statement所需要的參數(shù) |
| ResultSetHandler | 負責(zé)將JDBC返回的ResultSet結(jié)果集對象轉(zhuǎn)換為List類型的集合 |
| TypeHandler | 負責(zé)java數(shù)據(jù)類型和jdbc數(shù)據(jù)類型之間的映射和轉(zhuǎn)換 |
| MappedStatement | MappedStatement維護了一條<select、 update 、 delete 、insert >節(jié)點的封裝 |
| SqlSource | 負責(zé)根據(jù)用戶傳遞的parameterObject,動態(tài)的生成SQL語句,將信息封裝到BoundSql對象中 |
| BoundSql | 表示動態(tài)生成的SQL語句以及相應(yīng)的參數(shù)信息 |

總體流程:
1.加載配置并初始化
配置來源于兩個地方,一個是配置文件(conf.xml,mapper*.xml),一個是java代碼中的注解,將配置文件內(nèi)容封裝到Configuration,將sql的配置信息加載成為一個mappedstatement對象,存儲在內(nèi)存中。
2. 接收調(diào)用請求
觸發(fā)條件:調(diào)用Mybatis提供的API
傳入?yún)?shù):為SQL的ID和傳入的參數(shù)
將請求傳遞給下層的請求處理層進行處理
3.處理操作請求
- 根據(jù)SQL的ID查找對應(yīng)的MappedStatement對象
- 根據(jù)傳入?yún)?shù)對象解析,得到最終要執(zhí)行的SQL和執(zhí)行傳入?yún)?shù)
- 獲取數(shù)據(jù)庫連接,將最終SQL語句和參數(shù)給到數(shù)據(jù)庫執(zhí)行,并得到執(zhí)行結(jié)果
- 根據(jù)MappedStatement對象中的結(jié)果映射配置對得到的執(zhí)行結(jié)果進行轉(zhuǎn)換處理,并得到最終的處理結(jié)果
- 釋放連接資源
4.返回處理結(jié)果
Mybatis緩存
Mybatis有一級緩存和二級緩存。Mybatis收到查詢請求后首先會查詢二級緩存,若二級緩存未命中,再去查詢一級緩存,一級緩存沒有,再查詢數(shù)據(jù)庫。
一級緩存
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緩存里查數(shù)據(jù),沒有就去查數(shù)據(jù)庫
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.注意,二級緩存要想生效,必須要調(diào)用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獲取到,這樣就會出現(xiàn)線程安全問題。除此之外,若不加以控制,多個事務(wù)共用一個緩存實例,會導(dǎo)致臟讀的存在。
那么mybatis是怎么解決臟讀的呢?借用了上面的tcm這個變量,也就是TransactionalCacheManager類來解決的。
TransactionalCacheManager類維護了Cache,TransactionalCache的關(guān)系,真正的數(shù)據(jù)還是交由TransactionalCache處理的。
結(jié)構(gòu)如圖:

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;
//在事務(wù)被提交前,所有從數(shù)據(jù)庫中查詢的結(jié)果將緩存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
//在事務(wù)被提交前,當(dāng)緩存未命中時,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的時候只是將數(shù)據(jù)庫的數(shù)據(jù)放入到了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();
}
//刷新未緩存的結(jié)果到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查詢的,所以這個二級緩存查詢數(shù)據(jù)庫后,緩存是沒有立刻生效的。只有當(dāng)執(zhí)行了sqlSession的commit或close方法后,它會調(diào)用到tcm的commit,在調(diào)用到transactionlCache的commit,刷新緩存到delegate了。
總結(jié):
二級緩存的設(shè)計上,大量運用了裝飾器模式,如SynchronizedCache、LoggingCache。
二級緩存實現(xiàn)了Sqlsession之間的緩存數(shù)據(jù)共享,屬于namespace級別
二級緩存的實現(xiàn)由CachingExecutor和一個事務(wù)型預(yù)緩存TransactionlCache完成。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
詳解Java中super的幾種用法并與this的區(qū)別
這篇文章主要介紹了Java中super的幾種用法并與this的區(qū)別,有需要的朋友可以參考一下2013-12-12
Idea中如何查看SpringSecurity各Filter信息
這篇文章主要介紹了Idea中如何查看SpringSecurity各Filter信息,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01
淺析Spring?Cloud?Gateway中的令牌桶限流算法
這篇文章主要為大家淺析了Spring?Cloud?Gateway中的令牌桶限流算法原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-02-02
Java 并發(fā)編程學(xué)習(xí)筆記之Synchronized簡介
雖然多線程編程極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個數(shù)據(jù)庫表中插入不重復(fù)的數(shù)據(jù),就可能會導(dǎo)致數(shù)據(jù)庫中插入了相同的數(shù)據(jù)。今天我們就來一起討論下線程安全問題,以及Java中提供了什么機制來解決線程安全問題。2016-05-05

