这篇文章将为大家详细讲解有关mybatis核心流程的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。
我们先写个例子。首先要配置一个资源文件 app.properties,配置一些属性,比如环境变量。
# 环境配置
env=local
再配置 mybatis-config.xml,这是 mybatis 的配置文件,是配置 mybatis 的各种配置信息,主要有:属性 properties、全局设置 settings、别名 typeAliases、环境 environments、映射 mappers:
<?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>
<!-- autoMappingBehavior should be set in each test case -->
<!-- 读取资源文件-->
<properties resource="org/apache/ibatis/autoconstructor/app.properties"/>
<settings>
<!-- 开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
<!-- 开启驼峰式命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 别名配置 -->
<typeAliases>
<package name="org.apache.ibatis.autoconstructor"/>
</typeAliases>
<!-- 环境配置 -->
<environments default="${env}">
<environment id="local">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<!-- 此配置是基于内存连接的-->
<property name="url" value="jdbc:hsqldb:mem:automapping"/>
<property name="username" value="sa"/>
</dataSource>
</environment>
<environment id="dev">
<transactionManager type="JDBC">
<property name="" value=""/>
</transactionManager>
<dataSource type="UNPOOLED">
<property name="driver" value="org.hsqldb.jdbcDriver"/>
<!-- 此配置是基于内存连接的-->
<property name="url" value="jdbc:hsqldb:mem:automapping"/>
<property name="username" value="sa"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 扫描指定的映射文件 -->
<mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
</mappers>
</configuration>
接着配置映射文件 AutoConstructorMapper.xml,它就是写 SQL 的地方:
<?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="org.apache.ibatis.autoconstructor.AutoConstructorMapper">
<!--开启二级缓存-->
<cache/>
<!--<select id="selectOneById" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">-->
<select id="selectOneById" resultType="primitiveSubject">
SELECT * FROM subject WHERE id = #{id}
</select>
</mapper>
然后给出基本的 POJO 和 mapper 接口:
public class PrimitiveSubject implements Serializable {
private final int id;
private final String name;
private final int age;
private final int height;
private final int weight;
private final boolean active;
private final Date dt;
public PrimitiveSubject(final int id, final String name, final int age, final int height, final int weight, final boolean active, final Date dt) {
this.id = id;
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
this.active = active;
this.dt = dt;
}
@Override
public String toString() {
return "PrimitiveSubject{ hashcode="+ this.hashCode() + ", id=" + id + ", name='" + name + '\'' + ", age=" + age
+ ", height=" + height + ", weight=" + weight + ", active=" + active + ", dt=" + dt + '}';
}
}
/**
* mapper 接口
*/
public interface AutoConstructorMapper {
PrimitiveSubject selectOneById(int id);
}
初始化 SQL 数据 CreateDB.sql
DROP TABLE subject
IF EXISTS;
DROP TABLE extensive_subject
IF EXISTS;
CREATE TABLE subject (
id INT NOT NULL,
name VARCHAR(20),
age INT NOT NULL,
height INT,
weight INT,
active BIT,
dt TIMESTAMP
);
INSERT INTO subject VALUES
(1, 'a', 10, 100, 45, 1, CURRENT_TIMESTAMP),
(2, 'b', 10, NULL, 45, 1, CURRENT_TIMESTAMP),
(2, 'c', 10, NULL, NULL, 0, CURRENT_TIMESTAMP);
最后编写测试类,这个测试类中初始化了 SqlSessionFactory,同时装配了内存数据库;它通过 sqlSessionFactory 开启了一个 SqlSession,然后获取 AutoConstructorMapper 对象,执行了它的 selectOneById 方法:
class AutoConstructorTest {
private static SqlSessionFactory sqlSessionFactory;
@BeforeAll
static void setUp() throws Exception {
// create a SqlSessionFactory
try (
Reader reader = Resources
.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// populate in-memory database
BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
"org/apache/ibatis/autoconstructor/CreateDB.sql");
}
@Test
void selectOneById() {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 测试环境
Environment environment = sqlSessionFactory.getConfiguration().getEnvironment();
System.out.println("environment = " + environment.getId());
final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
PrimitiveSubject ps1 = mapper.selectOneById(1);
System.out.println("ps1 = " + ps1);
}
}
}
这样,一个简单的例子就编写完毕了。下面我们开始进入 mybatis 的源码中,探索下它的内部流程机制。
我们将它的源码分析分为以下几个流程:
解析 mybatis-config.xml 文件,构建 Configuration 配置类信息流程;
解析 mapper.xml 进行构建缓存、映射声明等流程;
创建 SqlSession 流程;
通过 SqlSession 获取 mapper 接口执行目标方法流程;
下面我们正式开始解析源码。
这个流程在上面的例子中的单元测试类代码中有体现,具体的相关代码如下:
SqlSessionFactory sqlSessionFactory;
// ...省略...
try (
Reader reader = Resources
.getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
// ...省略...
上面的逻辑是,加载 mybatis-config.xml 文件到一个输入流中,然后创建一个 SqlSessionFactoryBuilder 对象,进行构建出一个 SqlSessionFactory 实例,这个实例的生命周期非常长,它是随着应用程序的关闭而关闭的。
我们看下它的源码:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
// ...省略无关方法...
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 创建一个 XMLConfigBuilder 进行解析流,解析为一个 Configuration 实例
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
// ...省略无关方法...
/**
* 构建一个 SQLsession 工厂
* @param config
* @return
*/
public SqlSessionFactory build(Configuration config) {
// 创建一个默认的 SQLsession 工厂
return new DefaultSqlSessionFactory(config);
}
}
可以看到,上面的代码逻辑,主要是创建一个 XMLConfigBuilder 类型的对象,我们看下它的构造器
public XMLConfigBuilder(Reader reader, String environment, Properties props) {
this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
发现它会创建一个 Configuration 对象,关联到父类中。看下 Configuration 的构造器:
public Configuration() {
// 配置各种基础类的别名
// 事务管理器
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
// 数据源工厂
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
// 缓存类别名
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
// 日志类别名
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
// 动态代理别名
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
// xml 脚本解析器
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
可以看到,它的构造器方法中会注册一些基础配置的类的别名,这些别名一般是用在 xml 配置文件中的属性值,后续会根据别名来解析出对应的实际类型。
回过头来继续看 XMLConfigBuilder 的解析方法 parse() 方法,这个方法是把 mybatis 的 xml 文件解析成为一个 Configuration 类型,最后再创建一个 DefaultSqlSessionFactory 类型返回。org.apache.ibatis.builder.xml.XMLConfigBuilder#parse :
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 进行解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 解析 properties 属性
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
// 解析设置 setting
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
// 解析自定义日志
loadCustomLogImpl(settings);
// 解析类型别名
typeAliasesElement(root.evalNode("typeAliases"));
// 解析插件
pluginElement(root.evalNode("plugins"));
// 解析对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
// 解析对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析反射器工厂
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 设置配置元素
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析环境
environmentsElement(root.evalNode("environments"));
// 解析数据库 ID 提供者
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析映射文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
上面的代码也很好理解,主要是针对 mybatis-config.xml 文件中的各个标签元素进行解析:
解析 properties 属性配置;
解析 setting 属性配置;
解析 typeAliases 类型别名配置;
解析插件 plugins 配置;
解析 objectFactory 对象工厂配置;
解析 objectWrapperFactory 对象包装工厂配置;
解析 reflectorFactory 反射工厂配置;
解析 environments 环境配置;
解析 databaseIdProvider 数据库 ID 提供者配置;
解析 typeHandlers 类型处理器配置;
解析 mappers 映射文件配置。
这些解析内容中,mappers 解析最为重要,我们详细看下它的解析过程。
解析 mappers 的逻辑在 org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 方法中:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 解析 package 属性
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
// 解析 resource 属性
String resource = child.getStringAttribute("resource");
// URL 属性
String url = child.getStringAttribute("url");
// class 属性
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// resource 不为空,URL 和 class 为空
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// URL 不为空,resource 和 class 为空
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// class 不为空,resource 和 URL 为空
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
// 否则就抛异常
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
可以看到这里的逻辑是获取了 mappers 标签中子标签 package 和 mapper,获取它们的 name、url、class、resource 属性,进行加载解析对应的 mapper.xml 文件。
流程为:
如果 package 标签存在,就获取其 name 属性值,即包名,将它放入 configuration 配置中保存起来, 通过 MapperAnnotationBuilder 类进行解析;
如果 package 不存在,就获取 mapper 标签。
获取它们的 resource、url、class 属性,这里进行了判断,这三个属性只能存在一个;
其中 resource 和 url 是通过 XMLMapperBuilder 实例进行解析的;
class 属性的值也是会放入到 configuration 配置中进行解析并且保存起来,随后通过 MapperAnnotationBuilder 类进行解析。
我们这里主要看下 XMLMapperBuilder 类的解析流程。看下它的 parse() 方法,这个方法就是开始了对 mapper.xml 文件进行解析。org.apache.ibatis.builder.xml.XMLMapperBuilder#parse:
/**
* 执行解析 mapper.xml 文件
*/
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
// 配置 mapper 根元素
configurationElement(parser.evalNode("/mapper"));
// 保存资源路径
configuration.addLoadedResource(resource);
// 构建命令空间映射
bindMapperForNamespace();
}
// 解析待定的结果集映射
parsePendingResultMaps();
// 解析待定的缓存引用
parsePendingCacheRefs();
// 解析待定的 SQL 声明
parsePendingStatements();
}
这里执行了以下几个解析逻辑:
执行 configurationElement() 方法,解析 mapper 根元素;
保存资源路径到 configuration 实例中;
执行 bindMapperForNamespace() 方法,根据命名空间加载对应的映射接口;
执行 parsePendingResultMaps() 方法,解析待定的 ResultMap 结果集映射;
执行 parsePendingCacheRefs() 方法,解析待定的 CacheRef 缓存引用;
执行 parsePendingStatements(),解析待定的 Statement SQL 声明。
这主要的方法是 configurationElement(),我们看下它的逻辑 org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement:
private void configurationElement(XNode context) {
try {
// 构建命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 构建缓存引用 cache-ref
cacheRefElement(context.evalNode("cache-ref"));
// 构建二级缓存 cache
cacheElement(context.evalNode("cache"));
// 构建 parameterMap
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 构建 resultMap
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 构建 SQL 语句
sqlElement(context.evalNodes("/mapper/sql"));
// 构建 SQL 语句声明
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
它主要执行的逻辑是:
构建缓存引用 cache-ref 元素;
构建二级缓存 cache 元素;
构建 parameterMap 元素;
构建 resultMap 元素;
构建 SQL 元素;
构建 SQL 语句声明(解析 select|insert|update|delete 标签,这一步最为重要);
接着我们看下它的构建二级缓存的流程。它是在 org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement 方法中实现的:
/**
* 构建二级缓存 cache 元素
*
* @param context
*/
private void cacheElement(XNode context) {
if (context != null) {
// 配置默认的 cache 类型
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 过期策略
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 刷新时间
Long flushInterval = context.getLongAttribute("flushInterval");
// 缓存大小
Integer size = context.getIntAttribute("size");
// 是否只读,默认是 false,即
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 是否阻塞,为了解决缓存击穿问题(同一时刻出现大量的访问同一个数据的请求)
boolean blocking = context.getBooleanAttribute("blocking", false);
// 其他属性
Properties props = context.getChildrenAsProperties();
// 构建缓存
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
注意这里的 cache 标签,是在 mapper.xml 文件中声明的。它的逻辑:
获取 cache 标签的类型 type 属性值,默认为 PERPETUAL,它对应 PerpetualCache 类型;
获取过期策略 eviction 属性值,默认为 LRU 最近最少过期策略,它对应 LruCache 类型;
获取刷新时间 flushInterval 属性值;
获取缓存大小 size 属性值;
获取是否只读 readOnly 属性值,默认是 false,如果设置了 true,那么就需要 POJO 实现 Serializable 接口;
获取是否阻塞 blocking 属性值,这是用来解决缓存击穿问题的,稍后将构建缓存时会具体讲解;
获取以及其他属性;
通过调用 MapperBuilderAssistant 映射构建器辅助器的 useNewCache() 方法来构建缓存。
我们看下 MapperBuilderAssistant 映射构建器辅助器的 useNewCache() 方法,org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache:
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 缓存构建器
Cache cache = new CacheBuilder(currentNamespace)
// 这里默认使用 PerpetualCache 缓存类型实现,具体的缓存实现类
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
// 添加 LruCache 缓存装饰器
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
// 开始构建缓存
.build();
// 把缓存放入配置类中
configuration.addCache(cache);
currentCache = cache;
return cache;
}
这里又用到了 CacheBuilder 缓存构建器来构建缓存,,可以看到缓存使用 PerpetualCache 类型实现,并且添加了一个 添加 LruCache 缓存装饰器来装饰缓存,看下它的 build 方法 org.apache.ibatis.mapping.CacheBuilder#build:
/**
* 构建一个缓存
*
* @return
*/
public Cache build() {
// 设置默认实现类,和初始化的装饰器 LruCache
setDefaultImplementations();
// 通过反射创建一个 PerpetualCache 对象
Cache cache = newBaseCacheInstance(implementation, id);
// 设置缓存属性
setCacheProperties(cache);
// 不要为自定义的缓存应用装饰器
// issue #352, do not apply decorators to custom caches
if (PerpetualCache.class.equals(cache.getClass())) {
// 如果是 PerpetualCache 类型的缓存,那么就给它设置装饰器
for (Class<? extends Cache> decorator : decorators) {
// 创建一个缓存装饰器实例
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
// 设置其他标准的装饰器
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
/**
* 设置缓存的默认实现
*/
private void setDefaultImplementations() {
if (implementation == null) {
implementation = PerpetualCache.class;
if (decorators.isEmpty()) {
decorators.add(LruCache.class);
}
}
}
/**
* 设置标准的缓存装饰器
*
* @param cache
* @return
*/
private Cache setStandardDecorators(Cache cache) {
try {
// 获取缓存的元对象
MetaObject metaCache = SystemMetaObject.forObject(cache);
// 设置元数据的信息
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
// 根据清除间隔属性,设置定时刷新缓存的装饰器缓存 ScheduledCache
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
// 根据是否可读写属性,设置序列化缓存装饰器 SerializedCache
cache = new SerializedCache(cache);
}
// 设置日志缓存装饰器 LoggingCache
cache = new LoggingCache(cache);
// 设置同步缓存装饰器 SynchronizedCache
cache = new SynchronizedCache(cache);
if (blocking) {
// 根据是否阻塞,设置阻塞缓存装饰器
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
梳理下这里的逻辑:
执行 setDefaultImplementations() 方法,如果没有实现类,那就设置默认的实现类 PerpetualCache,添加装饰器 LruCache;
通过反射创建一个 Cache 实现类的实例;
如果缓存实例是 PerpetualCache 类型的,则遍历装饰器集合,通过反射创建装饰器实例,并且执行 setStandardDecorators() 方法为缓存实例设置其他标准的装饰器;这里的逻辑有:
获取缓存的元对象,这是 size 属性;
根据 flushInterval 刷新间隔属性,设置 ScheduledCache 定时刷新缓存的装饰器对缓存进行装饰;
根据 readWrite 是否可读写属性,设置 SerializedCache 序列化缓存装饰器对缓存进行装饰;
设置 LoggingCache 日志缓存装饰器对缓存进行装饰;
设置 SynchronizedCache 同步缓存装饰器对缓存进行装饰;
根据 blocking 是否阻塞属性,设置 BlockingCache 阻塞缓存装饰器对缓存进行装饰;
如果缓存实例不是 LoggingCache 类型,那就设置 LoggingCache 日志缓存装饰器对缓存进行装饰;
返回缓存实例。
可以看到这里是创建了二级缓存 Cache 接口实例,这里有很多 Cache 装饰器,下面我们深入其中研究下。
我们先看下 Cache 接口的类图:
可以看到 Cache 接口有多个实现。
上面构建缓存的流程中,我们看到了它首先会创建具体的真正存数据的缓存实例 PerpetualCache,看下它的实现:
/**
* 永久缓存,用于一级缓存
*
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private final String id;
/**
* 使用一个 hashmap 作为缓存
*/
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
它有两个属性,String 类型的 id 属性、和一个 HashMap 类型的 cache 属性,可以看到查询的数据会存储到这个 cache 属性中。
接着它会创建一个 LruCache 缓存对 PerpetualCache 实例进行包装,LruCache 的实现如下:
/**
* Lru (least recently used) cache decorator.
*
* @author Clinton Begin
*/
public class LruCache implements Cache {
private final Cache delegate;
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(final int size) {
// 重写 LinkedHashMap 的 removeEldestEntry() 方法,实现 LRU 算法
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key);
}
@Override
public Object getObject(Object key) {
// 这里获取 key 是为了让 key 保持最新,不至于被 LRU 清除掉
keyMap.get(key); // touch
return delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
keyMap.clear();
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
}
可以看到,它持有一个缓存实例 Cache 类型的 delegate 属性,这是一个委派的缓存实例;还有持有一个重写了 LinkedHashMap 类的 keyMap 属性,它重写了 removeEldestEntry() 方法,实现了 LRU 最近最少使用算法;同时还持有一个年级最长的 Object 类型的 key。
当有新的数据要放入缓存时,并且 keyMap 中的数据已经满了的时候,会把年级最长的缓存 key 删除掉,再存入新的数据。
接着看 ScheduledCache 定时刷新缓存装饰器:
public class ScheduledCache implements Cache {
private final Cache delegate;
protected long clearInterval;
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = TimeUnit.HOURS.toMillis(1);
this.lastClear = System.currentTimeMillis();
}
public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
clearWhenStale();
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
clearWhenStale();
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
return clearWhenStale() ? null : delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
clearWhenStale();
return delegate.removeObject(key);
}
@Override
public void clear() {
lastClear = System.currentTimeMillis();
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
}
这个类同样也是持有一个委派的 Cache 实例,并且它提供了一个 clearWhenStale() 方法。这个方法会根据当前时间、上次清理的时间,与配置的刷新的间隔时间进行判断,是否需要清理缓存。与当前时间,在获取缓存数据、保存缓存数据、移除缓存数据、查询缓存数据数量的时候进行调用。
接着看 SerializedCache 类:
public class SerializedCache implements Cache {
private final Cache delegate;
public SerializedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
return object == null ? null : deserialize((byte[]) object);
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private byte[] serialize(Serializable value) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
private Serializable deserialize(byte[] value) {
Serializable result;
try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis)) {
result = (Serializable) ois.readObject();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
}
public static class CustomObjectInputStream extends ObjectInputStream {
public CustomObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
return Resources.classForName(desc.getName());
}
}
}
它是一个序列化缓存装饰器,用于在保存数据时,把数据序列化成 byte[] 数组,然后把 byte[] 数组保存到委派的缓存实例中去,在查询数据时,再把查询出来的数据反序列化为对应的对象。这里要求保存的数据类要实现 Serializable 接口。
接着看 LoggingCache 类型:
public class LoggingCache implements Cache {
private final Log log;
private final Cache delegate;
protected int requests = 0;
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private double getHitRatio() {
return (double) hits / (double) requests;
}
}
这个缓存装饰器的功能就是在查询缓存的时候打印日志,会根据缓存的请求次数与实际命中的次数计算出的命中率,并且打印出来。
接着看 SynchronizedCache 类:
public class SynchronizedCache implements Cache {
private final Cache delegate;
public SynchronizedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public synchronized int getSize() {
return delegate.getSize();
}
@Override
public synchronized void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public synchronized Object getObject(Object key) {
return delegate.getObject(key);
}
@Override
public synchronized Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public synchronized void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
}
它是一个实现同步功能的缓存装饰器,在调用查询缓存、保存缓存、删除缓存、清空缓存方法时进行同步,防止多线程同时操作。
我们看最后一个缓存装饰器 BlockingCache:
/**
* 一个简单的阻塞装饰器。
* 一个简单的低效的 EhCache's BlockingCache 装饰器。当元素不存在缓存中的时候,它设置一个锁。
* 这样其他线程将会等待,直到元素被填充,而不是直接访问数据库。
* 本质上,如果使用不当,它将会造成死锁。
*
* <p>Simple blocking decorator
*
* <p>Simple and inefficient version of EhCache's BlockingCache decorator.
* It sets a lock over a cache key when the element is not found in cache.
* This way, other threads will wait until this element is filled instead of hitting the database.
*
* <p>By its nature, this implementation can cause deadlock when used incorrecly.
*
* @author Eduardo Macarron
*
*/
public class BlockingCache implements Cache {
private long timeout;
private final Cache delegate;
private final ConcurrentHashMap<Object, CountDownLatch> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
// 获取锁
acquireLock(key);
// 获取对象
Object value = delegate.getObject(key);
if (value != null) {
// 获取的数据不为空,释放锁
releaseLock(key);
}
// 如果 value 为空,则一直不释放锁,让其他查询此 key 的线程永久阻塞,直到该 key 对应的 value 被添加到缓存中,或者调用删除 key 操作,才会释放锁。
// 这样的操作是用于解决缓存穿透问题,防止大量请求访问一个目前不存在的数据
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
@Override
public void clear() {
delegate.clear();
}
private void acquireLock(Object key) {
// 创建一个倒计时闭锁
CountDownLatch newLatch = new CountDownLatch(1);
while (true) {
// 根据给定的 key,放入对应的闭锁
// 如果 key 对应的闭锁不存在,则放入闭锁,如果存在则不放入,返回以前的值
CountDownLatch latch = locks.putIfAbsent(key, newLatch);
if (latch == null) {
// latch 为 null 说明放入成功,则退出
break;
}
// latch 不为空,说已经有线程放入了 key 对应的闭锁,那就让闭锁阻塞 await,直到闭锁被放入它的线程解锁
try {
if (timeout > 0) {
boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException(
"Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} else {
latch.await();
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
}
}
/**
* 释放锁,它会在保存对象、查询到对象、移除对象时进行调用
*
* @param key
*/
private void releaseLock(Object key) {
// 释放一个锁
CountDownLatch latch = locks.remove(key);
if (latch == null) {
throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
}
// 倒计时
latch.countDown();
}
public long getTimeout() {
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
}
这个类是借助了 CountDownLatch 闭锁实现了先阻塞操作。当一个线程尝试获取缓存数据时,会创建一个 CountDownLatch,然后再去获取数据,当获取的数据不为空,就把这个 CountDownLatch 删除,否则不删除闭锁,返回空数据。
这样其他线程获取相同 key 对应的缓存时,会拿到这个 CountDownLatch,然后调用它的 await() 方法,该线程就会被阻塞起来,直到这个 CountDownLatch 执行了 countDown() 方法。
当 key 对应的数据被获取到、被删除、被重新填入时,会调用到 CountDownLatch 的 countDown() 方法,唤醒其他被该闭锁阻塞的线程。
这样做的目的是为了防止缓存击穿。在一个 session 当访问一个数据库中一直不存在的数据时,会触发一次数据库查询,此时当 session 还没有提交事务时,此时出现了大量的 session 也是查询该 key 对应的数据,这样就会导致它们都会查询数据库,可想而知,后来这些 session 的查询数据库行为是无效的,而且如果此时 session 过多,可能会打死数据库。
为了避免这样的情况,为一个 key 增加一个闭锁,阻塞那些获取该数据的线程,直到数据被填充或释放锁才能被唤醒。
这样的做是比较低效的,容易引发死锁,比如一个线程如果一直访问缓存中不存在,并且数据库中也不存在的数据时,会创建一个闭锁,查询数据结束也不会释放锁。其他获取该 key 数据的线程访问时将会永久的阻塞,严重的消耗的系统资源。
这个类一般是不用的,cache 元素中的 block 属性默认是 false。
上述就是缓存装饰器的全部的介绍了,上面的这些缓存装饰器是使用了适配器模式,如下图:
这样设计的好处是,根据各个功能设计出各个装饰器,让它们各司其职。
接着看构建 SQLStatement 逻辑,它通过调用 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法来执行。
org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)
/**
* 从上下文构建状态
*
* @param list
*/
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
// 遍历所有的 select、insert、update、delete 的语句
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 解析 SQL 语句
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
// 添加不完整的声明
configuration.addIncompleteStatement(statementParser);
}
}
}
可以看到,这里获取了 select|insert|update|delete 这些元素,然后遍历,通过创建一个 XMLStatementBuilder 类,调用了它的 parseStatementNode() 方法来进行解析,说明一个 select|insert|update|delete 语句对应着一个 XMLStatement,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode:
/**
* 解析增删改查 SQL 语句声明,一个增删改查 SQL 语句就对应一个 MappedStatement
*/
public void parseStatementNode() {
// SQL 的 ID 属性
String id = context.getStringAttribute("id");
// 数据库 ID
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 节点名称
String nodeName = context.getNode().getNodeName();
// 根据节点名称解析 SQL 的类型:增删改查
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
// 是否为查询类型
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 是否刷新缓存,除了 select 类型的 SQL 预计,执行的时候都会刷新缓存
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 是否使用缓存,默认不填写时是使用缓存的,如果是 select 类型,则默认是启用缓存
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
// 结果排序,false
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// 解析 includes
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析参数类型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 解析语言驱动
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 解析查询寻的 key
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// 解析 selectKey
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 创建数据源
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// 声明类型,默认是 PREPARED 类型,预装配模式
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
// fetchSize
Integer fetchSize = context.getIntAttribute("fetchSize");
// 超时属性
Integer timeout = context.getIntAttribute("timeout");
// 参数映射
String parameterMap = context.getStringAttribute("parameterMap");
// 结果类型
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
// 结果映射
String resultMap = context.getStringAttribute("resultMap");
// 结果集类型
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
// key 属性
String keyProperty = context.getStringAttribute("keyProperty");
// key 列
String keyColumn = context.getStringAttribute("keyColumn");
// 结果集
String resultSets = context.getStringAttribute("resultSets");
// 构建映射声明对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
可以看到它的逻辑:
获取元素的 id 属性、 databaseId 属性;
根据节点名称解析 SQL 命令类型(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH);
获取元素的是否查询类型 isSelect、是否刷新缓存 isSelect、是否使用缓存 isSelect、是否对结果排序 resultOrdered;
解析 include 元素节点;
解析元素的 parameterType 属性、解析语言驱动 lang 属性、解析 selectKey;
创建 keyGenerator;
创建数据源 sqlSource;
解析 StatementType 类型,默认是 PREPARED 类型;
获取 fetchSize、timeout 超时属性、parameterMap 参数映射、resultType 结果类型、resultMap 结果集、resultSetType 结果集类型、
获取元素的 keyProperty 属性、keyColumn、resultSets
通过 MapperBuilderAssistant 映射构建器辅助器调用 addMappedStatement() 方法,创建并添加映射 Statement。
我们看下 org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement() 方法:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 解析声明 ID
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 开始构建一个映射声明
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
// 获取声明参数映射
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
// 把声明对象放入 configuration 中
configuration.addMappedStatement(statement);
return statement;
}
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
// 格式为:命令空间 + "." + base
return currentNamespace + "." + base;
}
这里的逻辑:
根据命令空间以及元素 ID 生成一个 MappedStatement 的 ID 属性;
创建一个 MappedStatement.Builder 实例构建 MappedStatement 实例;
添加到 configuration 实例中,返回 MappedStatement 实例。
这个 MappedStatement 的生命周期是和 configuration 一样,也是和应用程序的生命周期一样。
这个方法是根据 mapper.xml 中的命名空间来注册对应的 Mapper 接口类,org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace:
private void bindMapperForNamespace() {
// 当前命令空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 绑定类型就是命名空间对应的接口类
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
// 保存命令空间
configuration.addLoadedResource("namespace:" + namespace);
// 保存映射,这里进行了注册
configuration.addMapper(boundType);
}
}
}
逻辑:
首先获取了命令空间值,然后加载这个类型,得到的就是对应的声明的 Mapper 接口;
保存命令空间到 Configuration 配置中;
把 Mapper 接口注册到 Configuration 中。
我们再看下 configuration.addMapper(boundType);
这个逻辑,org.apache.ibatis.session.Configuration#addMapper:
// org.apache.ibatis.session.Configuration#addMapper:
public <T> void addMapper(Class<T> type) {
// mapperRegistry 是 MapperRegistry 类型
mapperRegistry.addMapper(type);
}
里边又调用了 org.apache.ibatis.binding.MapperRegistry#addMapper() 方法:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 添加一个映射器代理工厂
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 映射注解构建器
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
我们看到了这里的逻辑:
把要注册的类保存到 Map<Class<?>, MapperProxyFactory<?>> 类型的 knownMappers
属性中,它的 key 为注册的类型,value 为 MapperProxyFactory 映射代理工厂类型实例;
创建一个 MapperAnnotationBuilder 映射注解解析器,对目标类型进行解析。
我们看下这个类:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这个类中维护目标接口类型信息、方法与映射方法执行器属性。
它提供了创建实例方法 newInstance(),通过 JDK 的动态代理对象创建一个目标接口的代理对象。
上面 JDK 动态代理对象时候,传入了一个 MapperProxy 类型的参数,它的实现为:
/**
* 方法代理器,实现了 JDK 动态代理的执行处理器 InvocationHandler 接口
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
/**
* 动态代理执行器的 invoke 方法
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
// 调用 MapperMethodInvoker 映射方法执行器
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
// It should be removed once the fix is backported to Java 8 or
// MyBatis drops Java 8 support. See gh-1929
// 从方法缓存中获取映射方法执行器
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
// 创建一个新的方法执行器,并放入 methodCache 缓存中
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
// 如果方法是一个接口的 default 方法,那就创建一个 DefaultMethodInvoker 类型
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
// 否则就创建普通的 PlainMethodInvoker 类型执行器
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
/**
* JDK 动态代理对象的的处理器方法
*
* @param proxy
* @param method
* @param args
* @param sqlSession
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 执行目标方法
return mapperMethod.execute(sqlSession, args);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
// 通过 MethodHandle 方法处理器,绑定代理对象,执行方法
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
再看下它的类图:
它实现了 InvocationHandler 接口的 invoke() 方法,里边主要的逻辑是:
调用 cachedInvoker() 方法,创建一个 MapperMethodInvoker;
先从 methodCache 缓存中获取,有的话直接返回;
methodCache 缓存没有的话,则创建一个 PlainMethodInvoker 类型的执行器,这个构造器会被传入一个 org.apache.ibatis.binding.MapperMethod 类型对象。
调用 MapperMethodInvoker 实例的 invoke() 执行目标方法,实际最终会执行 MapperMethod 实例的 execute() 方法。
我们看下 MapperMethod 类:
/**
* 映射方法
*
* @author Clinton Begin
* @author Eduardo Macarron
* @author Lasse Voss
* @author Kazuki Shimizu
*/
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// SQL 命令
this.command = new SqlCommand(config, mapperInterface, method);
// 方法签名
this.method = new MethodSignature(config, mapperInterface, method);
}
/**
* 执行方法
*
* @param sqlSession
* @param args
* @return
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 新增类型
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
// 修改
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// 删除
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// 查询
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
// 返回多条
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
// 返回 map
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
// 返回游标
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
// 刷新
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (!StatementType.CALLABLE.equals(ms.getStatementType())
&& void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
/**
* 查询多条记录
*
* @param sqlSession
* @param args
* @param <E>
* @return
*/
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
// 转换参数
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
// 有行绑定
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectCursor(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectCursor(command.getName(), param);
}
return result;
}
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
} else {
result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
}
return result;
}
// ...省略无关方法...
重点看下它 execute() 方法逻辑:
判断 SQL 执行类型:insert、update、delete、select;
根据执行类型最终都会调用 SqlSession 的对应方法,而 SqlSession 的对应方法内部最终会调用 Executor 的对应方法。
上面我们讲了解析 mybatis-config.xml 以及 mapper.xml 的流程,现在我们来看下获取一个 SqlSession 的流程。
从 1. 例子的单元测类中可以看到,它是通过 SqlSession sqlSession = sqlSessionFactory.openSession()
来获取一个 SqlSession,sqlSessionFactory.openSession 是 DefaultSqlSessionFactory 类型的,我们看下它的 openSession() 方法,org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession():
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
* 打开一个 session
*
* @param execType
* @param level
* @param autoCommit
* @return
*/
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);
// 创建一个默认的 DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 遇到异常关闭事务
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
/**
* 从环境信息中创建一个事务工厂
*
* @param environment
* @return
*/
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
// 创建默认的管理的事务工厂
return new ManagedTransactionFactory();
}
// 从环境中获取事务工厂
return environment.getTransactionFactory();
}
/**
* 关闭事务
*
* @param tx
*/
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
可以看到,它的主要流程为:
获取环境 Environment 信息;
获取一个 TransactionFactory 事务工厂实例;
通过事务工厂创建一个事务 Transaction 实例;
通过配置类创建一个 Executor 执行器;
创建一个 DefaultSqlSession 对象返回;
遇到异常关闭事务。
因为我们在 mybatis-config.xml 中配置了环境信息 environment,其中 transactionManager 元素的 type 为 JDBC ,所以 它会获取到的事务工厂为 JdbcTransactionFactory 类型。
然后通过它来创建了一个事务,org.apache.ibatis.transaction.TransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean):
/**
* Creates {@link JdbcTransaction} instances.
*
* @author Clinton Begin
*
* @see JdbcTransaction
*/
public class JdbcTransactionFactory implements TransactionFactory {
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
我们看下 newTransaction() 方法返回的 JdbcTransaction 类型:
它的实现:
/**
* {@link Transaction} that makes use of the JDBC commit and rollback facilities directly.
* It relies on the connection retrieved from the dataSource to manage the scope of the transaction.
* Delays connection retrieval until getConnection() is called.
* Ignores commit or rollback requests when autocommit is on.
*
* @author Clinton Begin
*
* @see JdbcTransactionFactory
*/
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed.
// Some databases start transactions with select statements
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true "
+ "before closing the connection. Cause: " + e);
}
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
这是一个jdbc 事务,里边提供了一些获取数据库连接、提交事务、回滚、关闭事务操作。
接着通过 configuration 创建一个执行器 Executor,org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType):
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
// 批量执行器
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
// 重用执行器
executor = new ReuseExecutor(this, transaction);
} else {
// 简单执行器
executor = new SimpleExecutor(this, transaction);
}
// 如果启用二级缓存
if (cacheEnabled) {
// 创建一个 CachingExecutor 类型,使用装饰器模式
executor = new CachingExecutor(executor);
}
// 添加拦截器,这里用户可以实现自定义的拦截器
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里的逻辑:
判断参数 ExecutorType 的类型,根据它的类型来创建不同的执行器,默认是 SIMPLE 类型;
ExecutorType.BATCH 类型,则创建 BatchExecutor 执行器;
ExecutorType.REUSE 类型,则创建 ReuseExecutor 执行器;
否则创建 SimpleExecutor 执行器;
如果启用了二级缓存,则创建 CachingExecutor 缓存执行器来包装上述执行器。默认是启用二级缓存;
为添加拦截器,这里用户可以实现自定义的拦截器;
返回执行器。
我们看下执行器 Executor 的类图:
可以看到,Executor 的继承类图,CachingExecutor 是一个装饰器,里边维护了一个真正的执行器,它默认实现的 SimpleExecutor 类型。
我们先看下 BaseExecutor 类的实现如下:
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
/**
* 本地缓存,一级缓存
*/
protected PerpetualCache localCache;
/**
* 本地输出参数缓存
*/
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
// 这是干啥的?
this.deferredLoads = new ConcurrentLinkedQueue<>();
// 本地
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
@Override
public Transaction getTransaction() {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 执行刷新声明
return doFlushStatements(isRollBack);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 绑定一个 SQL
BoundSql boundSql = ms.getBoundSql(parameter);
// 构建一个一级缓存 key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 清除本地缓存
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从一级缓存中获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 查询数据库
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
// TODO: 2020/9/18 引用队列?
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
deferredLoad.load();
} else {
// 这是干甚的?
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
/**
* 创建二级缓存 key
*
* @param ms
* @param parameterObject
* @param rowBounds
* @param boundSql
* @return
*/
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清除本地缓存
clearLocalCache();
// 刷新声明
flushStatements();
if (required) {
// 事务提交
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Apply a transaction timeout.
*
* @param statement
* a current statement
* @throws SQLException
* if a database access error occurs, this method is called on a closed <code>Statement</code>
* @since 3.4.0
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
/**
* 处理本地缓存输出参数
*
* @param ms
* @param key
* @param parameter
* @param boundSql
*/
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
// 处理 callable 类型,存储过程、存储函数
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
/**
* 从数据库获取
*
* @param ms
* @param parameter
* @param rowBounds
* @param resultHandler
* @param key
* @param boundSql
* @param <E>
* @return
* @throws SQLException
*/
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);
} finally {
// 清除本地缓存
localCache.removeObject(key);
}
// 放入本地缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
// 如果是调用存储过程、存储函数,则把参数放入缓存中
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
private static class DeferredLoad {
private final MetaObject resultObject;
private final String property;
private final Class<?> targetType;
private final CacheKey key;
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
private final ResultExtractor resultExtractor;
// issue #781
public DeferredLoad(MetaObject resultObject,
String property,
CacheKey key,
PerpetualCache localCache,
Configuration configuration,
Class<?> targetType) {
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
@SuppressWarnings("unchecked")
// we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}
这个类是抽象类,它实现了 Executor 接口的核心方法,留下一些抽象方法和模板方法交给了子类实现。这个类主要提供几个主要的属性:
PerpetualCache 类型的 localCache 属性,这是一个一级缓存,在同一个 sqlSession 查询相同接口数据时,提供缓存数据,避免查询相同查询语句和参数再次查询数据库。在查询时会从缓存中查找,以及保存缓存,在更新、删除都会清空缓存;
持有事务 Transaction 属性,用于在执行完一些事务提交、回滚、操作操作时,委派事务执行对应的逻辑;
默认的实际执行器是 SimpleExecutor 类型,看下它的实现:
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
// 获取配置类型
Configuration configuration = ms.getConfiguration();
// 获取 StatementHandler 处理器
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 创建 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
@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 处理器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
// 获取配置类型
Configuration configuration = ms.getConfiguration();
// 获取 StatementHandler 处理器
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
// 创建 Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
Cursor<E> cursor = handler.queryCursor(stmt);
stmt.closeOnCompletion();
return cursor;
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
return Collections.emptyList();
}
/**
* 准备一个 Statement
*
* @param handler
* @param statementLog
* @return
* @throws SQLException
*/
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取连接
Connection connection = getConnection(statementLog);
// 通过 StatementHandler 创建一个 Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 初始化参数
handler.parameterize(stmt);
return stmt;
}
}
这个类主要实现了 BaseExecutor 抽象的类的抽象的模板方法:doUpdate()、doQuery()、doQueryCursor()、doFlushStatements() 方法,这些方法主要的逻辑为:
获取 Configuration 配置类;
通过配置类 Configuration 的 newStatementHandler() 方法来创建 StatementHandler 类;
调用 prepareStatement() 方法,通过 StatementHandler 创建 Statement;
再通过 StatementHandler 执行对应的查询、更新相关方法。
在上述的 SimpleExecutor 类中,通过配置类 Configuration 的 newStatementHandler() 方法获取 StatementHandler 实例,我们先看下 StatementHandler 的类图:
我们看下它的实现,org.apache.ibatis.session.Configuration#newStatementHandler:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建一个 RoutingStatementHandler 路由的声明处理器
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 对 StatementHandler 应用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
它的逻辑:
创建一个 RoutingStatementHandler 路由的声明处理器;
对 StatementHandler 应用插件;
返回 statementHandler。
继续看下 RoutingStatementHandler 这个类:
public class RoutingStatementHandler implements StatementHandler {
/**
* 关联一个真正的 RoutingStatementHandler
*/
private final StatementHandler delegate;
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);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}
可以看到,这个类实现了 StatementHandler 接口,并且根据 MappedStatement 获取 StatementType,创建对应的 StatementHandler,有:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。默认是会创建 PreparedStatementHandler 实例。
它的其他方法都是使用委派的 StatementHandler 实例去执行,比如 prepare()、parameterize()、batch()、update()、query()、queryCursor()、getBoundSql()、getParameterHandler() 方法。
我们看下实际的 PreparedStatementHandler 类:
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行更新
ps.execute();
// 获取更新的行数
int rows = ps.getUpdateCount();
// 获取参数对象
Object parameterObject = boundSql.getParameterObject();
// 获取键生成器
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 后置处理器键,比如这里会针对 insert 语句,会设置插入之后的主键到参数对象上。
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 批量查询
ps.addBatch();
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 通过结果集处理器处理结果
return resultSetHandler.handleResultSets(ps);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询
ps.execute();
// 结果集处理器处理数据
return resultSetHandler.handleCursorResultSets(ps);
}
/**
* 初始化一个 Statement
*
* @param connection
* @return
* @throws SQLException
*/
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
//
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// 使用参数化对象进行设置参数
parameterHandler.setParameters((PreparedStatement) statement);
}
}
这个类就是实际真正执行目标 SQL 逻辑的类,它的一些方法逻辑:
update() 方法中,会通过 PreparedStatement 执行 SQL,然后获取参数对象、键生成器,对参数进行后置处理;
query()、queryCursor() 方法中,会通过 PreparedStatement 执行 SQL,然后通过结果集处理器对结果进行处理;
接着该看 CachingExecutor 类了:
/**
* 缓存执行器,装饰器模式,声明周期是一个 session
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class CachingExecutor implements Executor {
/**
* 委派的执行器
*/
private final Executor delegate;
/**
* 事务缓存管理器
*/
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public Transaction getTransaction() {
return delegate.getTransaction();
}
@Override
public void close(boolean forceRollback) {
try {
// issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 绑定 SQL
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 构建缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 获取二级缓存配置,它是从解析 mapper.xml 和 mapper 接口的 @CacheNamespace 注解得出来的
Cache cache = ms.getCache();
if (cache != null) {
// 是否需要刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存管理器,把缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 委派实际的 BaseExecutor 类型的查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return delegate.flushStatements();
}
@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();
}
}
}
private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");
}
}
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return delegate.isCached(ms, key);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
delegate.deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
// 查询之前,先清空二级缓存
tcm.clear(cache);
}
}
@Override
public void setExecutorWrapper(Executor executor) {
throw new UnsupportedOperationException("This method should not be called");
}
}
这个类是一个 Executor 的装饰器类,主要提供了二级缓存功能。它在查询数据、更新数据、提交、回滚操作时,会对二级缓存进行处理。
它的查询数据逻辑:
构建一个 CacheKey 类型的缓存 key;
从 MappedStatement 中获取二级缓存 Cache;
如果 cache 为空,则执行实际的委派执行器执行查询数据;
如果 cache 不为空,则先判断是否需要刷新缓存,如果需要刷新则通过 TransactionalCacheManager 清除缓存;然后从 TransactionalCacheManager 对象中获取 key 对应的二级缓存数据,缓存数据不为空直接返回,否则就继续执行实际委派执行器查询数据,然后把数据缓存到二级缓存中。
最后返回数据。
看下 TransactionalCacheManager 的实现:
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
// 获取 cache 对应的 TransactionalCache,然后把 key 和 value 存入
getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
// 遍历事务缓存
for (TransactionalCache txCache : transactionalCaches.values()) {
// 提交事务
txCache.commit();
}
}
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 如果 transactionalCaches 中的 cache 键没有对应的数据,则创建 TransactionalCache 对象
// 把 cache 对象当做 TransactionalCache 构造器的参数传入
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
这个类持有一个 key 是 Cache 类型,value 为 TransactionalCache 类型的 HashMap 类型属性 transactionalCaches,来保存事务缓存数据。
它的 getTransactionalCache() 方法中,参数 cache 是外部传入的二级缓存,当 transactionalCaches 没有这个 cache 对应的 value 时,就创建一个 TransactionalCache 类,并且把 cache 作为参数传入它的构造器中,保存起来。
它的结构为:
TransactionalCacheManager 这个个在保存缓存数据时,会调用 TransactionalCache 的 putObject() 方法,在提交事务、回滚事务的时候,会调用 TransactionalCache 的 commit() 和 rollback() 方法。
我们详细看下这个类。还记得上面 2.2.2 中我们讲过的缓存装饰器吗?没错这里又看见了一个缓存装饰器 TransactionalCache,它是实现如下:
/**
* The 2nd level cache transactional buffer.
* <p>
* This class holds all cache entries that are to be added to the 2nd level cache during a Session.
* Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
* Blocking cache support has been added. Therefore any get() that returns a cache miss
* will be followed by a put() so any lock associated with the key can be released.
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
/**
* 事务未提交前的保存的缓存数据
*/
private final Map<Object, Object> entriesToAddOnCommit;
/**
* 事务未提交前未命中的缓存数据
*/
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
// 把数据先临时保存起来
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
// 提交的时候清理二级缓存
delegate.clear();
}
// 提交的时候,刷新查询的数据,用于保存到二级缓存中
flushPendingEntries();
reset();
}
public void rollback() {
// 回滚时解析未命中的数据
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
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) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
这个类它也是有持有一个实际的委派的缓存,它默认是我们在 2.2.2 节中讲到的 SynchronizedCache 装饰过的二级缓存。
这个类还有个两个属性:Map<Object, Object> entriesToAddOnCommit 和 Set<Object> entriesMissedInCache,它们的作用是在 session 事务没有提交之前,临时保存缓存数据,等待真正的事务提交 commit() 时才会把缓存同步到二级缓存中,在回滚 rollback() 等时会清除未命中的缓存。
我们通过在它的 getObject() 方法中打断点,可以得到如下所示的结论。它是一个缓存装饰器,一层层的包装。
注意了 TransactionalCache 的声明周期不与委派的二级缓存一样,它是和一个 SqlSession 的声明一样的。而委派的二级缓存是和应用程序的生命周期一样的。
我们再看下为执行器应用插件的逻辑 interceptorChain.pluginAll(executor)
:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
这里会遍历所有的实现了 Interceptor 接口的拦截器类,调用它们的 plugin() 方法,对目标类进行拦截。实际上拦截器的调用一共有四个地方:
分别是:
创建 ParameterHandler 参数处理器时的拦截;
创建 ResultSetHandler 结果集处理器的拦截;
创建 StatementHandler 的拦截;
创建 Executor 的拦截。
我们可以实现自己的拦截器,根据自己的需求针对这四种类型进行拦截调用。比如可以针对 ParameterHandler 类型进行拦截,实现自动查询增加分页 SQL 的功能等等。
最后一步是根据已经创建好的 Executor 和 Configuration 来创建一个 DefaultSqlSession 实例。
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public DefaultSqlSession(Configuration configuration, Executor executor) {
this(configuration, executor, false);
}
@Override
public <T> T selectOne(String statement) {
return this.selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
@Override
public <T> Cursor<T> selectCursor(String statement) {
return selectCursor(statement, null);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return selectCursor(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从配置类中获取映射声明对象
// MappedStatement 声明周期很长,随着容器的关闭而关闭
MappedStatement ms = configuration.getMappedStatement(statement);
// 查询数据
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
select(statement, parameter, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
select(statement, null, RowBounds.DEFAULT, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public int delete(String statement) {
return update(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public void commit() {
commit(false);
}
@Override
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void rollback() {
rollback(false);
}
@Override
public void rollback(boolean force) {
try {
executor.rollback(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error rolling back transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public List<BatchResult> flushStatements() {
try {
return executor.flushStatements();
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error flushing statements. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
private void closeCursors() {
if (cursorList != null && !cursorList.isEmpty()) {
for (Cursor<?> cursor : cursorList) {
try {
cursor.close();
} catch (IOException e) {
throw ExceptionFactory.wrapException("Error closing cursor. Cause: " + e, e);
}
}
cursorList.clear();
}
}
@Override
public Configuration getConfiguration() {
return configuration;
}
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
@Override
public Connection getConnection() {
try {
return executor.getTransaction().getConnection();
} catch (SQLException e) {
throw ExceptionFactory.wrapException("Error getting a new connection. Cause: " + e, e);
}
}
@Override
public void clearCache() {
executor.clearLocalCache();
}
private <T> void registerCursor(Cursor<T> cursor) {
if (cursorList == null) {
cursorList = new ArrayList<>();
}
cursorList.add(cursor);
}
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
private Object wrapCollection(final Object object) {
return ParamNameResolver.wrapToMapIfCollection(object, null);
}
/**
* @deprecated Since 3.5.5
*/
@Deprecated
public static class StrictMap<V> extends HashMap<String, V> {
private static final long serialVersionUID = -5741767162221585340L;
@Override
public V get(Object key) {
if (!super.containsKey(key)) {
throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
}
return super.get(key);
}
}
}
这个类实现了 SqlSession 接口的增删改查方法,最终还是委派 Executor 去执行。
接下来,该看通过创建好的 SqlSession 来获取映射接口执行目标方法的流程了。
// 通过 SqlSession 获取映射接口
AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
// 执行目标方法
PrimitiveSubject ps1 = mapper.selectOneById(999);
从上面的分析,我们知道了 sqlSession 是 DefaultSqlSession 类型,它的 getMapper() 方法,我们在 2.3.5 中看到了它的实现,org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
它会通过配置类 Configuration 根据类型获取对应的 Mapper 类型,org.apache.ibatis.session.Configuration#getMapper:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
最后再调用 MapperRegistry 实例的 getMapper() 方法,org.apache.ibatis.binding.MapperRegistry#getMapper:
/**
* 获取映射器
*
* @param type
* @param sqlSession
* @param <T>
* @return
*/
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 映射器代理工厂获取代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
看到这里,我们就就比较熟悉了,在 2.2.4 节中讲了解析 mapper.xml 文件时,会根据 xml 中的命名空间来注册对应的 mapper 接口,会以一个 key 为目标接口类型,value 为 MapperProxyFactory 实例的形式保存到一个 HashMap 实例中。
这里就是获取除了目标类型对应的 MapperProxyFactory 类型,然后调用它的 newInstance() 方法,通过 JDK 动态代理创建代理实例类。
最后,用这个代理对象来执行目标方法。
我们在 org.apache.ibatis.executor.statement.PreparedStatementHandler#query 方法处,打个端点看下它的方法调用栈信息:
// 调用 PreparedStatementHandler 的 query 方法
query(Statement, ResultHandler):71, PreparedStatementHandler (org.apache.ibatis.executor.statement), PreparedStatementHandler.java
// 调用 RoutingStatementHandler 的 query 方法
query(Statement, ResultHandler):82, RoutingStatementHandler (org.apache.ibatis.executor.statement), RoutingStatementHandler.java
// 调用 SimpleExecutor 的 doQuery() 方法
doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql):69, SimpleExecutor (org.apache.ibatis.executor), SimpleExecutor.java
// 调用 BaseExecutor 的 queryFromDatabase() 方法
queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):381, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java
// 调用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):173, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):116, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
// 调用 DefaultSqlSession 的 select() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
// 调用 MapperMethod 类的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java
// 调用 MapperProxy 类的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java
// 调用 JDK 动态代理类的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source
// 单元测试类的查询方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略无关栈信息...
它的时序图:
sequenceDiagram
# 单元测试入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 单元测试方法
AutoConstructorTest->>$Proxy15:selectOneById()
# JDK代理对象
$Proxy15->>MapperProxy:invoke() 执行
# 代理查询
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()
# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()
# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()
# BaseExecutor
CachingExecutor->>BaseExecutor:query()
BaseExecutor->>BaseExecutor:queryFromDatabase()
BaseExecutor->>SimpleExecutor:doQuery()
# RoutingStatementHandler
SimpleExecutor->>RoutingStatementHandler:query()
RoutingStatementHandler->>PreparedStatementHandler:query()
我们再在查询二级缓逻辑处打断点,看下它的调用栈信息:
// 调用 PerpetualCache 的 getObject() 方法
getObject(Object):59, PerpetualCache (org.apache.ibatis.cache.impl), PerpetualCache.java
// 调用 LruCache 的 getObject() 方法
getObject(Object):75, LruCache (org.apache.ibatis.cache.decorators), LruCache.java
// 调用 SerializedCache 的 getObject() 方法
getObject(Object):63, SerializedCache (org.apache.ibatis.cache.decorators), SerializedCache.java
// 调用 LoggingCache 的 getObject() 方法
getObject(Object):55, LoggingCache (org.apache.ibatis.cache.decorators), LoggingCache.java
// 调用 SynchronizedCache 的 getObject() 方法
getObject(Object):48, SynchronizedCache (org.apache.ibatis.cache.decorators), SynchronizedCache.java
// 调用 TransactionalCache 的 getObject() 方法
getObject(Object):75, TransactionalCache (org.apache.ibatis.cache.decorators), TransactionalCache.java
// 调用 TransactionalCacheManager 的 getObject() 方法
getObject(Cache, CacheKey):35, TransactionalCacheManager (org.apache.ibatis.cache), TransactionalCacheManager.java
// 调用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):114, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
// 调用 DefaultSqlSession 的 selectList() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
// 调用 DefaultSqlSession 的 selectOne() 方法
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
// 调用 MapperMethod 的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java
// 调用 PlainMethodInvoker 的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java
// 调用 MapperProxy 的 invoke() 方法
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java
// 调用代理对象的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source
// 单元测试类的方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略无关栈信息...
画出二级缓存调用的时序图:
sequenceDiagram
# 单元测试入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 单元测试方法
AutoConstructorTest->>$Proxy15:selectOneById()
# JDK代理对象
$Proxy15->>MapperProxy:invoke() 执行
# 代理查询
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()
# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()
# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()
# 事务缓存管理器
CachingExecutor->>TransactionalCacheManager:getObject()
# 事务缓存装饰器
TransactionalCacheManager->>TransactionalCache:getObject()
# 同步缓存装饰器
TransactionalCache->>SynchronizedCache:getObject()
# 日志缓存装饰器
SynchronizedCache->>LoggingCache:getObject()
# 序列化装饰器
LoggingCache->>SerializedCache:getObject()
# Lru 缓存装饰器
SerializedCache->>LruCache:getObject()
# 实际的缓存
LruCache->>PerpetualCache:getObject()
这里的调用逻辑中,二级缓存的调用链可以配合着 2.2.2.9 的缓存小结图来阅读。
关于“mybatis核心流程的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。
原文链接:https://my.oschina.net/kaisesai/blog/4639306