文章目录
- 一、目标:二级缓存
- 二、设计:二级缓存
- 三、实现:二级缓存
-
- [3.1 工程结构](#3.1 工程结构)
- [3.2 二级缓存类图](#3.2 二级缓存类图)
- [3.3 二级缓存队列](#3.3 二级缓存队列)
-
- [3.3.1 FIFI缓存策略](#3.3.1 FIFI缓存策略)
- [3.3.2 事务缓存](#3.3.2 事务缓存)
- [3.3.3 事务管理](#3.3.3 事务管理)
- [3.3.4 修改一级缓存](#3.3.4 修改一级缓存)
- [3.4 缓存执行器](#3.4 缓存执行器)
-
- [3.4.1 执行器接口](#3.4.1 执行器接口)
- [3.4.2 执行器抽象基类](#3.4.2 执行器抽象基类)
- [3.4.3 缓存执行器](#3.4.3 缓存执行器)
- [3.5 修改配置](#3.5 修改配置)
-
- [3.5.1 映射器语句类](#3.5.1 映射器语句类)
- [3.5.2 配置项](#3.5.2 配置项)
- [3.5.3 默认SqlSession实现类](#3.5.3 默认SqlSession实现类)
- [3.6 映射构建器助手添加缓存](#3.6 映射构建器助手添加缓存)
-
- [3.6.1 缓存构建器](#3.6.1 缓存构建器)
- [3.6.2 映射构建器助手](#3.6.2 映射构建器助手)
- [3.7 XML配置构建器](#3.7 XML配置构建器)
-
- [3.7.1 构建器基类](#3.7.1 构建器基类)
- [3.7.2 注解配置构建器](#3.7.2 注解配置构建器)
- [3.7.3 XML语言构建器](#3.7.3 XML语言构建器)
- [3.7.4 XML映射构建器](#3.7.4 XML映射构建器)
- [3.7.5 XML配置构建器-全局缓存解析](#3.7.5 XML配置构建器-全局缓存解析)
- [3.7.6 XML配置构建器](#3.7.6 XML配置构建器)
- 四、测试:二级缓存
-
- [4.1 修改配置](#4.1 修改配置)
-
- [4.1.1 修改xml配置](#4.1.1 修改xml配置)
- [4.1.2 缓存策略配置](#4.1.2 缓存策略配置)
- [4.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 工程结构
java
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.xml
3.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
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 Deque<Object> keyList;
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<>();
this.size = 1024;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public void putObject(Object key, Object value) {
cycleKeyList(key);
delegate.putObject(key, value);
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyList.clear();
}
@Override
public 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
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 Map<Object, Object> entriesToAddOnCommit;
private Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
// delegate = FifoCache
this.delegate = delegate;
// 默认 commit 时不清缓存
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public void putObject(Object key, Object value) {
entriesToAddOnCommit.put(key, value);
}
@Override
public Object getObject(Object key) {
// key: CacheKey 拼装后的哈希码
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
return clearOnCommit ? null : object;
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
@Override
public 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.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
delegate.putObject(entry, null);
}
}
}
- TransactionalCache 事务缓存提供了对一级缓存的数据存放和使用的操作。
- 当一级缓存作用域范围的会话因为
commit、close
结束,则会调用到flushPengdingEntries
方法。 - 通过循环处理调用
delegate.putObject(entry.getKey(), entry.getValue())
,把数据刷新到二级缓存队列中。 - 另外
rollback
回滚方法则是一种清空缓存操作。
- 当一级缓存作用域范围的会话因为
3.3.3 事务管理
TransactionalCacheManager
java
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 Map<Cache, 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
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 Map<Object, Object> cache = new HashMap<>(16);
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@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 int getSize() {
return cache.size();
}
}
- 移除一级缓存日志打印
3.4 缓存执行器
3.4.1 执行器接口
Executor.java
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 List<E>
* @throws SQLException SQL异常
*/
<E> List<E> 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 List<E>
* @throws SQLException SQL异常
*/
<E> List<E> 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
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 {
...
@Override
public void setExecutorWrapper(Executor executor) {
this.wrapper = wrapper;
}
@Override
public 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
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);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return delegate.update(ms, parameter);
}
@Override
public <E> List<E> 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")
List<E> list = (List<E>) 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);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 1.获取绑定SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 2.创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@Override
public Transaction getTransaction() {
return delegate.getTransaction();
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
@Override
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
} finally {
if (required) {
tcm.rollback();
}
}
}
@Override
public void close(boolean forceRollback) {
try {
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public 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.<E>query
操作。 - 其实这个
delegate
就是 SimpleExecutor 实例化的对象,当缓存数据随着会话周期处理完后,则存放到 MappedStatement 所提供的 Cache 缓存队列中,也就是 FifoCache 先进先出缓存实现类。 - 另外关于缓存的流转会调用 TransactionalCacheManager 事务缓存管理器进行操作,从会话作用域范围,通过会话的结束,刷新提交到二级缓存或者清空处理。
3.5 修改配置
3.5.1 映射器语句类
MappedStatement
java
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 List<ResultMap> 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#getBoundSql
return 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(List<ResultMap> 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 List<ResultMap> 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
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 Map<String, MappedStatement> mappedStatements = new HashMap<>(16);
/**
* 缓存,存在Map里
*/
protected final Map<String, Cache> caches = new HashMap<>(16);
/**
* 结果映射,存在Map里
*/
protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);
/**
* 键值生成器,存在Map里
*/
protected final Map<String, 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 Set<String> 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(Class<T> type) {
mapperRegistry.addMapper(type);
}
public <T> T getMapper(Class<T> 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
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 {
...
@Override
public void close() {
executor.close(false);
}
...
}
3.6 映射构建器助手添加缓存
3.6.1 缓存构建器
CacheBuilder.java
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 List<Class<? 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.Entry<Object, 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
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, List<ResultFlag> 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.queryUserInfoById
id = 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#resultMaps
setStatementResultMap(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 返回结果,所以这里返回的是 null
resultMap = applyCurrentNamespace(resultMap, true);
List<ResultMap> resultMaps = new ArrayList<>();
if (resultMap != null) {
String[] resultMapNames = resultMap.split(",");
for (String resultMapName : resultMapNames) {
resultMaps.add(configuration.getResultMap(resultMapName.trim()));
}
}
/*
* 通常使用 resultType 即可满足大部分场景
* <select id="queryUserInfoById" resultType="com.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, List<ResultMapping> resultMappings) {
// 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMap
id = 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 [currentNamespace=com.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
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
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
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
* id="selectPerson"
* parameterType="int"
* parameterMap="deprecated"
* resultType="hashmap"
* resultMap="personResultMap"
* flushCache="false"
* useCache="true"
* timeout="10000"
* fetchSize="256"
* statementType="PREPARED"
* resultSetType="FORWARD_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);
// 外部应用 resultMap
String 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);
// 解析<selectKey>
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 解析成SqlSource,DynamicSqlSource/RawSqlSource
SqlSource 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 keyProperty="id" order="AFTER" resultType="long">
* 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();
// 解析成SqlSource,DynamicSqlSource/RawSqlSource
SqlSource 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
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 namespace="org.mybatis.example.BlogMapper">
* <select id="selectBlog" parameterType="int" resultType="Blog">
* select * from Blog where id = #{id}
* </select>
* </mapper>
*
* @param element 元素
*/
private void configurationElement(Element element) {
// 1.配置namespace
String namespace = element.attributeValue("namespace");
if ("".equals(namespace)) {
throw new RuntimeException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 2.配置cache
cacheElement(element.element("cache"));
// 3.解析resultMap
resultMapElement(element.elements("resultMap"));
// 4.配置select|insert|update|delete
buildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));
}
/**
* <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>
*/
private void cacheElement(Element context) {
if (context == null) {
return;
}
// 基础配置信息
String type = context.attributeValue("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存队列 FIFO
String 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 name="cacheFile" value="/tmp/xxx-cache.tmp"/>
List<Element> 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配置构建器-全局缓存解析
xml
<settings>
<!--全局缓存:true/false-->
<setting name="cacheEnabled" value="true"/>
<!--缓存级别:SESSION/STATEMENT-->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
- 在 Config XML 中配置全局缓存为开启,这个时候可以关闭一级缓存。
3.7.6 XML配置构建器
XMLConfigBuilder.java
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 name="localCacheScope" value="SESSION"/>
* </settings>
*/
private void settingElement(Element context) {
if (context == null) {
return;
}
List<Element> 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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
...
<settings>
<!--全局缓存:true/false-->
<setting name="cacheEnabled" value="true"/>
<!--缓存级别:SESSION/STATEMENT-->
<setting name="localCacheScope" value="STATEMENT"/>
</settings>
...
</configuration>
- 缓存的执行策略为二级缓存、一级缓存和数据库,所以这类的缓存配置可以根据代码测试阶段调整为一级、二级,交叉开启和关闭进行验证。
4.1.2 缓存策略配置
Activity_Mapper.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao">
<cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>
<resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity">
<id column="id" property="id"/>
<result column="activity_id" property="activityId"/>
<result column="activity_name" property="activityName"/>
<result column="activity_desc" property="activityDesc"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
</resultMap>
<select id="queryActivityById" parameterType="com.lino.mybatis.test.po.Activity" resultMap="activityMap">
SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">
<if test="null != activityId">
activity_id = #{activityId}
</if>
</trim>
</select>
</mapper>
cache
标签为二级缓存的使用策略,你可以配置FIFO、LRU
等不同的缓存策略。
4.2 单元测试
ApiTest.java
java
@Test
public void test_queryActivityById() throws IOException {
// 1.从SqlSessionFactory中获取SqlSession
Reader 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 开启session
SqlSession 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 开启session
SqlSession sqlSession02 = sqlSessionFactory.openSession();
// 4.2 获取映射器对象
IActivityDao dao02 = sqlSession02.getMapper(IActivityDao.class);
logger.info("测试结果02:{}", JSON.toJSONString(dao02.queryActivityById(activity)));
sqlSession02.close();
}
- 在单元测试中,分别两次开启
openSession
操作,并在第一次开启会话查询数据后执行会话关闭。- 因为实际代码实现,无论是
commit、close
结束会话,都会把一级缓存数据刷新到二级缓存,所以两个方式都可以。
- 因为实际代码实现,无论是
- 当会话关闭后开始执行第二次的会话开启,验证数据是否从二级缓存中获取数据,因为在二级缓存中添加了日志判断,所以可以通过打印日志进行验证。
测试结果
java
10:43:34.690 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 899644639.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_time
FROM activity
where activity_id = ?
10:43:34.700 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
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 - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
10:43:34.700 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class 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 这样的框架来预热数据库数据使用。