上海工程建设信息网站,合肥地区网站制作,广州网站建设专注乐云seo,北京网站推广服务文章目录 一、目标#xff1a;二级缓存二、设计#xff1a;二级缓存三、实现#xff1a;二级缓存3.1 工程结构3.2 二级缓存类图3.3 二级缓存队列3.3.1 FIFI缓存策略3.3.2 事务缓存3.3.3 事务管理3.3.4 修改一级缓存 3.4 缓存执行器3.4.1 执行器接口3.4.2 执行器抽象基类3.4.… 文章目录 一、目标二级缓存二、设计二级缓存三、实现二级缓存3.1 工程结构3.2 二级缓存类图3.3 二级缓存队列3.3.1 FIFI缓存策略3.3.2 事务缓存3.3.3 事务管理3.3.4 修改一级缓存 3.4 缓存执行器3.4.1 执行器接口3.4.2 执行器抽象基类3.4.3 缓存执行器 3.5 修改配置3.5.1 映射器语句类3.5.2 配置项3.5.3 默认SqlSession实现类 3.6 映射构建器助手添加缓存3.6.1 缓存构建器3.6.2 映射构建器助手 3.7 XML配置构建器3.7.1 构建器基类3.7.2 注解配置构建器3.7.3 XML语言构建器3.7.4 XML映射构建器3.7.5 XML配置构建器-全局缓存解析3.7.6 XML配置构建器 四、测试二级缓存4.1 修改配置4.1.1 修改xml配置4.1.2 缓存策略配置 4.2 单元测试 五、总结二级缓存 一、目标二级缓存 一级缓存是基于会话那么怎么在会话结束之后依旧可以使用缓存呢? 关于缓存的实现希望于当会话结束后再发起的会话还是相同的查询操作最好也是可以把数据从缓存中获取出来。二级缓存以一个 Mapper 为生命周期在这个 Mapper 内的同一个操作无论发起几次会话都可以使用缓存来处理数据。 之所以称为之 二级缓存是因为它在一级缓存会话层上添加的额外缓存操作当会话发生 close、commit 操作时则把数据刷到二级缓存中进行保存直至执行器发生 update 操作时清空缓存。
二、设计二级缓存 设计二级缓存? 二级缓存的重点在于无论多少个 SqlSession 会话操作同一个 SQL不管 SqlSession 是否相同只要 Mapper 的 namespace 相同就能共享数据。所以二级缓存也被称为 namespace 级别的缓存相当于一级缓存作用域范围更广了。设计二级缓存应该为 Mapper XML 解析后的 MappedStatement 映射器语句提供缓存服务。当有会话的生命周期结束后应该将会话的数据刷新到二级缓存中便于后续在同 namespace 下处理相同 SQL 的操作时使用。 首先要在 XML 的解析中添加关于全局是否使用缓存的操作此外因为缓存的作用域范围是在 Mapper 的 namespace 级别上所以这里要为解析 MappedStatement 映射器语句提供缓存策略。 注意缓存策略一共有四种实现包括LRU、FIFO、SOFT、WEAK。 当配置了开启二级缓存服务那么在开启会话创建执行器时会把执行器使用缓存执行器做一层装饰器的设计使用。 因为需要通过这个方式将事务缓存起来同时包装结束会话的指令 close、commit 处理一级缓存数据刷新到二级缓存中。这样在下次执行相同下 namespace 以及同样的 SQL 时就可以直接从缓存中获取数据了。
三、实现二级缓存
3.1 工程结构
mybatis-step-18
|-src|-main| |-java| |-com.lino.mybatis| |-annotations| | |-Delete.java| | |-Insert.java| | |-Select.java| | |-Update.java| |-binding| | |-MapperMethod.java| | |-MapperProxy.java| | |-MapperProxyFactory.java| | |-MapperRegistry.java| |-builder| | |-annotations| | | |-MapperAnnotationBuilder.java| | |-xml| | | |-XMLConfigBuilder.java| | | |-XMLMapperBuilder.java| | | |-XMLStatementBuilder.java| | |-BaseBuilder.java| | |-MapperBuilderAssistant.java| | |-ParameterExpression.java| | |-ResultMapResolver.java| | |-SqlSourceBuilder.java| | |-StaticSqlSource.java| |-cache| | |-decorators| | | |-FifoCache.java| | | |-TransactionalCache.java| | |-impl| | | |-PrepetualCache.java| | |-Cache.java| | |-CacheKey.java| | |-NullCacheKey.java| | |-TransactionalCacheManager.java| |-datasource| | |-druid| | | |-DruidDataSourceFacroty.java| | |-pooled| | | |-PooledConnection.java| | | |-PooledDataSource.java| | | |-PooledDataSourceFacroty.java| | | |-PoolState.java| | |-unpooled| | | |-UnpooledDataSource.java| | | |-UnpooledDataSourceFacroty.java| | |-DataSourceFactory.java| |-executor| | |-keygen| | | |-Jdbc3KeyGenerator.java| | | |-KeyGenerator.java| | | |-NoKeyGenerator.java| | | |-SelectKeyGenerator.java| | |-parameter| | | |-ParameterHandler.java| | |-result| | | |-DefaultResultContext.java| | | |-DefaultResultHandler.java| | |-resultset| | | |-DefaultResultSetHandler.java| | | |-ResultSetHandler.java| | | |-ResultSetWrapper.java| | |-statement| | | |-BaseStatementHandler.java| | | |-PreparedStatementHandler.java| | | |-SimpleStatementHandler.java| | | |-StatementHandler.java| | |-BaseExecutor.java| | |-CachingExecutor.java| | |-ExecutionPlaceholder.java| | |-Executor.java| | |-SimpleExecutor.java| |-io| | |-Resources.java| |-mapping| | |-BoundSql.java| | |-CacheBuilder.java| | |-Environment.java| | |-MappedStatement.java| | |-ParameterMapping.java| | |-ResultFlag.java| | |-ResultMap.java| | |-ResultMapping.java| | |-SqlCommandType.java| | |-SqlSource.java| |-parsing| | |-GenericTokenParser.java| | |-TokenHandler.java| |-plugin| | |-Interceptor.java| | |-InterceptorChain.java| | |-Intercepts.java| | |-Invocation.java| | |-Plugin.java| | |-Signature.java| |-reflection| | |-factory| | | |-DefaultObjectFactory.java| | | |-ObjectFactory.java| | |-invoker| | | |-GetFieldInvoker.java| | | |-Invoker.java| | | |-MethodInvoker.java| | | |-SetFieldInvoker.java| | |-property| | | |-PropertyNamer.java| | | |-PropertyTokenizer.java| | |-wrapper| | | |-BaseWrapper.java| | | |-BeanWrapper.java| | | |-CollectionWrapper.java| | | |-DefaultObjectWrapperFactory.java| | | |-MapWrapper.java| | | |-ObjectWrapper.java| | | |-ObjectWrapperFactory.java| | |-MetaClass.java| | |-MetaObject.java| | |-Reflector.java| | |-SystemMetaObject.java| |-scripting| | |-defaults| | | |-DefaultParameterHandler.java| | | |-RawSqlSource.java| | |-xmltags| | | |-DynamicContext.java| | | |-DynamicSqlSource.java| | | |-ExpressionEvaluator.java| | | |-IfSqlNode.java| | | |-MixedSqlNode.java| | | |-OgnlCache.java| | | |-OgnlClassResolver.java| | | |-SqlNode.java| | | |-StaticTextSqlNode.java| | | |-TextSqlNode.java| | | |-TrimSqlNode.java| | | |-XMLLanguageDriver.java| | | |-XMLScriptBuilder.java| | |-LanguageDriver.java| | |-LanguageDriverRegistry.java| |-session| | |-defaults| | | |-DefaultSqlSession.java| | | |-DefaultSqlSessionFactory.java| | |-Configuration.java| | |-LocalCacheScope.java| | |-ResultContext.java| | |-ResultHandler.java| | |-RowBounds.java| | |-SqlSession.java| | |-SqlSessionFactory.java| | |-SqlSessionFactoryBuilder.java| | |-TransactionIsolationLevel.java| |-transaction| | |-jdbc| | | |-JdbcTransaction.java| | | |-JdbcTransactionFactory.java| | |-Transaction.java| | |-TransactionFactory.java| |-type| | |-BaseTypeHandler.java| | |-DateTypeHandler.java| | |-IntegerTypeHandler.java| | |-JdbcType.java| | |-LongTypeHandler.java| | |-SimpleTypeRegistry.java| | |-StringTypeHandler.java| | |-TypeAliasRegistry.java| | |-TypeHandler.java| | |-TypeHandlerRegistry.java|-test|-java| |-com.lino.mybatis.test| |-dao| | |-IActivityDao.java| |-plugin| | |-TestPlugin.java| |-po| | |-Activity.java| |-ApiTest.java|-resources|-mapper| |-Activity_Mapper.xml|-mybatis-config-datasource.xml3.2 二级缓存类图 整个二级缓存的核心功能逻辑实现主要以体现在实现 Cache 缓存接口提供 Mapper XML 解析作用域 namespace 范围的缓存队列的使用上。通过提供装饰执行器实现模式的 CacheingExecutor 二级缓存执行器包括会话事务缓存操作在当会话以 close、commit 方式结束时将缓存刷新到二级缓存队列中便于下次相同作用域范围下的同一个查询可以直接从二级缓存中获取数据。
3.3 二级缓存队列
Mybatis 对二级缓存的设计非常灵活你可以通过配置对缓存的策略做一系列的调整包括缓存策略LRU、FIFO、SOFT、WEAK。 LRU最近很少使用主动移除最长时间不被使用的缓存对象。LRU 也是默认的缓存策略。FIFO先进先出按对象进入缓存的顺序移除过期对象。SOFT软引用基于垃圾回收器状态和软引用规则移除对象。WEAK弱引用更主动的基于垃圾回收器状态和弱引用规则移除对象。 同时也提供了相应的数据刷新策略、对象存储限制等。除此之外Mybatis 也支持用户自己实现以及跟第三方内存缓存库做集成使用。
3.3.1 FIFI缓存策略 FifoCache.java package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.Deque;
import java.util.LinkedList;/*** description: FIFI(first in, first out) cache decorator*/
public class FifoCache implements Cache {private final Cache delegate;private DequeObject keyList;private int size;public FifoCache(Cache delegate) {this.delegate delegate;this.keyList new LinkedList();this.size 1024;}Overridepublic String getId() {return delegate.getId();}Overridepublic void putObject(Object key, Object value) {cycleKeyList(key);delegate.putObject(key, value);}Overridepublic Object getObject(Object key) {return delegate.getObject(key);}Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}Overridepublic void clear() {delegate.clear();keyList.clear();}Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size size;}private void cycleKeyList(Object key) {keyList.addLast(key);if (keyList.size() size) {Object oldestKey keyList.removeFirst();delegate.removeObject(oldestKey);}}
}FIFO 先进先出队列基于 Deque 维护了一个链表其他的操作都包装给 Cache 去完成属于典型的装饰器模式。在 FifoCache 所提供的方法实现比较简单主要包括存放、获取、移除、清空队列。另外 cycleKeyList 方法的作用是在增加记录时判断记录是否超过 size 值以此移除链表的第一个元素从而达到 FIFO 缓存效果。
3.3.2 事务缓存
TransactionalCache 所保存的是会话期间内的缓存数据当会话结束后则把缓存刷新到二级缓存中。如果是回滚操作则清空缓存。 TransactionalCache.java package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** description: 事务缓存*/
public class TransactionalCache implements Cache {private Cache delegate;/*** commit时要不要清缓存*/private boolean clearOnCommit;/*** commit 时要添加的元素*/private MapObject, Object entriesToAddOnCommit;private SetObject entriesMissedInCache;public TransactionalCache(Cache delegate) {// delegate FifoCachethis.delegate delegate;// 默认 commit 时不清缓存this.clearOnCommit false;this.entriesToAddOnCommit new HashMap();this.entriesMissedInCache new HashSet();}Overridepublic String getId() {return delegate.getId();}Overridepublic void putObject(Object key, Object value) {entriesToAddOnCommit.put(key, value);}Overridepublic Object getObject(Object key) {// key: CacheKey 拼装后的哈希码Object object delegate.getObject(key);if (object null) {entriesMissedInCache.add(key);}return clearOnCommit ? null : object;}Overridepublic Object removeObject(Object key) {return null;}Overridepublic void clear() {clearOnCommit true;entriesToAddOnCommit.clear();}Overridepublic int getSize() {return delegate.getSize();}public void commit() {if (clearOnCommit) {delegate.clear();}flushPendingEntries();reset();}public void rollback() {unlockMissedEntries();reset();}public void reset() {clearOnCommit false;entriesToAddOnCommit.clear();entriesMissedInCache.clear();}/*** 刷新数据到 MappedStatement#Cache 中也就是把数据填充到 Mapper XML 级别下。* flushPendingEntries 方法把事务缓存下的数据填充到 FifoCache 中。*/private void flushPendingEntries() {for (Map.EntryObject, 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) {delegate.putObject(entry, null);}}
}TransactionalCache 事务缓存提供了对一级缓存的数据存放和使用的操作。 当一级缓存作用域范围的会话因为 commit、close 结束则会调用到 flushPengdingEntries 方法。通过循环处理调用 delegate.putObject(entry.getKey(), entry.getValue())把数据刷新到二级缓存队列中。另外 rollback 回滚方法则是一种清空缓存操作。
3.3.3 事务管理 TransactionalCacheManager package com.lino.mybatis.cache;import com.lino.mybatis.cache.decorators.TransactionalCache;
import java.util.HashMap;
import java.util.Map;/*** description: 事务缓存管理器*/
public class TransactionalCacheManager {private MapCache, TransactionalCache transactionCaches new HashMap(16);public void clear(Cache cache) {getTransactionalCache(cache).clear();}/*** 得到某个TransactionalCache的值*/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 : transactionCaches.values()) {txCache.commit();}}/*** 回滚时全部回滚*/public void rollback() {for (TransactionalCache txCache : transactionCaches.values()) {txCache.rollback();}}private TransactionalCache getTransactionalCache(Cache cache) {TransactionalCache txCache transactionCaches.get(cache);if (txCache null) {txCache new TransactionalCache(cache);transactionCaches.put(cache, txCache);}return txCache;}
}事务缓存管理器是对事务缓存的包装操作。用于在缓存执行器创建期间实例化包装执行期内的所有事务缓存操作做批量的提交和回滚时缓存数据刷新的处理。
3.3.4 修改一级缓存 PerpetualCache.java package com.lino.mybatis.cache.impl;import com.lino.mybatis.cache.Cache;import java.util.HashMap;
import java.util.Map;/*** description: 一级缓存在 Session 生命周期内一直保持每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache*/
public class PerpetualCache implements Cache {private String id;/*** 使用HashMap存放一级缓存数据session 生命周期较短正常情况下数据不会一直在缓存存放*/private MapObject, Object cache new HashMap(16);public PerpetualCache(String id) {this.id id;}Overridepublic String getId() {return id;}Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}Overridepublic Object getObject(Object key) {return cache.get(key);}Overridepublic Object removeObject(Object key) {return cache.remove(key);}Overridepublic void clear() {cache.clear();}Overridepublic int getSize() {return cache.size();}
}移除一级缓存日志打印
3.4 缓存执行器
3.4.1 执行器接口 Executor.java package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;/*** description: 执行器*/
public interface Executor {/*** 结果处理器*/ResultHandler NO_RESULT_HANDLER null;/*** 更新** param ms 映射器语句* param parameter 参数* return 返回的是受影响的行数* throws SQLException SQL异常*/int update(MappedStatement ms, Object parameter) throws SQLException;/*** 查询含缓存** param ms 映射器语句* param parameter 参数* param rowBounds 分页记录限制* param resultHandler 结果处理器* param key 缓存key* param boundSql SQL对象* param E 返回的类型* return ListE* throws SQLException SQL异常*/E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException;/*** 查询** param ms 映射器语句* param parameter 参数* param rowBounds 分页记录限制* param resultHandler 结果处理器* param E 返回的类型* return ListE* throws SQLException SQL异常*/E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;/*** 获取事务** return 事务对象*/Transaction getTransaction();/*** 提交** param required 是否请求执行* throws SQLException SQL异常*/void commit(boolean required) throws SQLException;/*** 回滚** param required 是否请求执行* throws SQLException SQL异常*/void rollback(boolean required) throws SQLException;/*** 关闭** param forceRollback 是否强制回滚*/void close(boolean forceRollback);/*** 清理session缓存*/void clearLocalCache();/*** 创建缓存key** param ms 映射器语句* param parameterObject 参数对象* param rowBounds 分页记录限制* param boundSql SQL对象* return 缓存key*/CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);/*** 设置执行器** param executor 执行器*/void setExecutorWrapper(Executor executor);
}添加 setExecutorWrapper 设置执行器方法。
3.4.2 执行器抽象基类 BaseExecutor.java package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** description: 执行器抽象基类*/
public abstract class BaseExecutor implements Executor {...Overridepublic void setExecutorWrapper(Executor executor) {this.wrapper wrapper;}Overridepublic void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {transaction.close();}} catch (SQLException e) {logger.warn(Unexpected exception on closing transaction. Cause: e);} finally {transaction null;localCache null;closed true;}}...
}3.4.3 缓存执行器
缓存执行器是一个装饰器模式将 SimpleExecutor 做一层包装提供缓存的能力。因为这样的包装后就可以将 SimpleExecutor 中的一级缓存以及相应的能力进行使用在二级缓存 CachingExecutor 执行器中完成缓存在会话周期内的流转操作。 CachingExecutor.java package com.lino.mybatis.executor;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.TransactionalCacheManager;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** description: 二级缓存执行器*/
public class CachingExecutor implements Executor {private Logger logger LoggerFactory.getLogger(BaseExecutor.class);private Executor delegate;private TransactionalCacheManager tcm new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate delegate;delegate.setExecutorWrapper(this);}Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {return delegate.update(ms, parameter);}Overridepublic E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Cache cache ms.getCache();if (cache ! null) {flushCacheIfRequired(ms);if (ms.isUseCache() resultHandler null) {SuppressWarnings(unchecked)ListE list (ListE) tcm.getObject(cache, key);if (list null) {list delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);// cache缓存队列实现类FIFO// key哈希值 [mappedStatementId offset limit SQL queryParams environment]// list查询的数据tcm.putObject(cache, key, list);}// 打印调试日志记录二级缓存获取数据if (logger.isDebugEnabled() cache.getSize() 0) {logger.debug(二级缓存: {}, JSON.toJSONString(list));}return list;}}return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);}Overridepublic E ListE query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1.获取绑定SQLBoundSql boundSql ms.getBoundSql(parameter);// 2.创建缓存keyCacheKey key createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}Overridepublic Transaction getTransaction() {return delegate.getTransaction();}Overridepublic void commit(boolean required) throws SQLException {delegate.commit(required);tcm.commit();}Overridepublic void rollback(boolean required) throws SQLException {try {delegate.rollback(required);} finally {if (required) {tcm.rollback();}}}Overridepublic void close(boolean forceRollback) {try {if (forceRollback) {tcm.rollback();} else {tcm.commit();}} finally {delegate.close(forceRollback);}}Overridepublic void clearLocalCache() {delegate.clearLocalCache();}Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}Overridepublic void setExecutorWrapper(Executor executor) {throw new UnsupportedOperationException(This method should not be called);}private void flushCacheIfRequired(MappedStatement ms) {Cache cache ms.getCache();if (cache ! null ms.isFlushCacheRequired()) {tcm.clear(cache);}}
}CachingExecutor 实现类中主要注意的点是会话中数据查询时的缓存使用在 query 方法中执行的 delegate.Equery 操作。其实这个 delegate 就是 SimpleExecutor 实例化的对象当缓存数据随着会话周期处理完后则存放到 MappedStatement 所提供的 Cache 缓存队列中也就是 FifoCache 先进先出缓存实现类。另外关于缓存的流转会调用 TransactionalCacheManager 事务缓存管理器进行操作从会话作用域范围通过会话的结束刷新提交到二级缓存或者清空处理。
3.5 修改配置
3.5.1 映射器语句类 MappedStatement package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;/*** description: 映射器语句类*/
public class MappedStatement {private String resource;private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class? resultType;private LanguageDriver lang;private ListResultMap resultMaps;private boolean flushCacheRequired;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private Cache cache;private boolean useCache;public MappedStatement() {}/*** 获取SQL对象** param parameterObject 参数* return SQL对象*/public BoundSql getBoundSql(Object parameterObject) {// 调用 SqlSource#getBoundSqlreturn sqlSource.getBoundSql(parameterObject);}public static class Builder {private MappedStatement mappedStatement new MappedStatement();public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class? resultType) {mappedStatement.configuration configuration;mappedStatement.id id;mappedStatement.sqlCommandType sqlCommandType;mappedStatement.sqlSource sqlSource;mappedStatement.resultType resultType;mappedStatement.keyGenerator configuration.isUseGeneratedKeys() SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();mappedStatement.lang configuration.getDefaultScriptingLanguageInstance();}public MappedStatement build() {assert mappedStatement.configuration ! null;assert mappedStatement.id ! null;mappedStatement.resultMaps Collections.unmodifiableList(mappedStatement.resultMaps);return mappedStatement;}public Builder resource(String resource) {mappedStatement.resource resource;return this;}public String id() {return mappedStatement.id;}public Builder resultMaps(ListResultMap resultMaps) {mappedStatement.resultMaps resultMaps;return this;}public Builder keyGenerator(KeyGenerator keyGenerator) {mappedStatement.keyGenerator keyGenerator;return this;}public Builder keyProperty(String keyProperty) {mappedStatement.keyProperties delimitedStringToArray(keyProperty);return this;}public Builder cache(Cache cache) {mappedStatement.cache cache;return this;}public Builder flushCacheRequired(boolean flushCacheRequired) {mappedStatement.flushCacheRequired flushCacheRequired;return this;}public Builder useCache(boolean useCache) {mappedStatement.useCache useCache;return this;}}private static String[] delimitedStringToArray(String in) {if (in null || in.trim().length() 0) {return null;} else {return in.split(,);}}public Configuration getConfiguration() {return configuration;}public String getId() {return id;}public SqlCommandType getSqlCommandType() {return sqlCommandType;}public SqlSource getSqlSource() {return sqlSource;}public Class? getResultType() {return resultType;}public LanguageDriver getLang() {return lang;}public ListResultMap getResultMaps() {return resultMaps;}public String[] getKeyProperties() {return keyProperties;}public KeyGenerator getKeyGenerator() {return keyGenerator;}public String getResource() {return resource;}public String[] getKeyColumns() {return keyColumns;}public boolean isFlushCacheRequired() {return flushCacheRequired;}public Cache getCache() {return cache;}public boolean isUseCache() {return useCache;}
}添加 Cache 缓存对象
3.5.2 配置项 Configuration.java package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.CachingExecutor;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.plugin.InterceptorChain;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** description: 配置项* author: lingjian* createDate: 2022/11/7 21:32*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 是否使用自动生成键值对*/protected boolean useGeneratedKeys false;/*** 默认启用缓存,cacheEnabled true/false*/protected boolean cacheEnabled true;/*** 缓存机制默认不配置的情况是 SESSION*/protected LocalCacheScope localCacheScope LocalCacheScope.SESSION;/*** 映射注册机*/protected MapperRegistry mapperRegistry new MapperRegistry(this);/*** 映射的语句存在Map里*/protected final MapString, MappedStatement mappedStatements new HashMap(16);/*** 缓存存在Map里*/protected final MapString, Cache caches new HashMap(16);/*** 结果映射存在Map里*/protected final MapString, ResultMap resultMaps new HashMap(16);/*** 键值生成器存在Map里*/protected final MapString, KeyGenerator keyGenerators new HashMap(16);/*** 插件拦截器链*/protected final InterceptorChain interceptorChain new InterceptorChain();/*** 类型别名注册机*/protected final TypeAliasRegistry typeAliasRegistry new TypeAliasRegistry();/*** 脚本语言注册器*/protected final LanguageDriverRegistry languageRegistry new LanguageDriverRegistry();/*** 类型处理器注册机*/protected final TypeHandlerRegistry typeHandlerRegistry new TypeHandlerRegistry();/*** 对象工厂*/protected ObjectFactory objectFactory new DefaultObjectFactory();/*** 对象包装工厂*/protected ObjectWrapperFactory objectWrapperFactory new DefaultObjectWrapperFactory();/*** 准备资源列表*/protected final SetString loadedResources new HashSet();/*** 数据库ID*/protected String databaseId;public Configuration() {typeAliasRegistry.registerAlias(JDBC, JdbcTransactionFactory.class);typeAliasRegistry.registerAlias(DRUID, DruidDataSourceFactory.class);typeAliasRegistry.registerAlias(UNPOOLED, UnpooledDataSourceFactory.class);typeAliasRegistry.registerAlias(POOLED, PooledDataSourceFactory.class);typeAliasRegistry.registerAlias(PERPETUAL, PerpetualCache.class);typeAliasRegistry.registerAlias(FIFO, FifoCache.class);languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);}public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);}public T void addMapper(ClassT type) {mapperRegistry.addMapper(type);}public T T getMapper(ClassT type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}public boolean hasMapper(Class? type) {return mapperRegistry.hasMapper(type);}public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);}public MappedStatement getMappedStatement(String id) {return mappedStatements.get(id);}public TypeAliasRegistry getTypeAliasRegistry() {return typeAliasRegistry;}public Environment getEnvironment() {return environment;}public void setEnvironment(Environment environment) {this.environment environment;}public String getDatabaseId() {return databaseId;}public ObjectFactory getObjectFactory() {return objectFactory;}public boolean isUseGeneratedKeys() {return useGeneratedKeys;}public void setUseGeneratedKeys(boolean useGeneratedKeys) {this.useGeneratedKeys useGeneratedKeys;}public LocalCacheScope getLocalCacheScope() {return localCacheScope;}public void setLocalCacheScope(LocalCacheScope localCacheScope) {this.localCacheScope localCacheScope;}/*** 生产执行器** param transaction 事务* return 执行器*/public Executor newExecutor(Transaction transaction) {Executor executor new SimpleExecutor(this, transaction);// 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰着模式if (cacheEnabled) {executor new CachingExecutor(executor);}return executor;}/*** 创建语句处理器** param executor 执行器* param mappedStatement 映射器语句类* param parameter 参数* param rowBounds 分页记录限制* param resultHandler 结果处理器* param boundSql SQL语句* return StatementHandler 语句处理器*/public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler new PreparedStatementHandler(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);// 嵌入插件代理对象statementHandler (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}/*** 创建结果集处理器** param executor 执行器* param mappedStatement 映射器语句类* param boundSql SQL语句* return ResultSetHandler 结果集处理器*/public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {return new DefaultResultSetHandler(executor, mappedStatement, resultHandler, rowBounds, boundSql);}/*** 创建元对象** param object 原对象* return 元对象*/public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory);}/*** 创建类型处理器注册机** return TypeHandlerRegistry 类型处理器注册机*/public TypeHandlerRegistry getTypeHandlerRegistry() {return typeHandlerRegistry;}/*** 是否包含资源** param resource 资源* return 是否*/public boolean isResourceLoaded(String resource) {return loadedResources.contains(resource);}/*** 添加资源** param resource 资源*/public void addLoadedResource(String resource) {loadedResources.add(resource);}/*** 获取脚本语言注册机** return languageRegistry 脚本语言注册机*/public LanguageDriverRegistry getLanguageRegistry() {return languageRegistry;}/*** 获取参数处理器** param mappedStatement 映射器语言类型* param parameterObject 参数对象* param boundSql SQL语句* return ParameterHandler参数处理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 创建参数处理器ParameterHandler parameterHandler mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 获取默认脚本语言驱动** return 脚本语言驱动*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}public ResultMap getResultMap(String id) {return resultMaps.get(id);}public void addResultMap(ResultMap resultMap) {resultMaps.put(resultMap.getId(), resultMap);}public void addKeyGenerator(String id, KeyGenerator keyGenerator) {keyGenerators.put(id, keyGenerator);}public KeyGenerator getKeyGenerator(String id) {return keyGenerators.get(id);}public boolean hasKeyGenerators(String id) {return keyGenerators.containsKey(id);}public void addInterceptor(Interceptor interceptorInstance) {interceptorChain.addInterceptor(interceptorInstance);}public boolean isCacheEnabled() {return cacheEnabled;}public void setCacheEnabled(boolean cacheEnabled) {this.cacheEnabled cacheEnabled;}public void addCache(Cache cache) {caches.put(cache.getId(), cache);}public Cache getCache(String id) {return caches.get(id);}}newExecutor 装饰缓存执行。
3.5.3 默认SqlSession实现类 DefaultSqlSession.java package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {...Overridepublic void close() {executor.close(false);}...
}
3.6 映射构建器助手添加缓存
3.6.1 缓存构建器 CacheBuilder.java package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.SystemMetaObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;/*** description: 缓存构建器, 建造者模式*/
public class CacheBuilder {private String id;private Class? extends Cache implementation;private ListClass? extends Cache decorators;private Integer size;private Long clearInterval;private boolean readWrite;private Properties properties;private boolean blocking;public CacheBuilder(String id) {this.id id;this.decorators new ArrayList();}public CacheBuilder implementation(Class? extends Cache implementation) {this.implementation implementation;return this;}public CacheBuilder addDecorator(Class? extends Cache decorator) {if (decorator ! null) {this.decorators.add(decorator);}return this;}public CacheBuilder size(Integer size) {this.size size;return this;}public CacheBuilder clearInterval(Long clearInterval) {this.clearInterval clearInterval;return this;}public CacheBuilder readWrite(boolean readWrite) {this.readWrite readWrite;return this;}public CacheBuilder blocking(boolean blocking) {this.blocking blocking;return this;}public CacheBuilder properties(Properties properties) {this.properties properties;return this;}public Cache build() {setDefaultImplementations();Cache cache newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class? extends Cache decorator : decorators) {// 使用装饰者模式包装cache newCacheDecoratorInstance(decorator, cache);// 额外属性设置setCacheProperties(cache);}}return cache;}private void setDefaultImplementations() {if (implementation null) {implementation PerpetualCache.class;if (decorators.isEmpty()) {decorators.add(FifoCache.class);}}}private void setCacheProperties(Cache cache) {if (properties ! null) {MetaObject metaCache SystemMetaObject.forObject(cache);for (Map.EntryObject, Object entry : properties.entrySet()) {String name (String) entry.getKey();String value (String) entry.getValue();if (metaCache.hasSetter(name)) {Class? type metaCache.getSetterType(name);if (String.class type) {metaCache.setValue(name, value);} else if (int.class type || Integer.class type) {metaCache.setValue(name, Integer.valueOf(value));} else if (long.class type || Long.class type) {metaCache.setValue(name, Long.valueOf(value));} else if (short.class type || Short.class type) {metaCache.setValue(name, Short.valueOf(value));} else if (byte.class type || Byte.class type) {metaCache.setValue(name, Byte.valueOf(value));} else if (float.class type || Float.class type) {metaCache.setValue(name, Float.valueOf(value));} else if (boolean.class type || Boolean.class type) {metaCache.setValue(name, Boolean.valueOf(value));} else if (double.class type || Double.class type) {metaCache.setValue(name, Double.valueOf(value));} else {throw new RuntimeException(Unsupported property type for cache: name of type type);}}}}}private Cache newBaseCacheInstance(Class? extends Cache cacheClass, String id) {Constructor? extends Cache cacheConstructor getBaseCacheConstructor(cacheClass);try {return cacheConstructor.newInstance(id);} catch (Exception e) {throw new RuntimeException(Could not instantiate cache implementation ( cacheClass ). Cause: e, e);}}private Constructor? extends Cache getBaseCacheConstructor(Class? extends Cache cacheClass) {try {return cacheClass.getConstructor(String.class);} catch (Exception e) {throw new RuntimeException(Invalid base cache implementation ( cacheClass ). Base cache implementations must have a constructor that takes a String id as a parameter. Cause: e, e);}}private Cache newCacheDecoratorInstance(Class? extends Cache cacheClass, Cache base) {Constructor? extends Cache cacheConstructor getCacheDecoratorConstructor(cacheClass);try {return cacheConstructor.newInstance(base);} catch (Exception e) {throw new RuntimeException(Could not instantiate cache decorator ( cacheClass ). Cause: e, e);}}private Constructor? extends Cache getCacheDecoratorConstructor(Class? extends Cache cacheClass) {try {return cacheClass.getConstructor(Cache.class);} catch (Exception e) {throw new RuntimeException(Invalid cache decorator ( cacheClass ). Cache decorators must have a constructor that takes a Cache instance as a parameter. Cause: e, e);}}
}3.6.2 映射构建器助手 MapperBuilderAssistant.java package com.lino.mybatis.builder;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;/*** description: 映射构建器助手建造者*/
public class MapperBuilderAssistant extends BaseBuilder {private String currentNamespace;private String resource;private Cache currentCache;public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);this.resource resource;}public ResultMapping buildResultMapping(Class? resultType, String property, String column, ListResultFlag flags) {Class? javaTypeClass resolveResultJavaType(resultType, property, null);TypeHandler? typeHandlerInstance resolveTypeHandler(javaTypeClass, null);ResultMapping.Builder builder new ResultMapping.Builder(configuration, property, column, javaTypeClass);builder.typeHandler(typeHandlerInstance);builder.flags(flags);return builder.build();}private Class? resolveResultJavaType(Class? resultType, String property, Class? javaType) {if (javaType null property ! null) {try {MetaClass metaResultType MetaClass.forClass(resultType);javaType metaResultType.getSetterType(property);} catch (Exception ignore) {}}if (javaType null) {javaType Object.class;}return javaType;}public String getCurrentNamespace() {return currentNamespace;}public void setCurrentNamespace(String currentNamespace) {this.currentNamespace currentNamespace;}public String applyCurrentNamespace(String base, boolean isReference) {if (base null) {return null;}if (isReference) {if (base.contains(.)) {return base;}} else {if (base.startsWith(currentNamespace .)) {return base;}if (base.contains(.)) {throw new RuntimeException(Dots are not allowed in element names, please remove it from base);}}return currentNamespace . base;}/*** 添加映射器语句*/public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,Class? parameterType, String resultMap, Class? resultType,boolean flushCache, boolean useCache,KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {// 给id加上namespace前缀com.lino.mybatis.test.dao.IUserDao.queryUserInfoByIdid applyCurrentNamespace(id, false);// 是否时select语句boolean isSelect sqlCommandType SqlCommandType.SELECT;MappedStatement.Builder statementBuilder new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);statementBuilder.resource(resource);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);// 结果映射 给 MappedStatement#resultMapssetStatementResultMap(resultMap, resultType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement statementBuilder.build();// 映射语句信息建造完存放到配置项中configuration.addMappedStatement(statement);return statement;}private void setStatementCache(boolean isSelect, boolean flushCache, boolean useCache, Cache cache, MappedStatement.Builder statementBuilder) {flushCache valueOrDefault(flushCache, !isSelect);useCache valueOrDefault(useCache, !isSelect);statementBuilder.flushCacheRequired(flushCache);statementBuilder.useCache(useCache);statementBuilder.cache(cache);}private void setStatementResultMap(String resultMap, Class? resultType, MappedStatement.Builder statementBuilder) {// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果所以这里返回的是 nullresultMap applyCurrentNamespace(resultMap, true);ListResultMap resultMaps new ArrayList();if (resultMap ! null) {String[] resultMapNames resultMap.split(,);for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* select idqueryUserInfoById resultTypecom.lino.mybatis.test.po.User* 使用 resultType 的情况下Mybatis 会自动创建一个 ResultMap基于属性名称映射列到 JavaBean 的属性上。*/else if (resultType ! null) {ResultMap.Builder inlineResultMapBuilder new ResultMap.Builder(configuration, statementBuilder.id() -Inline, resultType, new ArrayList());resultMaps.add(inlineResultMapBuilder.build());}statementBuilder.resultMaps(resultMaps);}public ResultMap addResultMap(String id, Class? type, ListResultMapping resultMappings) {// 补全ID全路径如com.lino.mybatis.test.dao.IActivityDao activityMapid applyCurrentNamespace(id, false);ResultMap.Builder inlineResultMapBuilder new ResultMap.Builder(configuration, id, type, resultMappings);ResultMap resultMap inlineResultMapBuilder.build();configuration.addResultMap(resultMap);return resultMap;}public Cache useNewCache(Class? extends Cache typeClass,Class? extends Cache evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {// 判断为null,则用默认值typeClass valueOrDefault(typeClass, PerpetualCache.class);evictionClass valueOrDefault(evictionClass, FifoCache.class);// 建造者模式构建 cache [currentNamespacecom.lino.mybatis.test.dao.IActivityDao]Cache cache new CacheBuilder(currentNamespace).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();// 添加缓存configuration.addCache(cache);currentCache cache;return cache;}private T T valueOrDefault(T value, T defaultValue) {return value null ? defaultValue : value;}
}3.7 XML配置构建器
3.7.1 构建器基类 BaseBuilder.java package com.lino.mybatis.builder;import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;/*** description: 构建器的基类建造者模式*/
public class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;...protected Boolean booleanValueOf(String value, Boolean defaultValue) {return value null ? defaultValue : Boolean.valueOf(value);}
}3.7.2 注解配置构建器 MapperAnnotationBuilder.java package com.lino.mybatis.builder.annotation;import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;/*** description: 注解配置构建器 Mapper*/
public class MapperAnnotationBuilder {.../*** 解析语句** param method 方法*/private void parseStatement(Method method) {Class? parameterTypeClass getParameterType(method);LanguageDriver languageDriver getLanguageDriver(method);SqlSource sqlSource getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource ! null) {final String mappedStatementId type.getName() . method.getName();SqlCommandType sqlCommandType getSqlCommandType(method);KeyGenerator keyGenerator;String keyProperty id;if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {keyGenerator configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator new NoKeyGenerator();}boolean isSelect sqlCommandType SqlCommandType.SELECT;String resultMapId null;if (isSelect) {resultMapId parseResultMap(method);}// 调用助手类assistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,parameterTypeClass,resultMapId,getReturnType(method),false,false,keyGenerator,keyProperty,languageDriver);}}...}3.7.3 XML语言构建器 XMLStatementBuilder.java package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;/*** description: XML语言构建器*/
public class XMLStatementBuilder extends BaseBuilder {.../*** 解析语句(select|insert|update|delete)* select* idselectPerson* parameterTypeint* parameterMapdeprecated* resultTypehashmap* resultMappersonResultMap* flushCachefalse* useCachetrue* timeout10000* fetchSize256* statementTypePREPARED* resultSetTypeFORWARD_ONLY* SELECT * FROM PERSON WHERE ID #{id}* /select*/public void parseStatementNode() {String id element.attributeValue(id);// 参数类型String parameterType element.attributeValue(parameterType);Class? parameterTypeClass resolveAlias(parameterType);// 外部应用 resultMapString resultMap element.attributeValue(resultMap);// 结果类型String resultType element.attributeValue(resultType);Class? resultTypeClass resolveAlias(resultType);// 获取命令类型(select|insert|update|delete)String nodeName element.getName();SqlCommandType sqlCommandType SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect sqlCommandType SqlCommandType.SELECT;boolean flushCache Boolean.parseBoolean(element.attributeValue(flushCache, String.valueOf(!isSelect)));boolean useCache Boolean.parseBoolean(element.attributeValue(useCache, String.valueOf(isSelect)));// 获取默认语言驱动器Class? langClass configuration.getLanguageRegistry().getDefaultDriverClass();LanguageDriver langDriver configuration.getLanguageRegistry().getDriver(langClass);// 解析selectKeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// 解析成SqlSourceDynamicSqlSource/RawSqlSourceSqlSource sqlSource langDriver.createSqlSource(configuration, element, parameterTypeClass);// 属性标记【仅对insert有用】MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty element.attributeValue(keyProperty);KeyGenerator keyGenerator null;String keyStatementId id SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerators(keyStatementId)) {keyGenerator configuration.getKeyGenerator(keyStatementId);} else {keyGenerator configuration.isUseGeneratedKeys() SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :new NoKeyGenerator();}// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);}.../*** selectKey keyPropertyid orderAFTER resultTypelong* SELECT LAST_INSERT_ID()* /selectKey*/private void parseSelectKeyNode(String id, Element nodeToHandle, Class? parameterTypeClass, LanguageDriver langDriver) {String resultType nodeToHandle.attributeValue(resultType);Class? resultTypeClass resolveClass(resultType);boolean executeBefore BEFORE.equals(nodeToHandle.attributeValue(order, AFTER));String keyProperty nodeToHandle.attributeValue(keyProperty);// 默认String resultMap null;boolean flushCache false;boolean useCache false;KeyGenerator keyGenerator new NoKeyGenerator();// 解析成SqlSourceDynamicSqlSource/RawSqlSourceSqlSource sqlSource langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);SqlCommandType sqlCommandType SqlCommandType.SELECT;// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);// 给id加上namespace前缀id builderAssistant.applyCurrentNamespace(id, false);// 存放键值生成器配置MappedStatement keyStatement configuration.getMappedStatement(id);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));}}3.7.4 XML映射构建器 XMLMapperBuilder.java package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.builder.ResultMapResolver;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultFlag;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;/*** description: XML映射构建器*/
public class XMLMapperBuilder extends BaseBuilder {.../*** 配置mapper元素* mapper namespaceorg.mybatis.example.BlogMapper* select idselectBlog parameterTypeint resultTypeBlog* select * from Blog where id #{id}* /select* /mapper** param element 元素*/private void configurationElement(Element element) {// 1.配置namespaceString namespace element.attributeValue(namespace);if (.equals(namespace)) {throw new RuntimeException(Mappers namespace cannot be empty);}builderAssistant.setCurrentNamespace(namespace);// 2.配置cachecacheElement(element.element(cache));// 3.解析resultMapresultMapElement(element.elements(resultMap));// 4.配置select|insert|update|deletebuildStatementFromContext(element.elements(select), element.elements(insert), element.elements(update), element.elements(delete));}/*** cache evictionFIFO flushInterval600000 size512 readOnlytrue/*/private void cacheElement(Element context) {if (context null) {return;}// 基础配置信息String type context.attributeValue(type, PERPETUAL);Class? extends Cache typeClass typeAliasRegistry.resolveAlias(type);// 缓存队列 FIFOString eviction context.attributeValue(eviction, FIFO);Class? extends Cache evictionClass typeAliasRegistry.resolveAlias(eviction);Long flushInterval Long.valueOf(context.attributeValue(flushInterval));Integer size Integer.valueOf(context.attributeValue(size));boolean readWrite !Boolean.parseBoolean(context.attributeValue(readOnly, false));boolean blocking !Boolean.parseBoolean(context.attributeValue(blocking, false));// 解析额外属性信息property namecacheFile value/tmp/xxx-cache.tmp/ListElement elements context.elements();Properties props new Properties();for (Element element : elements) {props.setProperty(element.attributeValue(name), element.attributeValue(value));}// 构建缓存builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}...
}cacheElement 缓存标签的解析作用于 configurationElement 解析环节中。解析后创建 Cache 存放到 Configuration 配置项对应的属性值。同时创建出来的缓存会被记录到 MappedStatement 映射语句类的属性上便于在缓存执行器中使用。
3.7.5 XML配置构建器-全局缓存解析
settings!--全局缓存true/false--setting namecacheEnabled valuetrue/!--缓存级别SESSION/STATEMENT--setting namelocalCacheScope valueSTATEMENT/
/settings在 Config XML 中配置全局缓存为开启这个时候可以关闭一级缓存。
3.7.6 XML配置构建器 XMLConfigBuilder.java package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;/*** description: XML配置构建器建造者模式集成BaseBuilder*/
public class XMLConfigBuilder extends BaseBuilder {.../*** settings* !--缓存级别SESSION/STATEMENT--* setting namelocalCacheScope valueSESSION/* /settings*/private void settingElement(Element context) {if (context null) {return;}ListElement elements context.elements();Properties props new Properties();for (Element element : elements) {props.setProperty(element.attributeValue(name), element.attributeValue(value));}configuration.setCacheEnabled(booleanValueOf(props.getProperty(cacheEnabled), true));configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty(localCacheScope)));}...}解析全局配置的操作再结合 XMLConfigBuilder 配置构建器中对配置解析扩展添加 cacheEnabled 即可。这样就可以把是否开启二级缓存的操作保存配置项目。默认情况下二级缓存是关闭的。
四、测试二级缓存
4.1 修改配置
4.1.1 修改xml配置 mybatis-config-datasource.xml ?xml version1.0 encodingUTF-8?
!DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtd
configuration...settings!--全局缓存true/false--setting namecacheEnabled valuetrue/!--缓存级别SESSION/STATEMENT--setting namelocalCacheScope valueSTATEMENT//settings...
/configuration缓存的执行策略为二级缓存、一级缓存和数据库所以这类的缓存配置可以根据代码测试阶段调整为一级、二级交叉开启和关闭进行验证。
4.1.2 缓存策略配置 Activity_Mapper.xml ?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.lino.mybatis.test.dao.IActivityDaocache evictionFIFO flushInterval600000 size512 readOnlytrue/resultMap idactivityMap typecom.lino.mybatis.test.po.Activityid columnid propertyid/result columnactivity_id propertyactivityId/result columnactivity_name propertyactivityName/result columnactivity_desc propertyactivityDesc/result columncreate_time propertycreateTime/result columnupdate_time propertyupdateTime//resultMapselect idqueryActivityById parameterTypecom.lino.mybatis.test.po.Activity resultMapactivityMapSELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitytrim prefixwhere prefixOverridesAND | OR suffixOverridesandif testnull ! activityIdactivity_id #{activityId}/if/trim/select/mappercache 标签为二级缓存的使用策略你可以配置 FIFO、LRU 等不同的缓存策略。
4.2 单元测试 ApiTest.java Test
public void test_queryActivityById() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionReader reader Resources.getResourceAsReader(mybatis-config-datasource.xml);SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(reader);// 2.请求对象Activity activity new Activity();activity.setActivityId(100001L);// 3.第一组SqlSession// 3.1 开启sessionSqlSession sqlSession01 sqlSessionFactory.openSession();// 3.2 获取映射器对象IActivityDao dao01 sqlSession01.getMapper(IActivityDao.class);logger.info(测试结果01{}, JSON.toJSONString(dao01.queryActivityById(activity)));sqlSession01.close();// 4.第二组SqlSession// 4.1 开启sessionSqlSession sqlSession02 sqlSessionFactory.openSession();// 4.2 获取映射器对象IActivityDao dao02 sqlSession02.getMapper(IActivityDao.class);logger.info(测试结果02{}, JSON.toJSONString(dao02.queryActivityById(activity)));sqlSession02.close();
}在单元测试中分别两次开启 openSession 操作并在第一次开启会话查询数据后执行会话关闭。 因为实际代码实现无论是 commit、close 结束会话都会把一级缓存数据刷新到二级缓存所以两个方式都可以。 当会话关闭后开始执行第二次的会话开启验证数据是否从二级缓存中获取数据因为在二级缓存中添加了日志判断所以可以通过打印日志进行验证。 测试结果 10:43:34.690 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 899644639.
拦截SQLSELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id ?
10:43:34.700 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value100001
10:43:34.700 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果01{activityDesc:测试活动,activityId:100001,activityName:活动名,createTime:1628424890000,updateTime:1628424890000}
10:43:34.700 [main] INFO c.l.m.d.pooled.PooledDataSource - Returned connection 899644639 to pool.
10:43:34.700 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statementcom.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter{activityId:100001}
10:43:34.700 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 propertyactivityId propertyTypeclass java.lang.Long
10:43:34.700 [main] DEBUG c.lino.mybatis.executor.BaseExecutor - 二级缓存: [{activityDesc:测试活动,activityId:100001,activityName:活动名,createTime:1628424890000,updateTime:1628424890000}]
10:43:34.700 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果02{activityDesc:测试活动,activityId:100001,activityName:活动名,createTime:1628424890000,updateTime:1628424890000}从断点调试和打印的日志看此时框架已经可以在两次会话中完成数据的二次缓存使用。
五、总结二级缓存
可以从二级缓存的设计实现上看到 Mybatis 框架在这里运用了大量的装饰器模式如 CachingExecutor 执行器和 Cache 接口的各类实现。 这样的设计的好处可以在不破坏原有逻辑的前提下完成功能通过配置开关的自由开启使用。 虽然二级缓存在 Mybatis 框架中也是一个不错的设计但由于系统架构逐步从单体到分布式以后单个实例的数据缓存并没有太大的意义。 因为在分布式架构下用户的每次请求会分散到不同应用实例上那么单个缓存这个时候大概率就没法起到作用了。所以在使用 Mybatis 时通常并不会开启二级缓存而是使用 Redis 这样的框架来预热数据库数据使用。