我正在参加「·启航方案」
MyBatis 是一款优异的耐久层框架,它支持自界说 SQL、存储过程以及高级映射。MyBatis 免除了几乎一切的 JDBC 代码以及设置参数和获取成果集的作业。MyBatis 能够经过简略的 XML 或注解来装备和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通旧式 Java 目标)为数据库中的记载。现在让咱们来探寻一下 Mybatis 的奥秘,源码比较多谨慎食用。
建立一个简略的 MyBatis 项目
咱们先来建立一个简略的 MyBatis
项目,以此来展开对 MyBatis
的剖析。
引进 maven 文件
<dependencies>
<!-- 引进 mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- 引进 mysql 衔接器 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<!-- 引进 lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
</dependencies>
创立装备文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="org.example.mybatis.mapper"/>
</mappers>
</configuration>
创立表
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='test';
创立对应的实体以及 Mapper
@Data
public class Test {
private Long id;
private String name;
}
public interface TestMapper {
@Select("select * from test where id = #{id}")
Test findById(Long id);
}
履行查询办法
public class Main {
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
TestMapper mapper = session.getMapper(TestMapper.class);
Test test = mapper.findById(1L);
System.out.println(test);
}
}
}
经过上面的例子能够看到运用 MyBatis
与数据库交互的过程是:
- 从
XML
文件中构建SqlSessionFactory
。 - 从
SqlSessionFactory
中获取SqlSession
。 - 运用
SqlSession
获取对应的Mapper
目标。 - 经过
Mapper
目标履行相应的办法。
MyBatis 履行流程
本节会简述一下 Mybatis
的中心目标以及 MyBatis
的履行流程。
中心目标简介
-
MapperProxy
: 用于生成Mapper
接口的署理类。 -
SqlSession
:作为MyBatis
作业的首要顶层API
,表明和数据库交互的会话,完结必要数据库增修改查功用。 -
Executor
:MyBatis
履行器,是MyBatis
调度的中心,担任SQL
句子的生成和查询缓存的维护。 -
StatementHandler
:封装了JDBC Statement
操作,担任对JDBC Statement
的操作。 -
ParameterHandler
:担任对用户传递的参数转化成JDBC Statement
所需求的参数。 -
ResultSetHandler
:担任将JDBC
回来的ResultSet
成果集目标转化成List
类型的调集。 -
TypeHandler
:担任java
数据类型和JDBC
数据类型之间的映射和转化。
履行流程
MyBatis
的履行流程图如下所示:
咱们先简述一下 MyBatis
的履行流程,后边将会详细介绍各个细节。
-
Mapper
调用增修改查操作时,实际上是调用了MapperProxy.invoke()
办法。 -
MapperProxy.invoke()
调用了SqlSession
供给的数据库交互的API
。 -
SqlSession
与把与数据库交互的作业交给Executor
去履行。 -
Executor
担任SQL
句子的生成和查询缓存的维护,将与数据库衔接的作业交给StatementHandler
。 -
StatementHandler
封装了JDBC Statement
操作,他经过ParameterHandler
将 用户传递的参数转化成JDBC Statement
所需求的参数,然后运用JDBC
真实的操作数据库,然后用ResultSetHandler
将JDBC
回来的ResultSet
成果集目标转化成List
类型的调集。
MyBatis 中心目标介绍
MapperProxy
经过 session.getMapper(TestMapper.class)
获取的类反编译的成果为:
/*
* Decompiled with CFR.
*
* Could not load the following classes:
* org.example.mybatis.entity.Test
* org.example.mybatis.mapper.TestMapper
*/
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import org.example.mybatis.entity.Test;
import org.example.mybatis.mapper.TestMapper;
public final class $Proxy3
extends Proxy
implements TestMapper {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy3(InvocationHandler invocationHandler) {
super(invocationHandler);
}
// ...省掉部分无关代码
public final Test findById(Long l) {
try {
return (Test)this.h.invoke(this, m3, new Object[]{l});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("org.example.mybatis.mapper.TestMapper").getMethod("findById", Class.forName("java.lang.Long"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
}
经过上面的代码咱们看到咱们在调用 findById
时真实调用的则是 return (Test)this.h.invoke(this, m3, new Object[]{l});
。我们能会疑惑 this.h
是什么,它其实是 Proxy
类的一个成员变量 protected InvocationHandler h;
。下面让咱们看一下这个 h
它到底是哪个类,这个署理类最终是经过 MapperProxyFactory.newInstance()
生成的。经过下面的代码能够看出 h
其实便是 MapperProxy
。
public class MapperProxyFactory<T> {
// 省掉...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
// 将 MapperProxy 作为 InvocationHandler 生成署理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
咱们来看一下 MapperProxy
的 invoke()
办法,它是最终调用了 MapperMethod.execute()
。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 经过 MapperMethod 来履行
return mapperMethod.execute(sqlSession, args);
}
而 MapperMethod.execute()
最终是经过 SqlSession
来履行真实的逻辑。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
// sqlSession 调用刺进办法
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
// 省掉部分代码
return result;
}
到这儿咱们就捋清了怎样获取 Mapper
以及到最终运用 SqlSession
与数据库交互的流程,下面咱们将看一下 SqlSession 是怎样和数据库交互的。
SqlSession
SqlSession
在 MyBatis
中是十分强壮的一个类。它包括了一切履行句子、提交或回滚业务以及获取映射器实例的办法。
DefaultSqlSession
DefaultSqlSession
是 SqlSession
的默许完结类。DefaultSqlSession
中运用到了战略形式,DefaultSqlSession
扮演了 Context
的人物,而将一切数据库相关的操作悉数封装到 Executor
接口完结中,并经过 executor
字段选择不同的 Executor
完结。
DefaultSqlSessionFactory
DefaultSqlSessionFactory
是一个详细工厂类,完结了 SqlSessionFactory
接口。DefaultSqlSessionFactory
首要供给了两种创立 DefaultSqlSession
的办法,一种办法经过数据源获取数据库衔接,并创立 Executor
目标以及 DefaultSqlSession
,该办法的详细完结如下所示:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取装备文件中的Environment目标
final Environment environment = configuration.getEnvironment();
// 获取TransactionFactory目标
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创立Transaction目标
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 依据装备创立Executor目标
final Executor executor = configuration.newExecutor(tx, execType);
// 创立DefaultSqlSession目标
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 封闭Transaction
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
另一种办法是用户供给数据库衔接目标 DefaultSqlSessionFactory
运用数据库衔接目标创立 Executor
目标以及 DefaultSqlSession
目标,详细完结如下:
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
// 获取当时衔接的业务是否为自动提交办法
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
// 当时数据库驱动供给的衔接不支持业务,则或许会抛 反常
autoCommit = true;
}
// 获取装备文件中的Environment目标
final Environment environment = configuration.getEnvironment();
// 获取TransactionFactory目标
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创立Transaction目标
final Transaction tx = transactionFactory.newTransaction(connection);
// 依据装备创立Executor目标
final Executor executor = configuration.newExecutor(tx, execType);
// 创立DefaultSqlSession目标
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
DefaultSqlSessionFactory
供给的一切 openSession()
办法重载都是依据上述两种办法创立 DefaultSqlSession
目标的,这儿不再赘述。
SqlSessionManager
SqlSessionManager
一起完结 SqlSession
接口和 SqlSessionFactory
接口,也就一起供给了 SqlSessionFactory
创立 SqlSession
以及 SqlSession
操纵数据库的功用。
SqlSessionManager
各个字段的意义如下:
// 底层封装的SqlSessionFactory目标
private final SqlSessionFactory sqlSessionFactory;
// localSqlSession中记载的SqlSession目标的署理目标
private final SqlSession sqlSessionProxy;
// ThreadLocal变量,记载一个与当时线程绑定的SqlSession目标
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
SqlSessionManager
和 DefaultSqlSessionFactory
的首要不同点是 SqlSessionManager
供给了两种形式:一种形式与 DefaultSqlSessionFactory
的行为相同,同一线程每次经过 SqlSessionManager
目标拜访数据库时,都会创立新的 DefaultSession
目标完结数据库操作;第二种形式是 SqlSessionManager
经过 localSqlSession
这个 ThreadLocal
,记载与当时线程绑定的 SqlSession
目标,供当时线程循环运用,从而防止在同一线程屡次创立 SqlSession
目标带来的功用损失。
首要看 SqlSessionManager
的结构办法,其结构办法都是私有的,假如要创立 SqlSessionManager
目标,需求调用其 newInstance()
办法。
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
// 运用动态署理的办法生成SqlSession的署理目标
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionManager(sqlSessionFactory);
}
SqlSessionManager.openSession()
办法以及其重载是直接经过调用其间底层封装的 SqlSessionFactory.openSession
办法来创立 SqlSession
目标的。
SqlSessionManager
中完结的 SqlSession
接口办法,例如 select*()、update()
等,都是直接调用 sqlSessionProxy
字段记载的 SqlSession
署理目标的相应办法完结的。在创立该署理目标时运用的 InvocationHandler
目标是 SqlSessionlnterceptor
目标,它是界说在 SqlSessionManager
中的内部类,invoke()
办法完结如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取当时线程绑定的SqlSession目标
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
// 调用真实的SqlSession目标,完结数据库的相关操作
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
try (SqlSession autoSqlSession = openSession()) {
try {
// 经过新建SqlSession目标完结数据库操作
final Object result = method.invoke(autoSqlSession, args);
// 提交业务
autoSqlSession.commit();
return result;
} catch (Throwable t) {
// 回滚业务
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
}
经过对 SqlSessionInterceptor
的剖析可知,第一种形式中新建的 SqlSession
在运用完结后会封闭。在第二种形式中与当时线程绑定的 SqlSession
目标需求先经过 SqlSessionManager.startManagedSession()
办法进行设置,详细完结如下:
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
当需求提交/回滚业务或是封闭 localSqlSession
中记载的 SqlSession
目标时,需求经过 SqlSessionManager.commit()、rollback()
以及 close()
办法完结,其间会先检测当时线程是否绑定 SqlSession
目标,假如未绑定则抛出反常 ,假如绑定了则调用该 SqlSession
目标的相应办法。
Executor
Executor
是 MyBatis
的中心接口之一,其间界说了数据库操作的根本办法。在实际运用中常常涉及的 SqlSession
接口的功用,都是依据 Executor
接口完结的。Executor
接口中界说的办法如下:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
// 履行insert、update、delete等操作
int update(MappedStatement ms, Object parameter) throws SQLException;
// 履行 select类型的SQL句子,回来位分为成果目标列表或游标目标
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
// 批量履行SQL句子
List<BatchResult> flushStatements() throws SQLException;
// 提交业务
void commit(boolean required) throws SQLException;
// 回滚业务
void rollback(boolean required) throws SQLException;
// 创立缓存顶用的CacheKey目标
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
// 依据CacheKey目标查找缓存
boolean isCached(MappedStatement ms, CacheKey key);
// 清空一级缓存
void clearLocalCache();
// 推迟加载一级缓存中的数据
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
// 获取业务目标
Transaction getTransaction();
// 封闭Executor目标
void close(boolean forceRollback);
// 检测Executor是否已封闭
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
BaseExecutor
BaseExecutor
是一个完结了 Executor
接口的抽象类,它完结了 Executor
接口的大部分办法,其间就运用了模板办法形式。BaseExecutor
首要供给了缓存办理和业务办理的根本功用,承继 BaseExecutor
的子类只要完结四个根本办法来完结数据库的相关操作即可,这四个办法分别 doUpdate()
办法、doQuery()
办法、doQueryCursor()
办法、doFlushStatement()
办法,其他的功用在 BaseExecutor
完结。
BaseExecutor
各个字段的意义如下:
// Transaction目标,完结业务的提交、回滚和封闭操作
protected Transaction transaction;
// 封装的Executor目标
protected Executor wrapper;
// 推迟加载队列
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 一级缓存,用于缓存该Executor目标查询成果集映射得到的成果目标
protected PerpetualCache localCache;
// 一级缓存,用于缓存输出类型的参数
protected PerpetualCache localOutputParameterCache;
// 用来记载嵌套查询的层数
protected int queryStack;
一级缓存
履行 select
句子查询数据库是最常用的功用,BaseExecutor.query()
办法会首要创立 CacheKey
目标,并依据该 CacheKey
目标查找一级缓存,假如缓存射中则回来缓存中记载的成果目标,假如缓存未射中则查询数据库得到成果集,之后将成果集映射成成果目标并保存到一级缓存中,一起回来成果目标。query()
办法的详细完结如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql目标
BoundSql boundSql = ms.getBoundSql(parameter);
// 创立CacheKey目标
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用query()的一个重载
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
这儿关注 BaseExecutor.createCacheKey()
办法创立的 CacheKey
目标由哪几部分构成,createCacheKey()
办法详细完结如下:
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
// 检测Executor是否封闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
// 增加MappedStatement的id
cacheKey.update(ms.getId());
// 增加offset
cacheKey.update(rowBounds.getOffset());
// 增加limit
cacheKey.update(rowBounds.getLimit());
// 增加SQL句子
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
// 获取实参并增加到CacheKey
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);
}
}
// 增加Environment的id
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
由此可知该 CacheKey
目标由 MappedStatement
的 id
、对应的 offse、limit、SQL
句子(包括 ?
占位符)、用户传递的实参以及 Environment.id
这五部分构成。
持续来看上述代码中调用的 query()
办法的另一重载的详细完结,该重载会依据前面创立的 CacheKey
目标查询一级缓存,假如缓存射中则将缓存中记载的成果目标回来,假如缓存未射中,则调用 doQuery()
办法完结数据库的查询操作并得到成果目标,之后将成果目标记载到一级缓存中。详细完结如下:
@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());
// 检测Executor是否封闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 非嵌套查询,而且<select>节点装备的flushCache特点为true时,才会清空一级缓存
clearLocalCache();
}
List<E> list;
try {
// 增加查询层数
queryStack++;
// 查询一级缓存
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 针对存储过程调用的处理,其功用是:在一级缓存射中时,获取缓存中保存的输出类型参数,并设置到用户传入的实参目标中
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 调用doQuery()办法完结数据库查询,并得到映射后的成果目标,
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 当时查询完结,层数削减
queryStack--;
}
// 推迟加载
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
上面介绍了 BaseExecutor
中缓存的第一种功用,也便是缓存查询得到的成果目标。除此之外,一级缓存还有第二个功用:前面在剖析嵌套查询时,假如一级缓存中缓存了嵌套查询的成果目标,则能够从一级缓存中直接加载该成果目标;假如一级缓存中记载的嵌套查询的成果目标并未彻底加载,则能够经过 DeferredLoad
完结相似推迟加载的功用。
Executor
中与上述功用直接相关的办法有两个,一个是 isCached()
办法担任检测是否缓存指定查询的成果目标,详细完结如下:
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
// 检测缓存中是否缓存了对应的目标
return localCache.getObject(key) != null;
}
另一个是 deferLoad()
办法,它担任创立 DeferredLoad
目标并将其增加到 deferredLoads
调集中,详细完结如下:
@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 deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
// 一级缓存中现已记载了指定查询的成果目标 直接从缓存中加载目标,并设置到外层目标中
deferredLoad.load();
} else {
// 将DeferredLoad目标增加到deferredLoads队列中,待整个外层查询完毕后,再加载该成果目标
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
DeferredLoad
是界说在 BaseExecutor
中的内部类,它担任从 localCache
缓存中推迟加载成果目标,其字段的意义如下:
// 外层目标对应的MetaObject目标
private final MetaObject resultObject;
// 推迟加载的局生称号
private final String property;
// 推迟加载的特点的类型
private final Class<?> targetType;
// 推迟加载的成果目标在一级缓存中相应的CacheKey目标
private final CacheKey key;
// 一级缓存
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
// 担任结采目标的类型转化
private final ResultExtractor resultExtractor;
DeferredLoad.canLoad()
办法担任检测缓存项是否现已彻底加载到了缓存中。首要要说明彻底加载的意义BaseExecutor.queryFromDatabase()
办法中,开端查询调用 doQuery()
办法查询数据库之前,会先在 localCache
中增加占位符,待查询完结之后才将真实的成果目标放到 localCache
中缓存,此时该缓存项才算彻底加载。BaseExecutor.queryFromDatabase()
办法详细完结如下:
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;
}
DeferredLoad.canLoad()
办法的详细完结如下:
public boolean canLoad() {
// 检测缓存是否存在指定的成果目标和是否为占位符
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
DeferredLoad.load()
办法担任从缓存中加载成果目标,并设置到外层目标的相应特点中,详细完结如下:
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);
}
介绍完 DeferredLoad
目标之后,来看触发 DeferredLoad
缓存中加载成果目标的相关代码,这段代码在 BaseExecutor.query()
办法中,详细完结如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// ...
// 推迟加载
if (queryStack == 0) {
// 在最外层的查询完毕时,一切嵌套查询也现已完结,相关缓存项也现已彻底加载,所以在这儿能够触发DeferredLoad加载一级缓存中记载的嵌套查询的成果目标
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 加载完结后清空deferredLoads
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
// 依据localCacheScope装备决议是否清空一级缓存
clearLocalCache();
}
}
return list;
}
BaseExecutor.queryCursor()
办法的首要功用也是查询数据库,这一点与 query()
办法相似,但它不会直接将成果集映射为成果目标,而是将成果集封装成 Cursor
目标并回来,待用户遍历 Cursor
时才真实完结成果集的映射操作。另外,queryCursor()
办法是直接调用 doQueryCursor()
这个根本办法完结的,并不会像 query()
办法那样运用查询一级缓存。
BaseExecutor.update()
办法担任履行 insert、update、delete
三类 SQL
句子,它是调用 doUpdate()
模板办法完结的。在调用 doUpdate()
办法之前会清空缓存,因为履行 SQL
句子之后,数据库中的数据现已更新,一级缓存的内容与数据库中的数据或许现已不一致了,所以需求调用 clearLocalCache()
办法清空一级缓存中的脏数据。
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
// 判别当时Executor是否现已封闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清楚一级缓存
clearLocalCache();
// 调用doUpdate()办法
return doUpdate(ms, parameter);
}
业务相关操作
在 BatchExecutor
完结中,能够缓存多条 SQL
句子,等候适宜的时机将缓存的多条 SQL
句子,并发送到数据库履行。 Executor.flushStatements()
办法首要是针对批处理多条 SQL
句子的,它会调用 doFlushStatements()
这个根本办法处理 Executor
缓存的多条 SQL
句子。在BaseExecutor.commit()、rollback()
等办法中都会首要调用 flushStatements()
办法,然后再履行相关业务操作。
BaseExecutor.flushStatements()
办法的详细完结如下:
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
// 判别当时Executor是否现已封闭
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 调用doFlushStatements()这个根本办法,其参数isRollBack表明是否履行Executor中缓存的SQL句子,false表明履行,true表明不履行
return doFlushStatements(isRollBack);
}
BaseExecutor.commit()
办法首要会清空一级缓存、调用 flushStatements()
办法,最终才依据参数决议是否真实提交业务。commit()
办法的完结如下:
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
// 清空一级缓存
clearLocalCache();
// 履行缓存的SQL句子,其间调用了flushStatements(false)办法
flushStatements();
// 依据required参数决议是否提交业务
if (required) {
transaction.commit();
}
}
BaseExecutor.rollback()
办法的完结与 commit()
完结相似,同样会依据参数决议是否真实回滚业务 ,差异是其间调用的是 flushStatements()
办法的 isRollBack
参数为 true
这就会导致 Executor
中缓存的 SQL
句子悉数被疏忽,即不会被发送到数据库履行。
BaseExecutor.close()
办法首要会调用 rollback()
办法疏忽缓存的SQL句子,之后依据参数决议是否封闭底层的数据库衔接。
SimpleExecutor
SimpleExecutor
承继了 BaseExecutor
抽象类,它是最简略的 Executor
接口完结。正如前面所说,Executor
运用了模板办法形式,一级缓存等固定不变的操作都封装到了BaseExecutor
。SimpleExecutor
中就不必再关怀一级缓存等操作,只需求专注完结四个根本办法的完结即可。
首要来看 SimpleExecutor.doQuery()
办法的详细完结:
@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目标 ,实际回来的是RoutingStatementHandler目标
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 完结Statement的创立和初始化
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler .query()办法,履行SQL句子,并经过ResultSetHandler,完结成果集的映射
return handler.query(stmt, resultHandler);
} finally {
// 封闭Statement目标
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 创立Statements目标
stmt = handler.prepare(connection, transaction.getTimeout());
// 处理占位符
handler.parameterize(stmt);
return stmt;
}
SimpleExecutor.doQueryCursor()
办法、doUpdate()
办法与 doQuery()
办法完结相似,这儿不再剖析。 SimpleExecutor
不供给批量处理 SQL
句子的功用,所以其 doFlushStatements()
办法直接回来空调集,不做其他任何操作。
ReuseExecutor
在传统的 JDBC
编程中,重用 Statement
目标是常用的一种优化手法,该优化手法能够削减 SQL
预编译的开支以及创立和销毁 Statement
目标的开支,从而进步功用。
ReuseExecutor
供给了 Statement
重用的功用,ReuseExecutor
中经过 statementMap
字段缓存运用过的 Statement
目标,key
是 SQL
句子,value
是 SQL
对应的 Statement
目标。ReuseExecutor.doQuery()、doQueryCursor()、doUpdate()
办法的完结与S impleExecutor
对应办法的完结相同,差异在于其间调用的 preparestatement()
办法,SimpleExecutor
每次都会经过 JDBC Connection
创立新的 Statement
目标,而 ReuseExecutor
会先测验重用 StaternentMap
缓存的Statement
目标。
ReuseExecutor.prepareStatement()
办法的详细完结如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取BoundSql目标
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
// 判别该SQL是否缓存过Statement
if (hasStatementFor(sql)) {
// 获取缓存的Statement目标
stmt = getStatement(sql);
// 修改超时时刻
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
// 创立新的Statement目标,并缓存到statementMap调集中
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
当业务提交或回滚、衔接封闭时,都需求封闭这些缓存的 Statement
目标 。前面介绍了 BaseExecutor.commit()、 rollback()
和 close()
办法时提到,其间都会调用 doFlushStatements()
办法,所以在该办法完结封闭 Statement
目标的逻辑十分适宜,详细完结如下:
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) {
// 遍历statementMap调集并封闭其间的Statement目标
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
// 清空statementMap
statementMap.clear();
return Collections.emptyList();
}
这儿需求留意一下 ReuseExecutor.queryCursor()
办法的运用,了解 JDBC
编程的读者知道,每个 Statement
目标只能对应一个成果集,当屡次调用 queryCursor()
办法履行同 SQL
句子时,会复用 Statement
目标,只要最终一个 ResultSet
是可用的。而 queryCursor()
办法回来的 Cursor
目标,在用户迭代 Cursor
目标时,才会真实遍历成果集目标并进行映射操作,这就或许导致运用前面 Cursor
目标中封装的成果集封闭。
BatchExecutor
运用体系在履行一条 SQL
句子时,会将 SQL
句子以及相关参数经过网络发送到数据库体系。关于频繁操作数据库的运用体系来说,假如履行 SQL
句子就向数据库发送一次恳求,许多时刻会浪费在网络通信上。运用批量处理的优化办法能够在客户端缓存多条 SQL
句子,并在适宜的时机将多条 SQL
句子打包发送给数据库履行,从而削减网络方面的开支,提高体系的功用。
不过有一点需求留意,在批量履行多条 SQL
句子时,每次向数据库发送的 SQL
句子条数是有上限的,假如超越这个上限,数据库会拒绝履行这些 SQL
句子井抛出反常 所以批量发送 SQL
句子的时机很重要。
BatchExecutor
完结了批处理多条 SQL
句子的功用,其间中心字段的意义如下:
// 缓存多个Statement目标其间每个Statement目标中都缓存了多条SQL句子
private final List<Statement> statementList = new ArrayList<>();
// 记载批处理的成果,BatchResult中经过updateCounts字段记载每个Statement履行批处理的成果
private final List<BatchResult> batchResultList = new ArrayList<>();
// 记载当时履行的SQL句子
private String currentSql;
// 记载当时履行的MappedStatement目标
private MappedStatement currentStatement;
JDBC
中的批处理只支持 insert、update、delete
等类型的 SQL
句子,不支持 select
类型的 SQL
句子,所以下面要剖析的是 BatchExecutor.doUpdate
办法。
BatchExecutor.doUpdate()
办法在增加一条 SQL
句子时,首要会将 currentSql
字段记载的 SQL
句子以及 currentStatement
字段记载的 MappedStatement
目标与当时增加的 SQL
以及 MappedStatement
目标进行比较,假如相同则加到同一个 Statement
目标等候履行,假如不同则创立新的 Statement
目标并将其缓存到 statementList
调集中等候履行。doUpdate()
办法详细完结如下:
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
// 获取装备目标
final Configuration configuration = ms.getConfiguration();
// 创立StatementHandler目标
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
// 获取SQL句子
final String sql = boundSql.getSql();
final Statement stmt;
// 假如当时履行的SQL形式与前次履行的SQL形式相同且对应的MappedStatement目标相同
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
// 获取statementList中的最终一个
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
// 绑定实参
handler.parameterize(stmt);
// 查找对应的BatchResult目标,并记载用户传入的实参
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
// 创立新的Statement目标
stmt = handler.prepare(connection, transaction.getTimeout());
// 绑定实参
handler.parameterize(stmt);
// 更新currentSql、currentStatement
currentSql = sql;
currentStatement = ms;
// 增加刚创立的Statement目标
statementList.add(stmt);
// 增加新的BatchResult目标
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// 底层经过调用Statement.addBatch()办法添SQL句子
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
JDBC
批处理功用中 Statement
能够增加不同形式的 SQL
,可是每增加一个新形式的 SQL
句子都会触发一次编译操作 PreparedStatement
中只能增加同一形式的 SQL
句子,只会触发一次编译操作,可是能够经过绑定多组不同的实参完结批处理。经过上面临 doUpdate()
办法的剖析可知,BatchExecutor
会将连续增加的、相同形式的 SQL
句子增加到同一个 Statement/PreparedStatement
目标中,这样能够有效地削减编译操作的次数。
在增加完待履行的SQL句子之后来看一下 BatchExecutor.doFlushStatemnts()
办法是怎么批量处理这些 SQL
句子的:
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
// 用于贮存批量处理的成果
List<BatchResult> results = new ArrayList<>();
// 假如清晰指定了要回滚业务,则直接回来空调集,疏忽statementList调集中记载的SQL句子
if (isRollback) {
return Collections.emptyList();
}
// 遍历statementList调集
for (int i = 0, n = statementList.size(); i < n; i++) {
// 获取Statement目标
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
// /获取对应的BatchResult目标
BatchResult batchResult = batchResultList.get(i);
try {
// 调用Statement.executeBatch()办法批量履行其间记载的SQL句子,并运用回来的int数组
// 更新BatchResult.updateCounts字段,其间每一个元素都表明一条SQL句子影响的记载条数
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
// 获取装备的KeyGenerator目标
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
// 获取数据库生成的主键,并设置到parameterObjects中
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
// 关于其他类型的keyGenerator,会调用其processAfter()办法
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
// 封闭Statement目标
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
// 增加BatchResult到results调集
results.add(batchResult);
}
return results;
} finally {
// 封闭Statement目标,并清空currentSql字段、清空statementList调集、清空batchResultList调集
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
BatchExecutor.doQuery
和 doQueryCursor()
办法的完结与前面介绍的 SimpleExecutor
相似,首要差异便是 BatchExecutor
中的这两个办法在最开端都会先调用 flushStatement()
办法,履行缓存 SQL
句子,这样才能从数据库中查询到最新的数据。
CachingExecutor
CachingExecutor
是 Executor
接口的装饰器,它为 Executor
目标增加了二级缓存的相关的功用,先来简略介绍 MyBatis
中的二级缓存及其依靠的相关组件。
二级缓存简介
MyBatis
中供给的二级缓存是运用级别的缓存,它的生命周期与运用程序的生命周期相同。与二级缓存相关的装备有三个:
(1)首要是 mybatis-config.xml
装备文件中的 cacheEnabled
装备,它是二级缓存的总开关。只要当该装备设置为true
时,后边两项的装备才会有效果,cacheEnabled
的默许值为 true
。详细装备如下:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
(2)在前面介绍映射装备文件的解析流程时提到,映射装备文件中能够装备 <cache>
节点或 <cache-ref>
节点。假如映射装备文件中装备了这两者中的任何一个节点,则表明敞开了二级缓存功用。假如装备了 <cache>
节点,在解析时会为该映射装备文件指定的命名空间创立相应的 Cache
目标作为其二级缓存,默许是 PerpetualCache
目标,用户能够经过 <cache>
节点的 type
特点指定自界说 Cache
目标。假如装备了<cache-ref>
节点,在解析时则不会为当时映射装备文件指定的命名 空间创立独立的 Cache
目标,而是认为它与 <cache-ref>
节点的 namespace
特点指定的命名空间同享同一个 Cache
目标。经过 <cache>
节点和<cache-ref>
节点的装备,用户能够在命名空间的粒度上办理二级缓存的敞开和封闭。
(3)最终一个装备项是 <select>
节点中的 useCache
特点,该特点表明查询操作发生的成果目标是否要保存到二级缓存中。useCache
特点的默许值是 true
。
TransactionalCache&TransactionalCacheManager
TransactionalCache
和 TransactionalCacheManager
是 CachingExecutor
依靠的两个组件。其间,TransactionalCache
承继了Cache
接口,首要用于保存在某个SqlSession
的某个业务中需求向某个二
级缓存中增加的缓存数据。TransactionalCache中中心字段的意义如下:
// 底层封装的二级缓存所对应的Cache目标
private final Cache delegate;
// 当该字段为true时,则表明当时TransactionalCache不行查询,且提交业务时会将底层Cache清空
private boolean clearOnCommit;
// 暂时记载增加到TransactionalCache中的数据。在业务提交时,会将其间的数据增加到二级缓存中
private final Map<Object, Object> entriesToAddOnCommit;
// 记载缓存未射中的CacheKey目标
private final Set<Object> entriesMissedInCache;
TransactionalCache.putObject()
办法并没有直接将成果目标记载到其封装二级缓存中,而是暂时保存在 entriesToAddOnCommit
调集中,在业务提交时才会将这些成果目标从 entriesToAddOnCommit
调集增加到二级缓存中。putObject()
办法的详细完结如下:
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
再来看 TransactionalCache.getObject()
办法,它首要会查询底层的二级缓存,并将未射中的 key
增加到 entriesMissedInCache
中,之后会依据 clearOnCommit
字段的值决议详细的回来值,详细完结如下:
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
// 将未射中的key增加到entriesMissedInCache中
entriesMissedInCache.add(key);
}
// issue #146
// 依据clearOnCommit字段的值决议详细的回来值
if (clearOnCommit) {
return null;
} else {
return object;
}
}
TransactionalCache.clear()
办法会清空 entriesToAddOnCommit
调集,并设置 clearOnCommi
为 true
。
TransactionalCache.commit()
办法会依据 clearOnCommit
字段的值决议是否清空二级缓存,然后调用 flushPendingEntries()
办法将 entriesToAddOnCommit
调集中记载的成果目标保存到二级缓存中,详细完结如下:
public void commit() {
// 在业务提交前 清空二级缓存
if (clearOnCommit) {
delegate.clear();
}
// 将entriesToAddOnCommit调集中的数据保存到二级缓存
flushPendingEntries();
// 重置clearOnCommit为false ,并清空entriesToAddOnCommit和entriesMissedInCache 调集
reset();
}
private void flushPendingEntries() {
// 遍历entriesToAddOnCommit调集,将其间记载的缓存项增加到二级缓存中
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
// 遍历entriesMissedInCache调集,将entriesToAddOnCommit调集中不包括的缓存项增加到二级缓存中
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
TransactionalCache.rollback()
办法会将 entriesMissedlnCache
调集中记载的缓存项从二级缓存中删去,并清空 entriesToAddOnCommit
调集和 entriesMissedlnCache
调集,详细完结如下:
public void rollback() {
// 将entriesMissedInCache调集中记载的缓存项从二级缓存中删去
unlockMissedEntries();
// 遍历entriesMissedInCache调集,将entriesToAddOnCommit调集中不包括的缓存项增加到二级缓存中
reset();
}
TransactionalCacheManager
用于办理 CachingExecutor
运用的二级缓存目标,其间只界说了transactionalCaches
字段,它的 key
是对应的 CachingExecutor
运用的二级缓存目标,value
是相应 TransactionaCach
目标,在该 TransactionalCache
中封装了对应二级缓存目标,也便是这儿的 key
。
TransactionalCacheManager
的完结比较简略,下面简略介绍各个办法的功用和完结。
clear()办法、 putObject办法、 getObject()办法: 调用指定二级缓存对应的 TransactionalCache
目标的对应办法,假如 transactionalCaches
调集中没有对应的 TransactionalCache
目标,则经过 getTransactionalCache()
办法创立。
private TransactionalCache getTransactionalCache(Cache cache) {
// 假如transactionalCaches调集中没有对应的TransactionalCache目标,则新建
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
commit()办法、 rollback()办法: 遍历 transactionalCaches
调集,井调用其间各个 TransactionalCache
目标的相应办法。
CachingExecutor的完结
CachingExecutor.query()
办法履行查询操作的过程如下:
(1)获取 BoundSql
目标,创立查询句子对应的 CacheKey
目标。
(2)检测是否敞开了二级缓存,假如没有敞开二级缓存,则直接调用底层 Executor
目标的 query()
办法查询数据库。假如敞开了二级缓存,则持续后边的过程。
(3)检测查询操作是否包括输出类型的参数,假如是这种状况,则报错
(4)调用 TransactionalCacheManager.getObject()
办法查询二级缓存,假如二级缓存中查找到相应的成果目标,则直接将该成果目标回来。
(5)假如二级缓存没有相应的成果目标,则调用底层 Executor
目标的 query()
办法。正如前面介绍的 ,它会先查询一级缓存,一级缓存未射中时,才会查询数据库。最终还会将得到的成果目标放入 TransactionalCache.entriesToAddOnCommit
调集中保存。
CachingExecutor.query()
办法的详细代码如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取BoundSql目标
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 创立CacheKey目标
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 {
// 获取查询句子地点命名空间对应的二级缓存
Cache cache = ms.getCache();
// :是否敞开了二级缓存功用
if (cache != null) {
// 依据<select>节点的装备,决议是否需妥清空二级缓存
flushCacheIfRequired(ms);
// 检测SQL节点的useCache装备以及是否运用了resultHandler装备
if (ms.isUseCache() && resultHandler == null) {
// 二级缓存不能保存输出类型的参数 假如查询操作调用了包括输出参数的存储过程,则报错
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 查询二级缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 二级缓存没有相应的成果目标,调用封装的Executor目标的query()办法
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将查询成果保存到TransactionalCache.entriesToAddOnCommit调集中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 没有发动二级缓存,调用封装的Executor目标的query()办法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
再看 CachingExecutor.commit()
和 rollback()
办法的完结,它们首要调用底层的 Executor
目标的对应办法完结提交和回滚,然后调用 TransactionalCacheManager
的对应办法完结对二级缓存的操作。详细完结如下:
@Override
public void commit(boolean required) throws SQLException {
// 调用底层的Executor提交业务
delegate.commit(required);
// 遍历相关TransactionalCache目标履行commit()办法
tcm.commit();
}
@Override
public void rollback(boolean required) throws SQLException {
try {
// 调用底层的Executor回滚业务
delegate.rollback(required);
} finally {
if (required) {
// 遍历相关TransactionalCache目标履行rollback()办法
tcm.rollback();
}
}
}
StatementHandler
StatementHandler
接口中的功用许多,例如创立 Statement
目标,为 SQL
句子绑定实参,履行 select、insert、update、delete
等多种类型的 SQL
句子,批量履行 SQL
句子,将成果集映射成成果目标。
StatementHandler
接口的界说如下:
public interface StatementHandler {
// 从衔接中获取一个Statement
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
// 参数绑定
void parameterize(Statement statement)
throws SQLException;
// 批量履行
void batch(Statement statement)
throws SQLException;
// 履行update、insert、delete操作
int update(Statement statement)
throws SQLException;
// 履行select操作
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
RoutingStatementHandler
RoutingStatementHandler
会依据 MappedStatement
指定的 statementTyp
字段创立对应的 StatementHandler
接口完结。RoutingStatementHandler
类的详细完结如下:
public class RoutingStatementHandler implements StatementHandler {
// 封装的真实的StatementHandler目标
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 依据MappedStatement的装备,生成一个对应的StatementHandler目标,并设置到delegate字段中
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());
}
}
//...经过delegate调用对应的办法
}
BaseStatementHandler
BaseStatementHandler
是一个完结了 StatementHandler
接口的抽象类,它只供给了一些参数绑定相关的办法,并没有完结操作数据库的办法。BaseStatementHandler
字段的意义如下:
// 记载运用的ResultSetHandler目标,将成果集映射成成果目标
protected final ResultSetHandler resultSetHandler;
// 记载运用的ParameterHandler目标,运用传入的实参替换SQL句子的中的?占位符
protected final ParameterHandler parameterHandler;
// 记载履行SQL句子的Executor目标
protected final Executor executor;
// 记载SQL句子对应的MappedStatement、BoundSql目标
protected final MappedStatement mappedStatement;
// RowBounds记载了用户设置的offset,limit,用于在成果集中定位映射的起始位置和完毕位置
protected final RowBounds rowBounds;
在 BaseStatementHandler
的结构办法中,除了初始化上述字段之外,还会调用 KeyGenerator.processBefore()
办法初始化 SQL
句子的主键,详细完结如下:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// ...其他字段疏忽
if (boundSql == null) {
// 调用keyGenerator.processBefore()办法获取主键
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
BaseStatementHandler
完结了 StatementHandler
接口中的 prepare()
办法,该办法首要调 instantiateStatement()
抽象办法初始化 Java.sqI.Statement
目标 然后为其装备超时时刻以及 fetchSize
等设置。
ParameterHandler
经过前面临动态 SQL
的介绍可知,在 BoundSql
中记载的 SQL
句子中或许包括 ?
占位符,而每个 ?
占位符都对应了 BoundSql、parameterMappings
调集中的一个元素,在该 ParameterMapping
中记载了对应参数称号以及该参数的相关特点。
在 ParameterHandler
接口中只界说了一个 setParameters()
办法,该办法首要担任调用 PreparedStatement.set*()
办法为 SQL
句子绑定实参。MyBatis
只为 ParameterHandler
接口供给了一个完结类 DefaultParameterHandler.DefaultParameterHandler
中中心字段的意义如下:
// 办理mybatis中的悉数TypeHandler目标
private final TypeHandlerRegistry typeHandlerRegistry;
// 其间记载SQL节点相应的装备信息
private final MappedStatement mappedStatement;
// 用户传入的实参目标
private final Object parameterObject;
// 对应的BoundSql目标,需求设置参数的PreparedStatement目标
private final BoundSql boundSql;
在 DefaultParameterHandler.setParameters()
办法中会遍历 BoundSql.parameterMappings
调集中记载的 ParameterMapping
目标,井依据其间记载的参数称号查找相应实参,然后与 SQL
句子绑定。setParameters()
办法的详细完结如下:
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 取出sql中的参数映射列表
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
// 过滤掉存储过程中的输出参数
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
// 获取对应的实参值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
// 实参为空
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 依据TypeHandler转化成JdbcType
value = parameterObject;
} else {
// 获取目标中相应的特点位或查找Map目标中值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取ParameterMapping中设置的TypeHandler目标
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// //经过TypeHandler.setParameters()办法会调用PreparedStatement.set*()办法为SQL句子绑定相应的实参
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
为 SQL
句子绑定完实参之后,就能够调用 Statement
目标相应的 execute()
办法,将 SQL
句子交给数据库履行。
SimpleStatementHandler
SimpleStatementHandler
承继了BaseStatementHandler
抽象类。它底层运用 java.sql.Statement
目标来完结数据库的相关操作,所以 SQL
句子中不能存在占位符相应的,SimpleStatementHandler.parameterize()
办法是空完结。
SimpleStatementHandler.instantiateStatement()
办法直接经过 JDBCConnection
创立 Statement
目标,详细完结如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.createStatement();
} else {
// 设置成果集是否能够翻滚及其游标是否能够上下移动,设置成果集是否可更新
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
上面创立的 Statement
目标之后会被用于完结数据库操作,SimpleStatementHandler.query()
办法等完结了数据库查询的操作,并经过 ResultSetHandler
将成果集映射成成果目标。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 获取SQL句子
String sql = boundSql.getSql();
// 履行SQL句子
statement.execute(sql);
// 映射成果集
return resultSetHandler.handleResultSets(statement);
}
SimpleStatementHandler
中的 queryCursor()、batch()
办法与 query()
办法完结相似,也是直接调用 Statement
目标的相应办法,不再赘述。
SimpleStatementHandler.update()
办法担任履行 insert、update、delete
等类型的 SQL
句子,而且会依据装备的 KeyGenerator
获取数据库生成的主键 详细完结如下:
@Override
public int update(Statement statement) throws SQLException {
// 获取SQL句子
String sql = boundSql.getSql();
// 获取实参目标
Object parameterObject = boundSql.getParameterObject();
// 获取装备的KeyGenerator目标
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
// 履行SQL句子
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
// 获取受影响的行
rows = statement.getUpdateCount();
// 将数据库生成的主键增加到parameterObject中
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
// 履行SQL句子
statement.execute(sql);
// 获取受影响的行
rows = statement.getUpdateCount();
// 履行<selectKey>节点中装备的SQL句子获取数据库生成的主键,增加到parameterObject中
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
PreparedStatementHandler
PreparedStatementHandler
底层依靠于 java.sql.PreparedStatement
目标来完结数据库的相关操作。在 SimpleStatementHandler.parameterize()
办法中, 会调用前面介绍的 ParameterHandler.setParameters()
办法完结 SQL
句子的参数绑定。
PreparedStatementHandler.instantiateStatement()
办法直接调用 JDBC Connection
的 prepareStatement()
办法创立 PreparedStatement
目标, 详细完结如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 获取SQL句子
String sql = boundSql.getSql();
// 获取mappedStatement.getKeyGenerator()创立prepareStatement目标
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
// insert句子履行完结之后,会将keyColumnNames指定的列回来
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);
}
}
PreparedStatementHandler
中其他办法的完结与 SimpleStatementHandler
对应办法的完结相似,这儿就不再赘述。
CallableStatementHandler
CallableStatementHandler
底层依靠于 java.sql.CallableStatement
存储过程 parameterize()
办法也会调用 ParameterHandler.setParameters()
办法完结 SQL
句子的参数绑定指定输出参数的索引位置和 JDBC
类型。其他办法与前面介绍 SimpleStatementHandler
完结相似,仅有差异是会调用 resultSetHandler.handleOutputParameters()
处理输出参数。
总结
本文从一个简略的 MyBatis
示例带领我们从源码的角度剖析 MyBatis
的履行流程。希望我们在运用 MyBatis
时知其所以然,一起也帮助我们在运用 MyBatis
时遇到问题有方向去排查。感谢我们的阅览。
参阅文章
mybatis 官网
《MyBatis技能内情》