思維導圖

文章已收錄Github精選,歡迎Star: https://github.com/yehongzhi/learningSummary
概述
Mybatis是一個比較主流的ORM架構,是以在日常工作中接觸得很多。我比較喜歡看優秀架構的源碼,因為能寫出這種架構的作者肯定有其獨特之處。如果能看懂源碼的一些巧妙構思,一定是受益匪淺的。
所謂萬事開頭難,看源碼也要找到切入的點。設計模式無疑是源碼分析一個很好的切入點,廢話不多說,那麼我們就開始吧。
工廠模式
工廠模式屬于建立型模式。工廠模式的作用是把建立對象的邏輯封裝起來,提供一個接口給外部建立對象,降低類與類之間的耦合。
在Mybatis中,用到工廠模式主要在DataSourceFactory。這是一個負責建立DataSource資料源的工廠。DataSourceFactory是一個接口,有不同的子類實作,根據不同的配置,生成不同的DataSourceFactory實作類。類圖如下:
接着我們看一下DataSourceFactory的源碼:
public interface DataSourceFactory {
void setProperties(Properties props);
DataSource getDataSource();
}
DataSourceFactory接口定義了兩個抽象方法,怎麼工作的呢,其實是跟dataSource标簽的屬性type有關。
<environment id="development">
<transactionManager type="JDBC"/>
<!-- type屬性是關鍵屬性 -->
<dataSource type="POOLED">
<property name="driver" value="${dataSource.driver}"/>
<property name="url" value="${dataSource.url}"/>
<property name="username" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
</dataSource>
</environment>
Mybatis内置的type有三種配置,分别是UNPOOLED,POOLED,JNDI。
UNPOOLED,這個資料源的實作隻是每次被請求時打開和關閉連接配接。
POOLED,這種資料源的使用“池“的思想,避免了建立新的連接配接執行個體時所必需的初始化和認證時間。
JNDI,這個資料源的實作是為了能在如 EJB 或應用伺服器這類容器中使用,容器可以集中或在外部配置資料源,然後放置一個 JNDI 上下文的引用。
接着看XMLConfigBuilder的dataSourceElement()方法。
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
//讀取配置檔案中dataSource标簽的屬性type
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
//根據type屬性的值,傳回不同的子類,使用接口DataSourceFactory接收,展現了面向接口程式設計的思想
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
//設定屬性值,比如資料庫的url,username,password等等
factory.setProperties(props);
//傳回factory
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
獲得DataSourceFactory之後,就通過getDataSource()方法擷取資料源,完事了。
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");
}
//這裡for循環主要是配置檔案可以配置多個資料源
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
//dsFactory調動getDataSource()方法,建立dataSource對象
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
Mybatis使用工廠模式來建立DataSourceFactory,可以做到通過配置去使用不同的DataSourceFactory建立DataSource,非常靈活。在Mybatis中使用到工廠模式還有很多地方,比如SqlSessionFactory,這裡就不再展開了,有興趣的可以自己探索一下。
單例模式
單例模式屬于建立型設計模式,這個類提供了一種通路其唯一的對象的方式,可以直接通路,不需要執行個體化該類的對象。保證在應用中隻有單一對象被建立。
Mybatis中用到單例模式的地方有很多,這裡舉個例子是ErrorContext類。這是一個用于記錄該線程的執行環境錯誤資訊,是以是線上程範圍内的單例。
public class ErrorContext {
//使用ThreadLocal儲存每個線程中的單例對象
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
//私有化構造器
private ErrorContext() {
}
//向外提供唯一的接口擷取單例對象
public static ErrorContext instance() {
//從LOCAL中取出context對象
ErrorContext context = LOCAL.get();
if (context == null) {
//如果為null,new一個
context = new ErrorContext();
//放入到LOCAL中儲存
LOCAL.set(context);
}
//如果不為null,直接傳回
return context;
}
}
建造者模式
建造者模式也是屬于建立型模式,主要是在建立一個複雜對象時使用,通過一步一步構造最終的對象,将一個複雜對象的建構與它的表示分離。
在Mybatis中,使用到建造者模式的地方展現在ParameterMapping類,這是用于參數映射的一個類。
public class ParameterMapping {
private Configuration configuration;
private String property;
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;
//私有化構造器
private ParameterMapping() {
}
//通過内部類Builder建立對象
public static class Builder {
//初始化ParameterMapping執行個體
private ParameterMapping parameterMapping = new ParameterMapping();
//通過構造器初始化ParameterMapping的一些成員變量
public Builder(Configuration configuration, String property, TypeHandler<?> typeHandler) {
parameterMapping.configuration = configuration;
parameterMapping.property = property;
parameterMapping.typeHandler = typeHandler;
parameterMapping.mode = ParameterMode.IN;
}
public Builder(Configuration configuration, String property, Class<?> javaType) {
parameterMapping.configuration = configuration;
parameterMapping.property = property;
parameterMapping.javaType = javaType;
parameterMapping.mode = ParameterMode.IN;
}
//設定parameterMapping的mode
public Builder mode(ParameterMode mode) {
parameterMapping.mode = mode;
return this;
}
//設定parameterMapping的javaType
public Builder javaType(Class<?> javaType) {
parameterMapping.javaType = javaType;
return this;
}
//設定parameterMapping的jdbcType
public Builder jdbcType(JdbcType jdbcType) {
parameterMapping.jdbcType = jdbcType;
return this;
}
//設定parameterMapping的numericScale
public Builder numericScale(Integer numericScale) {
parameterMapping.numericScale = numericScale;
return this;
}
//設定parameterMapping的resultMapId
public Builder resultMapId(String resultMapId) {
parameterMapping.resultMapId = resultMapId;
return this;
}
//設定parameterMapping的typeHandler
public Builder typeHandler(TypeHandler<?> typeHandler) {
parameterMapping.typeHandler = typeHandler;
return this;
}
//設定parameterMapping的jdbcTypeName
public Builder jdbcTypeName(String jdbcTypeName) {
parameterMapping.jdbcTypeName = jdbcTypeName;
return this;
}
//設定parameterMapping的expression
public Builder expression(String expression) {
parameterMapping.expression = expression;
return this;
}
//通過build()方法建立對象,傳回
public ParameterMapping build() {
resolveTypeHandler();
validate();
return parameterMapping;
}
}
}
在SqlSourceBuilder類的buildParameterMapping()方法中可以看到建造者模式的實戰應用:
//根據參數content,建構parameterMapping執行個體
private ParameterMapping buildParameterMapping(String content) {
//屬性值的Map集合
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
//省略...
//建立一個ParameterMapping.Builder對象
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
//省略...
//這裡周遊Map集合,把屬性值設定到ParameterMapping對象中,并建立
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
//抛出異常Expression based parameters are not supported yet
} else {
//抛出異常
}
}
//省略...
//建立ParameterMapping對象,并傳回
return builder.build();
}
模闆模式
模闆模式是一種行為型模式,一般用在一些比較通用的方法中,定義一個抽象類,編寫一個算法的骨架,将一些步驟延遲到子類。就像是請假條一樣,開頭和結尾都是寫好的模闆,中間的請假的原因(内容)由請假人(子類)去補充完整,這樣可以提高代碼的複用。
在Mybatis中,模闆模式展現在Executor和BaseExecutor這兩個類中。首先看張類圖:
Executor是一個接口,從命名上可以看出是用來執行SQL語句的對象。下面有一個BaseExecutor的抽象類,這就是用來定義模闆方法的。再下面有三個實作類,SimpleExecutor(簡單執行器),ReuseExecutor(重用執行器),BatchExecutor(批量執行器)。實作類就是用來填充模闆中間的内容的。
執行器在執行JDBC操作的前後往往有很多需要處理的工作都是相同的,比如查詢的時候使用緩存,更新時需要清除緩存等等,是以就很适合使用模闆模式。
接着我們看BaseExecutor抽象類的源碼,一看就明白了,其實就定義了一個骨架。
public abstract class BaseExecutor implements Executor {
//查詢操作
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 {
//這段代碼不同的子類有不同的實作,是以是調用抽象方法doQuery()
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;
}
//抽象方法,由子類去實作
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
}
那麼就有疑問了,一開始如果都不設定的話,預設使用哪個子類的實作。很簡單,直接跟着源碼去順藤摸瓜,我們就看到了。
public class Configuration {
//預設是SIMPLE,也就是SimpleExecutor
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
}
其實模闆模式很簡單就能“認出來”,我的了解就是,抽象類裡定義具(具體的方法),具體方法再調抽(抽象方法)。那十有八九就是模闆模式了。
代理模式
代理模式屬于結構型模式,代理模式的定義說的很抽象,為其他對象提供一種代理以控制對這個對象的通路。在某些情況下,一個對象不适合或者不能直接引用另一個對象,而代理對象可以在用戶端和目标對象之間起到中介的作用。
其實代理模式可以簡單了解為中介的作用,比如一手房東隻關心收租,其他的水電費結算,帶人看房這些雜七雜八的東西他不想關心,就交給中介(二手房東),租客要租房就給錢中介,一手房東收錢就找中介,這個中介就是所謂的代理者。
回到Mybatis架構中,SqlSession類就用到代理模式,SqlSession是操作資料庫一個會話對象,我們使用者一般通過SqlSession做增删改查,但是如果每次做增删改都開啟事務,關閉事務,顯然是很麻煩,是以就可以交給代理類來完成這個工作,如果沒有開啟事務,由代理類自動開啟事務。
Mybatis在這裡是使用JDK動态代理,是以SqlSession是一個接口。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<T> Cursor<T> selectCursor(String statement);
void select(String statement, Object parameter, ResultHandler handler);
int insert(String statement);
int update(String statement);
int delete(String statement);
void commit();
void rollback();
//省略...
}
我們知道動态代理要有一個實作InvocationHandler接口的類,這個類在SqlSessionManager裡,是一個内部類,叫做SqlSessionInterceptor,在這個類裡做相關的處理。
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
// Prevent Synthetic Access
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//擷取SqlSession對象,localSqlSession是一個ThreadLocal,是以是每個線程有自己的sqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
//如果不為null
if (sqlSession != null) {
try {
//執行方法,傳回結果
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//如果sqlsession為null
} else {
//打開session
final SqlSession autoSqlSession = openSession();
try {
//執行Sqlsession的方法,獲得結果
final Object result = method.invoke(autoSqlSession, args);
//commit送出事務
autoSqlSession.commit();
//傳回結果
return result;
} catch (Throwable t) {
//rollback復原事務
autoSqlSession.rollback();
//抛出異常
throw ExceptionUtil.unwrapThrowable(t);
} finally {
//關閉sqlsession
autoSqlSession.close();
}
}
}
}
然後通過構造器去初始化,提供靜态方法傳回這個SqlSessionManager執行個體,請看源碼。
//實作了SqlSessionFactory,SqlSession接口
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
private final SqlSessionFactory sqlSessionFactory;
//代理類sqlSessionProxy
private final SqlSession sqlSessionProxy;
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
//初始化代理類
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
//提供一個靜态方法給外部擷取SqlSessionManager類,可以用SqlSession接收
public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionManager(sqlSessionFactory);
}
//重寫selectOne方法,使用代理類去執行
@Override
public <T> T selectOne(String statement) {
return sqlSessionProxy.<T> selectOne(statement);
}
//重寫insert方法,使用代理類去執行
@Override
public int insert(String statement) {
return sqlSessionProxy.insert(statement);
}
//省略...
}
這就是Mybatis使用代理模式的一個例子,其實也不是很複雜,還是能看懂的。
但是上面這種方式一般很少用,我們一般都是使用Mapper接口的方式,其實Mapper接口的方式也是使用了代理模式,接下來再繼續看。直接看MapperProxy類。
//實作了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
//代理類做的什麼事情,看這個方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//省略...
//擷取mapperMethod對象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//執行方法。根據Mapper.xml配置檔案的配置進行執行,傳回結果
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
//從methodCache取出mapperMethod
MapperMethod mapperMethod = methodCache.get(method);
//為null
if (mapperMethod == null) {
//new一個。這裡已經把Mapper.xml的一些配置都封裝好了
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
//然後put進methodCache
methodCache.put(method, mapperMethod);
}
//傳回mapperMethod
return mapperMethod;
}
}
mapperMethod的execute()方法的作用就是根據Mapper接口的方法名找到Mapper.xml檔案的sql的Id,然後執行相應的操作,傳回結果。内部的代碼比較長,但是思路就是這樣,這裡就不展開了。
是以我們平時用的時候,TbCommodityInfoMapper接口假設是這樣定義了一個list()方法。
public interface TbCommodityInfoMapper {
//查詢TbCommodityInfo清單
List<TbCommodityInfo> list();
}
那個在TbCommodityInfoMapper.xml就要對應有一個id為list的配置。
<!-- 命名空間也要和接口的全限定名一緻 -->
<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
<!-- 必須要有一個對應的屬性id為list的sql配置 -->
<select id="list" resultType="tbCommodityInfo">
select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
</select>
</mapper>
然後在MapperProxyFactory類,使用工廠模式提供擷取代理類。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
//通過構造器初始化mapperInterface
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//通過這個方法,擷取Mapper接口的代理類
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
那個這個getMapper的方法就在SqlSession的子類中被調用。
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
最終使用者就是這樣使用,這裡是純Mybatis,沒有內建Spring的寫法。
public class Test {
public static void main(String[] args) {
//擷取SqlSession對象
SqlSession sqlSession = SqlSessionFactoryUtils.openSqlSession();
//再擷取Mapper接口的代理類
TbCommodityInfoMapper tbCommodityInfoMapper = sqlSession.getMapper(TbCommodityInfoMapper.class);
//通過代理類去執行相應的方法
List<TbCommodityInfo> commodityInfoList = tbCommodityInfoMapper.list();
}
}
是以代理模式可以說是Mybatis核心的設計模式,用的是非常巧妙。
裝飾器模式
裝飾器模式屬于結構型模式,使用裝飾類包裝對象,動态地給對象添加一些額外的職責。那麼在Mybatis中,哪個地方會使用到裝飾器模式呢?
沒錯了,就是緩存。Mybatis有一級緩存和二級緩存的功能,一級緩存預設是打開的,範圍在SqlSession中生效,二級緩存需要手動配置打開,範圍在全局Configuration,在每個namespace中配置。二級緩存的類型有以下幾種,請看配置。
<mapper namespace="io.github.yehongzhi.commodity.mapper.TbCommodityInfoMapper">
<!--
eviction:代表的是緩存回收政策,目前MyBatis提供以下政策。
(1) LRU,最近最少使用的,一處最長時間不用的對象
(2) FIFO,先進先出,按對象進入緩存的順序來移除他們
(3) SOFT,軟引用,移除基于垃圾回收器狀态和軟引用規則的對象
(4) WEAK,弱引用,更積極的移除基于垃圾收集器狀态和弱引用規則的對象。這裡采用的是LRU,移除最長時間不用的對形象
flushInterval:重新整理間隔時間,機關為毫秒,這裡配置的是100秒重新整理,如果你不配置它,那麼當SQL被執行的時候才會去重新整理緩存。
size:引用數目,一個正整數,代表緩存最多可以存儲多少個對象,不宜設定過大。設定過大會導緻記憶體溢出。這裡配置的是1024個對象
readOnly:隻讀,意味着緩存資料隻能讀取而不能修改,這樣設定的好處是我們可以快速讀取緩存,缺點是我們沒有辦法修改緩存
-->
<cache eviction="LRU" flushInterval="100000" readOnly="true" size="1024"/>
<select id="list" resultMap="base_column">
select `id`, `commodity_name`, `commodity_price`, `description`, `number` from tb_commodity_info
</select>
</mapper>
是以這個場景已經很清晰了,也就是在一級緩存的基礎上,再添加二級緩存,也就符合裝飾器模式的那句話,動态給對象添加職責功能。怎麼做呢,我們不妨先看Cache類的類圖。
先看Cache接口,其實就定義了一些接口方法。
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
然後再看PerpetualCache類,這是Cache接口最基本的實作,二級緩存要擴充就在這個類上面再去包裝來實作擴充。其實就是一個HashMap,再簡單包裝一下。
public class PerpetualCache implements Cache {
private final String id;
//成員變量cache,建立一個HashMap
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);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
//省略...
}
那麼我們再看二級緩存的一個代表性的類LruCache。
public class LruCache implements Cache {
//一級緩存,儲存在這個成員變量中
private final Cache delegate;
//實際上這是一個LinkedHashMap,利用LinkedHashMap的LRU算法實作緩存的LRU
private Map<Object, Object> keyMap;
private Object eldestKey;
//構造器緩存對象,初始化
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
//初始化keyMap,重寫removeEldestEntry方法,實作LUR算法
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
//每次put進來時,eldestKey都是最老的key
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
//儲存進緩存
delegate.putObject(key, value);
//這裡就是删除掉不常用的值
cycleKeyList(key);
}
@Override
public Object getObject(Object key) {
keyMap.get(key); //touch
return delegate.getObject(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
//删除掉Map中最老的key
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
關鍵在于成員變量delegate,這在其他的二級緩存裝飾類中都定義了。這個是為了儲存PerpetualCache這個基礎緩存類的。是以這也就是說二級緩存是在PerpetualCache為基礎擴充的。再繼續看就更加明白了。
直接看建立SqlSessionFactory的builder方法,一直追蹤下去,就可以找到MapperBuilderAssistant類的useNewCache方法。
public class MapperBuilderAssistant extends BaseBuilder {
//目前Mapper.xml的命名空間
private String currentNamespace;
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
//設定基礎緩存
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
//添加緩存裝飾類
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
//建立緩存
.build();
//把緩存添加到configuration,是以二級緩存是configuration範圍的
configuration.addCache(cache);
currentCache = cache;
return cache;
}
}
再看CacheBuilder的build方法,更加清晰了。
public class CacheBuilder {
private Class<? extends Cache> implementation;
private final List<Class<? extends Cache>> decorators;
public Cache build() {
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
//周遊裝飾器類
for (Class<? extends Cache> decorator : decorators) {
//在cache上添加二級緩存
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
//傳回緩存
return cache;
}
private void setDefaultImplementations() {
if (implementation == null) {
//如果為空,初始化為PerpetualCache
implementation = PerpetualCache.class;
//如果裝飾器類為空,預設用LruCache
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
}
是以我們大概可以想到二級緩存就像千層餅一樣,一層一層地包裝起來。最後debug模式驗證一下。
在執行查詢的時候你就會看到真的是千層餅一樣,一層一層的。看CachingExecutor類的query方法。
最後在調用Cache的putObject方法時就會一層一層從外到内地調用,實作為對象動态擴充功能的裝飾器模式。
裝飾器模式在Mybatis中的應用就講到這裡了,有什麼不懂的,可以關注公衆号
java技術愛好者
,加我微信提問。
總結
這篇文章就介紹了Mybatis中用到的6種設計模式,分别是工廠模式,單例模式,模闆模式,建造者模式,代理模式,還有裝飾器模式。實際上Mybatis除了我講的這些之外,還有很多我沒有提到的,比如組合模式,擴充卡模式等等,有興趣自己去研究一下吧。
因為現在很多面試動不動就問有看過什麼架構的源碼,實際上看源碼是好的,但是不能盲目入手,因為很多架構是運用了大量的設計模式,如果對設計模式沒有一定的認識,很容易看不懂,看懵。是以對于還沒有設計模式基礎的同學,建議先看設計模式,然後再去學習源碼,這樣才能循序漸進地提升自身實力。
這篇文章就講到這裡了,感謝大家的閱讀。
覺得有用就點個贊吧,你的點贊是我創作的最大動力~
我是一個努力讓大家記住的程式員。我們下期再見!!!
能力有限,如果有什麼錯誤或者不當之處,請大家批評指正,一起學習交流!