MyBatis源碼相對於Spring應該是層次比較清晰,容易理解的;其實簡單來說就是解耦、封裝,讓開發者更關注業務層的開發,實現簡單而又方便的調用。
本文主要介紹mybatis框架的幾個重要點:xml文件加載初始化、mapper接口動態代理加強和源碼的一些模塊以及所涉及的設計模式;通過閱讀本文之後你將會學到Mybatis的工作流程以及常用的框架技術和模式。
文章目錄
注:以下介紹主要是基於使用註解的SpringBoot配置。
一 、xml文件加載初始化
Mybatis加載XML文件核心類:
XMLConfigBuilder:解析Mybatis-config.xml文件節點</configuration>;這一塊可以忽略,因爲現在幾乎都是Spring集成Mybatis,通過application.properties來配置,跳過config文件解析直接進入第二階段XMLMapperBuilder解析mapper.xml文件。
XMLMapperBuilder:解析mapper.xml文件節點</mapper>;Mybatis結合springBoot或者Spring使用時,在配置dataSource創建SqlSessionFactory時啓動的XMLMapperBuilder對象。主要有ResultMap、Sql節點等。
XMLStatementBuilder:解析select|insert|update|delete,封裝成MappedStatement。
bing綁定:綁定mapper class和對代理工廠對象,mapperRegistry是其註冊中心;創建Mapper接口的代理工廠類MapperProxyFactory,該類主要爲Mapper接口方法生成代理類(MapperProxy(JDK動態代理))進行增強。
集成SpringBoot初始化XML:
配置數據源時SqlSessionFactory是必不可少,它是生成SqlSession的工廠類(默認DefaultSqlSessionFactory);而SqlSessionFactory是由SqlSessionFactoryBean實例化的如下圖代碼:
- MapperLocations 就是設置Mapper文件資源的;
- plugins是引入插件例如分頁插件,關於插件第三節中詳細介紹;
- 加載解析xml,在getObject()------->afterPropertiesSet()------->buildSqlSessionFactory(),最後這個方法會真正調用Mybatis加載XML文件核心類加載、初始化mapper文件,這邊主要介紹Mapper是如何綁定代理工廠的。
注意BeanFactory和FactoryBean的區別
public SqlSessionFactory test1SqlSessionFactory(@Qualifier("test1DataSource") DataSource datasource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(datasource); // 設置mybatis的xml所在位置 bean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath*:mapper/test1/*.xml")); bean.setPlugins(new Interceptor[]{DefinePageInterceptor.buildPageInterceptor()}); // FactoryBean就是通過getObject()獲取真正的bean實例(注意BeanFactory和FactoryBean的區別) return bean.getObject(); }
Mapper是如何綁定代理工廠(注意文件中的註釋):
// 1、XMLMapperBuilder 爲Mapper綁定代理工廠
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 1. 綁定Mapper的代理工廠MapperProxyTactory---入口
configuration.addMapper(boundType);
}
}
}
}
-----------------------------------------------------------------------------
// 1、MapperRegistry 註冊Mapper對應的代理工廠類
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
二、Mapper接口動態代理加強
問題思考:Mybatis是如何通過Mapper接口進行sql操作的?
MapperProxy就是Mapper接口的代理類,這個代理類由代理工廠類MapperProxyFactory生成如圖所示(此工廠類在第一步中已經將Mapper接口類型作爲key進行緩存),MapperProxy的必須屬性參數有mapper相關的sqlsession,mapper接口類型Class。
// 1、MapperRegistry:註冊中心獲取代理工廠
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 2、代理工廠創建代理類
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
-------------------------------------------------------------------------------------
// 1、MapperProxyFactory 代理工廠類創建代理
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
1、動態代理:當調用Mapper接口中的方法就會交由MapperProxy進行代理
@Override
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);
}
// 1、獲取MapperMethod 執行execute(method的執行是放在MapperMethod中的)
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 2、MapperMethod中根據SqlCommand的類調用sqlSession的 insert|update.....
return mapperMethod.execute(sqlSession, args);
}
// 3、先取緩存如果沒有,創建一個新的(封裝了mapper接口、要執行的方法以及貫穿整個mybatis的Configuration)
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
method.execute(sqlsession,args)真正 調用sqlSession的地方。走進去看一下sqlSession觸發不同的sql類型,但是對於select由於返回類型可能不同,再繼續處理。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根據sqlCommand類型執行insert|update
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:
// 2、根據select返回類型的不同執行不同的方法,,其最終都會執行selectList,只是前期準備不同。
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} 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;
}
2、Executor執行:其實最終還是要使用Executor執行的
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 1、根據namespace獲取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 2、wrapCollection()封裝參數值
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();
}
}
Executor有兩個實現類,類圖如下;Executor是由Configuration創建的,並且使用了裝飾器模式以及動態代理進行加強(interceptorChain.pluginAll(executor))
// 1、DefaultSqlSessionFactory 中創建DefaultSqlSession
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);
// 2、DefaultSqlSessionFactory中創建DefaultSqlSession
final Executor executor = configuration.newExecutor(tx, execType);
// 3、封裝參數到SqlSession中
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();
}
}
------------------------------------------------------------------------------------
// 1、Configuration中創建Executor
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 2、裝飾器模式對基礎的Executor進行裝飾
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 3、遍歷所有的攔截器,然後調用各個攔截器的plugin方法,然後使用Plugin.wrap()爲executor生成
Plugin代理對象(封裝了interceptor、待攔截的method、target對象)。
// 攔截器鏈InterceptorChain會對每一個攔截器依次封裝在代理對象中(即:realObject -->
proxy1RealObject --> prox2Prox1RealObject)只要符合攔截條件都會被依次代理增強。攔截執行
的時候會倒序依次執行(prox2Prox1RealObject-->proxy1RealObject -->realObject)
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
ReuseExecutor:對Statement進行了緩存,重複利用。
以SimpleExecutor爲例看其處理過程:
下圖中doQuery()方法第四行代碼configuration.newStatementHandler():在Configuration類中通過RoutingStatementHandler(靜態代理)根據MappedStatement的StatementType(默認PREPARED)生成哪一種StatementHandler(類圖如下),
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// 1、sql執行---最終會使用PreparedStatementHandler (如下代碼)
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 是否對獲取的連接進行log加強
Connection connection = getConnection(statementLog);
// 獲取Statement ----最終會調用instantiateStatement()如下代碼
stmt = handler.prepare(connection, transaction.getTimeout());
// 參數設值 --- 如下代碼
handler.parameterize(stmt);
return stmt;
}
-----------------------------------------------------------------------------------------
// 1、PreparedStatementHandler 使用Statement執行execute() (和使用jdbc一模一樣的)
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
// 2、結果集處理見(ResultSetHandler處理小節)
return resultSetHandler.<E> handleResultSets(ps);
}
// 3、PreparedStatementHandler 使用connection獲取Statement (和使用jdbc一模一樣的)
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
// 4、參數設值(獲取ParameterMapping使用MetaObject(反射模塊詳細介紹)取值,然後佔位符位置設值)
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
StatementHandler繼承關係:
3、ResultSetHandler解析查詢結果:結果解析(ResultSetHandler)唯一默認實現類DefaultResultSetHandler,也會被加強;
// 1、 Configuration中ResultSetHandler生成 (DefaultResultSetHandler默認唯一實現)
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
----------------------------------------------------------------------------------------
// 2、DefaultResultSetHandler處理查詢結果接上一小節
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 返回結果集處理封裝對象。-----不詳細介紹
while (rsw != null && resultMapCount > resultSetCount) {
// 3、獲得resultMap,實體類和表中數據字段的對應關係
ResultMap resultMap = resultMaps.get(resultSetCount);
// 4、將值設置成對應的resultmap對象
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
........
return collapseSingleResultList(multipleResults);
}
總結:ResultSetHandler、StatementHandler職責分工明確、層次分明,jdbc操作數據庫的整套代碼被完全封裝起來,其本質和邏輯還是一樣的。
三、Mybatis涉及的模塊以及設計模式使用
- 反射模塊:查詢的結果ResultSet通過反射構建bean。(實例化目標對象和對象屬性賦值需要反射生成)---工廠模式
反射的核心類:
1)Reflector:存放class定義的屬性、方法信息
2)DefaultReflectorFactory:依據class類新創建Reflector並緩存
3)MetaClass:引用ReflectorFactory、Reflector,可以獲取類的元信息
4)ObjectWrapper:對bean、MetaClass的封裝,可以實現對bean屬性的設值以及獲取
5)DefaultObjectFactory:實例化對象
6)MetaObject:主要封裝了bean、ObjectWrapper、DefaultObjectFactory、DefaultReflectorFactory,因此可以通過MetaObject設置和獲取bean的屬性。(主要是通過ObjectWrapper操作)
- 用於實例化目標對象的類
------ObjectFactory:MyBatis每次創建結果對象的新實例時,使用ObjectFactory構建POJO,默認實現DefaultObjectFactory(反射實例化對象)
反射創建對象(注意區別):
- 用於對象屬性賦值的類
------ReflectorFactory:創建Reflector的工廠類,Reflector是MyBatis反射模塊的基礎,每個Reflector對象都對應一個類,在其中緩存了反射操作所需要的類元信息。默認實現類DefaultReflectorFactory(讀取類元信息封裝在Reflector中,每一個類都有一個Reflector)。
Reflector(核心元信息緩存類):
------MetaObject:
MetaObject封裝了對象元信息,包裝了MyBatis中的五個核心的反射類,也是提供給外部使用的反射工具類,可以利用它讀取或者修改對象的屬性信息(其實也是通過ObjectWrapper調用相關的方法,例如BeanWrapper)。
// MetaObject 包裝五個核心類
private final Object originalObject;
private final ObjectWrapper objectWrapper;
private final ObjectFactory objectFactory;
private final ObjectWrapperFactory objectWrapperFactory;
private final ReflectorFactory reflectorFactory;
BeanWrapper作用如下:(是對bean的封裝可以給bean屬性設置值,也可以獲取某個屬性的值)
public class BeanWrapper extends BaseWrapper {
//1、BeanWrapper 封裝了bean對象以及對應的類元信息,通過BeanWrapper get()和set()獲取屬性值或者設置屬性值
private final Object object;
private final MetaClass metaClass;
public BeanWrapper(MetaObject metaObject, Object object) {
super(metaObject);
this.object = object;
this.metaClass = MetaClass.forClass(object.getClass(), metaObject.getReflectorFactory());
}
}
MetaClass(封裝類元信息,並且也是通過Reflector來獲取類的相關屬性信息):
public class MetaClass {
// 1、封裝 類的屬性和方法信息,並且可以通過此類獲取屬性等信息(如方法返回類型,參數類型)
private final ReflectorFactory reflectorFactory;
private final Reflector reflector;
private MetaClass(Class<?> type, ReflectorFactory reflectorFactory) {
this.reflectorFactory = reflectorFactory;
this.reflector = reflectorFactory.findForClass(type);
}
public static MetaClass forClass(Class<?> type, ReflectorFactory reflectorFactory) {
return new MetaClass(type, reflectorFactory);
}
}
總結:反射模塊核心類使用如下
public void mybatisReflect(){
DefaultObjectFactory defaultObjectFactory = new DefaultObjectFactory();
ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
// 1、DefaultObjectFactory創建bean
User user = defaultObjectFactory.create(User.class);
DefaultReflectorFactory defaultReflectorFactory = new DefaultReflectorFactory();
// 2、創建MetaObject對象並在BeanWrapper中使用defaultReflectorFactory創建Reflector(Reflector加載了類元信息)
MetaObject metaObject = MetaObject.forObject(user, defaultObjectFactory, objectWrapperFactory, defaultReflectorFactory);
// 3、BeanWrapper
ObjectWrapper objectWrapper = metaObject.getObjectWrapper();
// 4、使用ObjectWrapper讀取對象信息,並對對象屬性進行賦值操作(最終調用Reflector獲取method,然後通過反射設值)
objectWrapper.set(new PropertyTokenizer("userName"),"xiaodai");
String userName = user.getUserName();
System.out.println(userName);
}
2. 插件模塊:
MyBatis 允許使用插件來攔截的方法調用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
-------執行器的攔截-----------------------------------
- ParameterHandler (getParameterObject, setParameters)
-------參數處理的攔截--------------------------------
- ResultSetHandler (handleResultSets, handleOutputParameters)
--------結果集處理的攔截----------------------------
- StatementHandler (prepare, parameterize, batch, update, query)
--------sql語法構建的攔截--------------------------
// 通過pluginAll實現對四大接口攔截設置
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//RoutingStatementHandler 其實是個靜態代理,根據不同要求創建不同的對象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
這4個方法實例化了對應的對象之後,都會調用interceptorChain的pluginAll方法,InterceptorChain的pluginAll就是遍歷所有的攔截器,然後調用各個攔截器的plugin方法。注意:攔截器的plugin方法的返回值會直接被賦值給原先的對象。
在創建SqlSession的時候最先會執行的Configuration的newExecutor()並進行攔截器配置和加強。例:加入PageInterceptor攔截器,newExecutor()時會調用interceptorChain的pluginAll方法,然後在執行PageInterceptor.plugin(),最後會使用Plugin.wrap()爲executor生成Plugin代理對象。最後在執行executor.query()時會被Plugin加強,根據PageInterceptor註解@Signature判斷是否是要攔截的方法,PageInterceptor攔截主要是分頁處理。
Plugin這個類的作用就是根據 @Interceptors註解,得到這個註解的屬性 @Signature數組,然後根據每個 @Signature註解的type,method,args屬性使用反射找到對應的Method。最終根據調用的target對象實現的接口(會和@Signature的type進行比較)決定是否返回一個代理對象替代原先的target對象
注:攔截的入口其實就是在代理類Plugin的invoke是真實對象之前根據判斷@Signature解析的Method和classType是否和代理的對象的類型和方法一致來決定是否執行攔截器。
攔截器鏈InterceptorChain會對每一個攔截器依次封裝在代理對象中(即:realObject --> proxy1RealObject --> prox2Prox1RealObject)只要符合攔截條件都會被依次代理增強。攔截執行的時候會倒序依次執行(prox2Prox1RealObject-->proxy1RealObject -->realObject)。
3. Logging模塊可以引入第三方例如Slfj----------適配器模式:
mybatis只有屬於自己一套的log接口,並沒有所有本身的日誌,那麼如何和市面上面 流行的日誌框架做整合呢?這個時候就需要用到適配器模式。
如上圖實現mybatis的日誌實現類都需要實現這個接口。這使用到LogFactory
==org.apache.ibatis.logging.LogFactory== 日誌工廠
如上圖 tryImplementation()先判斷logConstructor是否爲空,只有logConstructor爲空的時候tryImplementation裏面的方法纔會執行。
例如:useSlf4jLogging() ---> 會獲取Slf4jImpl的構造器,當使用LogFactory.getLogger()就會實例化,此時logConstructor已經有值,那麼其它的try都不會在創建構造器。
4. 緩存模塊--------Cache接口(裝飾器模式)
實現類-->PerpetualCache:Cache的基本實現類
BlockingCache:它是個阻塞版本的緩存裝飾器,保證只有一個線程到數據庫中查找 key 對應的數據,使用 ConcurrentHashMap<Object, ReentrantLock> 實現了阻塞功能。
FifoCache:FifoCache 底層維護了一個隊列,當需要清除時會清除隊列頭部的緩存
LruCache:LruCache底層維護了一個 LinkedHashMap 它是一種有序的 HashMap。當我們獲取一個對象的時候會把 這個對象移動到 LinkedHashMap 的尾部表示最近訪問的,需要清除的時候清除頭部即可。
SoftCache&WeakCache:是 JVM 中定義的引用類型,是爲了更方便的進行JVM垃圾回收的,針對那些只用一次的對象 進 行更高效率的回收。
MyBatis中涉及到動態SQL需要通過CacheKey來封裝緩存的key值。
構成CacheKey對象的要素(如下圖代碼):
- mappedStatement的id
- 指定查詢結果集的範圍(分頁信息)
- 查詢所使用的SQL語句
- 用戶傳遞給SQL語句的實際參數值
BaseExecutor的query:
cacheKey比較是否相等判斷邏輯:
總結:MyBatis 的緩存接口 Cache 的基礎實現 PerpetualCache 維護了一份 HashMap 作爲緩存的實現。同時它還有很多裝飾類,比如阻塞式緩存BlockingCache 內部維護了帶對象同步器的 ConcurrentHashMap、帶清除策略的 FifoCache/LruCache,FifoCache 內部維護了一個隊列Dqueue,LruCache內部維護了一份 LinkedHashMap 。這些都使得緩存模塊的功能很多,業務方可以根據自己的需求進行靈活組裝。
一級緩存和二級緩存:
MyBatis 中的緩存就是說 MyBatis 在執行一次SQL查詢或者SQL更新之後,這條SQL語句並不會消失,而是被MyBatis 緩存起來,當再次執行相同SQL語句的時候,就會直接從緩存中進行提取,而不是再次執行SQL命令。
一級緩存:
mybatis的一級緩存是基於sqlsession的緩存,默認開啓,不同sqlsession之間的緩存數據區域是不會相互影響的,如果會話失效,則緩存失效。
清除緩存:
當commit或者rollback的時候會清除緩存,並且當執行insert、update、delete的時候也會清除緩存。
mybatis一級緩存失效的四種情況
- sqlsession變了 緩存失效
- sqlsession不變,查詢條件不同,一級緩存失效
- sqlsession不變,中間發生了增刪改操作,一級緩存失敗
- sqlsession不變,手動清除緩存,一級緩存失敗
二級緩存:
二級緩存是手動開啓的,作用域爲mapper級別(也可以說MapperStatement級緩存,也就是一個namespace就會有一個緩存),不同的sqlsession兩次執行相同的namespace下的sql,且向sqlq中傳遞的參數也相同,即最終執行相同的sql,則第一次中會從數據庫查並存緩存,第二次會從內存查;因爲二級緩存的數據不一定都是存儲到內存中,它的存儲介質多種多樣,實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的,也就是要求實現Serializable接口,如果存儲在內存中的話,實測不序列化也可以的。
如果開啓了二級緩存的話,你的Executor將會被裝飾成CachingExecutor,緩存是通過CachingExecutor來操作的,查詢出來的結果會存在statement中的cache中,若有更新,刪除類的操作默認就會清空該MapperStatement的cache(也可以通過修改xml中的屬性,讓它不執行),不會影響其他的MapperStatement。
5、建造者模式:在解析mapper.xml時會通過MapperBuilderAssistant助理進行每一階段的屬性值的builder(例如addResultMap、addMappedStatement)。