归档
总说明
- 源码仓库: https://github.com/mybatis/mybatis-3
- 克隆:
git clone https://github.com/mybatis/mybatis-3.git
- 切分支(tag):
git checkout master
- JDK:
17
- Mapper 测试在
org.apache.ibatis.submitted.*
包下面
单元测试
- 参考:
org.apache.ibatis.submitted.extend.ExtendTest
@BeforeAll
static void setUp() throws Exception {
try (Reader reader = Resources.getResourceAsReader("org/apache/ibatis/submitted/extend/ExtendConfig.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); // ref: sign_m_110
}
}
@Test
void testExtend() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) { // ref: sign_m_210
ExtendMapper mapper = sqlSession.getMapper(ExtendMapper.class); // ref: sign_m_310
Child answer = mapper.selectChild(); // ref: sign_m_410
answer = mapper.selectChild(); // 测试:一级缓存关闭
} // sign_demo_010 需要第一次 SqlSession 提交,二级缓存才有数据。ref: sign_m_621
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
ExtendMapper mapper = sqlSession.getMapper(ExtendMapper.class);
Child answer = mapper.selectChild(); // 测试:二级缓存开启
}
}
extend\ExtendConfig.xml
<configuration>
<settings>
<setting name="localCacheScope" value="STATEMENT"/> <!-- sign_demo_110 关闭一级缓存 -->
<setting name="cacheEnabled" value="true"/> <!-- sign_demo_120 开启二级缓存 -->
</settings>
...
</configuration>
extend\Extend.xml
<mapper namespace="org.apache.ibatis.submitted.extend.ExtendMapper">
<cache/> <!-- sign_demo_210 配合开启二级缓存 -->
...
</mapper>
原理
创建 SqlSessionFactory
org.apache.ibatis.session.SqlSessionFactoryBuilder
// sign_m_110 构建工厂
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null); // ref: sign_m_111
}
// sign_m_111
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse()); // 使用解析的配置,构建工厂,ref: sign_m_112 | sign_m_120
} ... // catch finally
}
// sign_m_112 创建默认的工厂
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config); // ref: sign_c_210
}
org.apache.ibatis.builder.xml.XMLConfigBuilder
// 解析 <configuration> xml 配置文件
public class XMLConfigBuilder extends BaseBuilder {
// sign_m_120 解析配置
public Configuration parse() {
...
parseConfiguration(parser.evalNode("/configuration")); // ref: sign_m_121
return configuration;
}
// sign_m_121
private void parseConfiguration(XNode root) {
try {
// 先读取属性
propertiesElement(root.evalNode("properties")); // nop
Properties settings = settingsAsProperties(root.evalNode("settings")); // nop
loadCustomVfsImpl(settings); // nop
loadCustomLogImpl(settings); // nop
typeAliasesElement(root.evalNode("typeAliases")); // nop
pluginsElement(root.evalNode("plugins")); // nop
objectFactoryElement(root.evalNode("objectFactory")); // nop
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // nop
reflectorFactoryElement(root.evalNode("reflectorFactory")); // nop
settingsElement(settings); // nop
// 在 objectFactory 和 objectWrapperFactory 之后读取
environmentsElement(root.evalNode("environments")); // 配置环境
databaseIdProviderElement(root.evalNode("databaseIdProvider")); // nop
typeHandlersElement(root.evalNode("typeHandlers")); // nop
mappersElement(root.evalNode("mappers")); // 解析 mapper, ref: sign_m_122
} ... // catch
}
// sign_m_122 解析 mapper
private void mappersElement(XNode context) throws Exception {
...
for (XNode child : context.getChildren()) {
if ("package".equals(child.getName())) {
...
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource,
configuration.getSqlFragments());
mapperParser.parse(); // ref: sign_m_130
}
} else if (resource == null && url != null && mapperClass == null) {
...
} else if (resource == null && url == null && mapperClass != null) {
...
} ... // else { throw new BuilderException }
}
}
}
}
org.apache.ibatis.builder.xml.XMLMapperBuilder
// 解析 <mapper> xml 配置文件
public class XMLMapperBuilder extends BaseBuilder {
// sign_m_130
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper")); // ref: sign_m_131
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
configuration.parsePendingResultMaps(false);
configuration.parsePendingCacheRefs(false);
configuration.parsePendingStatements(false);
}
// sign_m_131
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
...
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap")); // resultMap 映射
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete")); // 具体 SQL 方法处理,ref: sign_m_132
} ... // catch
}
// sign_m_132
private void buildStatementFromContext(List<XNode> list) {
...
buildStatementFromContext(list, null); // ref: sign_m_133
}
// sign_m_133
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
// context 相当于 xml 里面的一个 select 方法声明
final XMLStatementBuilder statementParser = new XMLStatementBuilder(
configuration, builderAssistant, context, requiredDatabaseId
);
try {
statementParser.parseStatementNode(); // ref: sign_m_140
} ... // catch
}
}
}
org.apache.ibatis.builder.xml.XMLStatementBuilder
// 解析 <select> 等 xml 片段
public class XMLStatementBuilder extends BaseBuilder {
// sign_m_140
public void parseStatementNode() {
...
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
...
String parameterType = context.getStringAttribute("parameterType");
...
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
...
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
...
builderAssistant.addMappedStatement(...); // 将语句块添加到配置,ref: sign_m_150
}
}
org.apache.ibatis.builder.MapperBuilderAssistant
public class MapperBuilderAssistant extends BaseBuilder {
// sign_m_150
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, ...) {
...
MappedStatement.Builder statementBuilder = ...; // 填充 builder
...
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement); // 将解析的语句块添加到配置
return statement;
}
}
获取 SqlSession
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory
// sign_c_210
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
// sign_m_210
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); // ref: sign_m_211
}
// sign_m_211
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType); // ref: sign_m_220
return new DefaultSqlSession(configuration, executor, autoCommit); // 返回默认的 session, ref: sign_c_310
} ... // catch finally
}
}
org.apache.ibatis.session.Configuration
// sign_m_220
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType; // 最终值为: SIMPLE
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else { // 进入此逻辑
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) { // 进入此逻辑
executor = new CachingExecutor(executor);
}
return (Executor) interceptorChain.pluginAll(executor); // 组装‘拦截链’
}
获取 Mapper
org.apache.ibatis.session.defaults.DefaultSqlSession
// sign_c_310
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
// sign_m_310
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this); // ref: sign_m_320
}
}
org.apache.ibatis.session.Configuration
// sign_m_320
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession); // ref: sign_m_330
}
org.apache.ibatis.binding.MapperRegistry
// sign_m_330
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
...
try {
return mapperProxyFactory.newInstance(sqlSession); // 创建 Mapper 动态代理,ref: sign_m_340
} ... // catch
}
org.apache.ibatis.binding.MapperProxyFactory
// sign_m_340 创建 Mapper 动态代理
public T newInstance(SqlSession sqlSession) {
// 创建 JDK 代理用的 InvocationHandler, ref: sign_c_410
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy); // 创建 JDK 动态代理,ref: sign_m_341
}
// sign_m_341 创建 JDK 动态代理
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
执行 Mapper 方法
org.apache.ibatis.binding.MapperProxy
// sign_c_410
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
// sign_m_410 调用 Mapper 方法时,底层操作由此方法完成
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
...
return cachedInvoker(method) // ref: sign_m_411
.invoke(proxy, method, args, sqlSession); // ref: sign_m_412
} ... // catch
}
// sign_m_411
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (!m.isDefault()) { // 不是 default 方法,进入此逻辑
return new PlainMethodInvoker( // ref: sign_c_411
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()) // ref: sign_c_420 | sign_cm_420
);
}
... // default 方法处理
});
} ... // catch
}
// sign_c_411
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
// sign_m_412
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args); // ref: sign_m_420
}
}
}
org.apache.ibatis.binding.MapperMethod
// sign_c_420
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
// sign_cm_420
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
// sign_m_420 执行 SQL
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: // sqlSession.insert
case UPDATE: // sqlSession.update
case DELETE: // sqlSession.delete
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) { // sqlSession.select
} else if (method.returnsMany()) { // sqlSession.selectList
} else if (method.returnsMap()) { // sqlSession.selectMap
} else if (method.returnsCursor()) { // sqlSession.selectCursor
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param); // ref: sign_m_430
...
}
break;
...
}
...
return result;
}
}
org.apache.ibatis.session.defaults.DefaultSqlSession
- ref:
sign_c_310
- ref:
// sign_m_430
@Override
public <T> T selectOne(String statement, Object parameter) {
// 0 个结果上返回 null,多个结果抛出异常。
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0); // ref: sign_m_431
}
...
}
// sign_m_431
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT); // ref: sign_m_432
}
// sign_m_432
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER); // ref: sign_m_433
}
// sign_m_433
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
dirty |= ms.isDirtySelect();
return executor.query(ms, wrapCollection(parameter), rowBounds, handler); // ref: sign_m_440
} ... // catch finally
}
org.apache.ibatis.executor.CachingExecutor
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager(); // 缓存管理器
// sign_m_440
@Override
public <E> List<E> query(
MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler
) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // ref: sign_m_441
}
// sign_m_441
@Override
public <E> List<E> query(
MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql
) throws SQLException {
Cache cache = ms.getCache(); // Cache 链参考: sign_cc_010
if (cache != null) { // sign_bc_410 有开启二级缓存
...
if (ms.isUseCache() && resultHandler == null) {
...
List<E> list = (List<E>) tcm.getObject(cache, key); // sign_bc_411 从二级缓存中取,ref: sign_m_610
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // ref: sign_m_510
tcm.putObject(cache, key, list); // sign_bc_412 添加(只是暂存)到二级缓存,ref: sign_m_611
}
return list; // sign_bc_413 二级缓存数据不为空,直接返回
}
}
// delegate 为 SimpleExecutor 实例 (继承 BaseExecutor)
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // ref: sign_m_510
}
}
底层查询
org.apache.ibatis.executor.BaseExecutor
// sign_m_510
@Override
public <E> List<E> query(
MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
CacheKey key, BoundSql boundSql
) throws SQLException {
...
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else { // sign_bc_510 没有一级缓存进入此逻辑
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); // ref: sign_m_511
}
} finally {
queryStack--;
}
if (queryStack == 0) {
...
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // sign_bc_520 默认 SESSION 是启用一级缓存,设置为 STATEMENT 会清空,相当于禁用
}
}
return list;
}
// sign_m_511
private <E> List<E> queryFromDatabase(
MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql
) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); // ref: sign_m_520
} ... // finally
localCache.putObject(key, list); // sign_bc_530 添加到一级缓存
...
return list;
}
org.apache.ibatis.executor.SimpleExecutor
// sign_m_520
@Override
public <E> List<E> doQuery(
MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql
) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler( // 创建处理器并设置‘拦截链’,ref: sign_m_530
wrapper, ms, parameter, rowBounds, resultHandler, boundSql
);
stmt = prepareStatement(handler, ms.getStatementLog()); // ref: sign_m_521
return handler.query(stmt, resultHandler); // ref: sign_m_541
} finally {
closeStatement(stmt);
}
}
// sign_m_521
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog); // 获取 JDBC 连接
stmt = handler.prepare(connection, transaction.getTimeout()); // 获取 JDBC Statement, ref: sign_m_540
handler.parameterize(stmt); // 处理参数
return stmt;
}
org.apache.ibatis.session.Configuration
// sign_m_530 创建处理器并设置‘拦截链’
public StatementHandler newStatementHandler(
Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql
) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql); // ref: sign_cm_540
return (StatementHandler) interceptorChain.pluginAll(statementHandler); // 设置‘拦截链’
}
org.apache.ibatis.executor.statement.RoutingStatementHandler
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
// sign_cm_540
public RoutingStatementHandler(
Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql
) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
case PREPARED: // 进入此逻辑,ref: sign_cm_550
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
... // default Exception
}
}
// sign_m_540 获取 JDBC Statement
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout); // ref: sign_m_550
}
// sign_m_541 查询
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler); // ref: sign_m_561
}
}
org.apache.ibatis.executor.statement.BaseStatementHandler
// sign_cm_550
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, ..., BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
....
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(...); // 创建参数处理器,并设置‘拦截链’
this.resultSetHandler = configuration.newResultSetHandler(...); // 创建结果处理器,并设置‘拦截链’
}
// sign_m_550
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
...
Statement statement = null;
try {
statement = instantiateStatement(connection); // ref: sign_m_560
setStatementTimeout(statement, transactionTimeout); // 设置超时
setFetchSize(statement); // 设置拉取行数
return statement;
} ... // catch
}
org.apache.ibatis.executor.statement.PreparedStatementHandler
// sign_m_560 底层 JDBC 创建 Statement
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
...
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) { // 进入此逻辑
return connection.prepareStatement(sql); // JDBC 接口
} else {
return connection.prepareStatement(...); // JDBC 接口
}
}
// sign_m_561 底层 JDBC 执行查询
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); // 执行 SQL 查询
return resultSetHandler.handleResultSets(ps); // 处理查询结果(封装返回)
}
缓存
一级缓存
- 默认开启;关闭参考:
sign_demo_110
- 原理参考:
sign_bc_510 | sign_bc_520 | sign_bc_530
二级缓存
-
开启参考:
sign_demo_120 | sign_demo_210
-
使用注意点:
sign_demo_010
-
原理参考:
sign_bc_410 | sign_bc_411 | sign_bc_412 | sign_bc_413
-
org.apache.ibatis.cache.TransactionalCacheManager
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
// sign_m_610
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
// sign_m_611 暂存
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value); // 暂存,ref: sign_m_620
}
}
org.apache.ibatis.cache.decorators.TransactionalCache
public class TransactionalCache implements Cache {
private final Cache delegate; // Cache 链参考: sign_cc_010
private final Map<Object, Object> entriesToAddOnCommit; // 中间暂存
// sign_m_620 暂存
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
// sign_m_621 提交后,刷入最终的缓存
public void commit() {
...
flushPendingEntries(); // ref: sign_m_622
...
}
// sign_m_622
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue()); // 加入到最终的缓存
}
...
}
}
- Cache 链
// sign_cc_010
// (delegate)
TransactionalCache ->
SynchronizedCache -> LoggingCache -> SerializedCache -> LruCache -> PerpetualCache
// 上面相关实现原理较简单
// SynchronizedCache 使用 ReentrantLock 加锁
// LoggingCache 只是在 getObject() 方法记录和打印命中率
// SerializedCache 使用 JDK 序列化
// LruCache 使用 LinkedHashMap 实现 LRU
// PerpetualCache 使用 HashMap 记录
Spring 集成
- 注解扫描:
- 示例
@MapperScan("com.baomidou.mybatisplus.test.h2.mapper")
- 类
org.mybatis.spring.annotation.MapperScan
- 会引入注册类
org.mybatis.spring.annotation.MapperScannerRegistrar
- 会引入注册类
- 示例