在MyBatis中Mapper对象的创建过程一文中,在创建mapper的动态代理对象时指定的InvocationHandler是MapperFactory类型的。那么所有mapper接口中的方法都会经过MapperFactory类的invoke方法,该方法就是分析Mapper方法也就是整个MyBatis运行时的入口。
整个MyBatis执行过程可以拆分为下面几个部分:
- 统一拦截层;
- SqlSession层;
- Executor层;
- JDBC层;
上面是我个人自己的总结,不是MyBatis的官方说法。接下来我一个个步骤进行介绍。
统一拦截层
MapperProxy
/*
* MyBatis中所有Mapper的接口方法会被该方法拦截
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object中的方法,
if (Object.class.equals(method.getDeclaringClass())) {
// 则直接调用
return method.invoke(this, args);
} else {
/*
* 先获取invoker对象,再调用。
* 如果缓存中不存在invoker对象,那么新建一个并缓存起来。
*/
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) { // 如果是默认方法
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 { // 如果是普通方法
// 这里同时创建了MapperMethod和invoker
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}这里主要是获取或创建了方法调用器对象,主要考虑PlainMethodInvoker这种情况。
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
/*
* 执行方法
*/
return mapperMethod.execute(sqlSession, args);
}
}这里仅是简单分装,调用了MapperMethod的execute方法。
MapperMethod
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 创建SQL命令对象,内部会判断SQL的类型
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}/*
* MyBatis中mapper方法的核心实现
*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
/*
* 根据命令的类型,执行不同的操作。
* 这些操作都是基于SqlSession来执行的。
*/
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,是因为会根据参数情况返回不同的类型,如Collection,Map等
* 既然这里是Object类型的,后续执行时是怎么使用param对象的?怎么知道param是哪种具体类型的?
*/
Object param = method.convertArgsToSqlCommandParam(args);
// 执行查询
result = sqlSession.selectOne(command.getName(), param);
// 方法返回类型为Optional的情况
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());
}
// 如果底层返回结果为null,但是mapper方法的返回值是基础类型但不为void,则抛出异常
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;
}该方法中针对不同类型的SQL语句进行了不同的处理,各种情况只是中间处理过程不一样,最终执行到底层时都是类似的,所以本文选用一般的case SELECT的情况,即最后一个else。 这种情况先是对参数进行了处理,因为有各种情况的参数,所以这里同一封装为Object;然后调用了SqlSession的selectOne方法。
ParamNameResolver
public Object convertArgsToSqlCommandParam(Object[] args) {
// 获取参数名-值映射
return paramNameResolver.getNamedParams(args);
}public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 创建参数名称解析器
this.paramNameResolver = new ParamNameResolver(configuration, method);
}在MethodSignature构造方法中创建了ParamNameResolver对象,这个构造过程还是比较重要的,对参数名称进行了处理。
public ParamNameResolver(Configuration config, Method method) {
// 判断是否使用实际的参数名称,默认为true
this.useActualParamName = config.isUseActualParamName();
// 获取方法参数类型
final Class<?>[] paramTypes = method.getParameterTypes();
// 获取方法中各个参数的注解
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
// 遍历参数
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 判断是不是特殊参数,如果是则跳过(里面判断了两种类型,大部分参数都不会是这里的特殊参数)
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 遍历方法参数的注解
for (Annotation annotation : paramAnnotations[paramIndex]) {
// 如果是@Param注解
if (annotation instanceof Param) {
hasParamAnnotation = true;
// 获取通过注解配置的参数名称
name = ((Param) annotation).value();
break;
}
}
// 没有使用@Param注解,或者使用了注解但没有指定参数名称
if (name == null) {
// @Param was not specified.
// 如果使用参数名,则获取参数名称
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
// 如果不使用实际参数名,那么使用参数序号作为参数名
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
// 保存参数名称
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}private String getActualParamName(Method method, int paramIndex) {
return ParamNameUtil.getParamNames(method).get(paramIndex);
}public class ParamNameUtil {
public static List<String> getParamNames(Method method) {
return getParameterNames(method);
}
public static List<String> getParamNames(Constructor<?> constructor) {
return getParameterNames(constructor);
}
private static List<String> getParameterNames(Executable executable) {
return Arrays.stream(executable.getParameters()).map(Parameter::getName).collect(Collectors.toList());
}
private ParamNameUtil() {
super();
}
}可以分为三种情况:
- 使用了@Param注解且配置了参数名称,则获取注解中的名称;
- 如果允许使用实际参数名称,获取形参名称;
- 如果不允许使用实际参数名称,则使用数字序号(从0开始,字符串形式)作为参数名称;
这里是是否允许使用实际参数名称是Configuration中的useActualParamName属性来控制的,默认是true。实际开发中一般也不会修改该属性。 在获取实参时,是通过JDK的反射相关类来实现的。该实现实际是从.class文件中获取的参数名称,如果编译时没有在.class文件中保留参数名称,那么这里会返回"argN"格式的名称,其中"N"是参数序号(从0开始)。 默认情况下,javac编译器是不会写入参数实际名称的,以此来减少.class文件的大小;如果需要保留参数名称,可以指定编译参数-parameters。
接着看ParamNameReolver的getNamedParams方法。
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) { // 如果参数个数为1, 而且没有@Param注解
Object value = args[names.firstKey()];
// 根据参数值类型建立其他的名称映射
return wrapToMapIfCollection(value, useActualParamName ? names.get(names.firstKey()) : null);
} else {
// 创建参数名和值的映射
final Map<String, Object> param = new ParamMap<>();
int i = 0;
// 遍历names
for (Map.Entry<Integer, String> entry : names.entrySet()) {
// 添加参数名-值键值对
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
// 创建通用参数名,即前缀(param)+ 序号
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
/*
* 如果通用参数名没有被使用,则添加进去;
* 从这里也可以看出来,对于每个参数,都会有一个通用参数名;
*/
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}public static Object wrapToMapIfCollection(Object object, String actualParamName) {
// 如果是Collection及其子类型
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) { // 如果是list,则同时添加这种参数名映射
map.put("list", object);
}
// 实际参数名映射
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
} else if (object != null && object.getClass().isArray()) { // 如果是数组类型
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}在使用MyBatis开发时,经常遇到找不到参数映射或者参数映射错误的情况,尤其是mapper方法有多个参数的时候。理解了这部分代码逻辑,会很大程度解决这个问题。当然使用@Param注解也可以在不理解的情况下进行正常开发,但多写一个注解总是麻烦的,从实际来看,大部分开发都不会写。
该方法逻辑可以分为三种情况:
- 没有参数:直接返回null;
- 只有一个参数,且没有指定@Param注解:会调用wrapToMapIfCollection方法来进行一些特殊的参数名称映射,包括“collection”, “list”或“array”;
- 其他情况:添加实际名称,另外还会添加通用的参数名称,即“paramN”格式,其中“N”是参数序号(从1开始)。
SqlSession层
上面说到,MapperMethod的execute方法会调用SqlSession中的方法,这里以selectOne方法为例继续分析。
但SqlSession是个接口,具体是什么类型的呢?这就要找到创建SqlSession对象的地方了,实际上,该对象是在创建MapperProxy实例时传进来的。这就要回到MapperFactoryBean中的getObject方法了,SqlSession对象就是从这里开始传入MapperProxy中的,即MapperFactoryBean中的属性sqlSessionTemplate,而在Spring Boot中,此对象则是MyBatisAutoConfiguration中注入的SqlSessionTemplate对象。这个过程在MyBatis中Mapper对象的创建过程一文中已详细介绍,本文不再赘述。
所以SqlSession层使用的对象是SqlSessionTemplate对象,这个template对象其实是为了和Spring的事务管理机制进行适配做的一层封装,而这个类本身也位于mybatis-spring模块。
SqlSessionTemplate
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建SqlSession的代理对象,实际的操作会委托给此对象处理
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
} private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*
* 获取sqlSession,与Spring的事务机制适配
* 这里的sqlSessionFactory是DefaultSqlSessionFactory类型的,
* 参考SqlSessionFactoryBean#buildSqlSessionFactory方法的结尾,也就是SqlSessionFactoryBuilder的build方法。
* 这里的SqlSessionFactory是DefaultSqlSessionFactory,所以这里创建的是DefaultSqlSession类型的对象。
*/
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 调用目标方法,通过上面获取的sqlSession来反射调用
Object result = method.invoke(sqlSession, args);
// 如果当前操作不在事务中,则立马提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
// 关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
} @Override
public <T> T selectOne(String statement, Object parameter) {
// 通过代理执行,此代理用于适配spring的事务管理,参考上面构造方法中该变量的初始化
return this.sqlSessionProxy.selectOne(statement, parameter);
}实际上,SqlSessionTemplate中的很多从SqlSession实现的操作都是交给代理对象来处理的,而在SqlSessionInterceptor中,是通过SqlSessionUtils来获取的SqlSession对象。
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 通过spring的事务同步管理器来获取sqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
// 如果上面没有获取到,则新建一个sqlSession
session = sessionFactory.openSession(executorType);
// 添加到线程本地存储中,避免后续重复创建
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}可以看到,MyBatis是通过spring的事务管理机制来管理SqlSession对象,如果不存在则调用SqlSessionFactory的openSession方法来新建。同样回到MyBatisAutoConfiguration中,这里注册了SqlSessionFactory bean,最终注入到SqlSessionTemplate对象中,然后交给其内部的SqlSessionInterceptor来创建新的SqlSession对象。工厂对象的具体创建过程比较复杂,先知道是DefaultSqlSessionFactory类型的即可,后续有时间单独写一篇文章来介绍。
下面来看看工厂对象是怎么创建SqlSession对象的!
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
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);
// 创建sqlSession
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();
}
}
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;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务对象
final Transaction tx = transactionFactory.newTransaction(connection);
// 创建执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 创建SqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
// 在Spring中,返回的是SpringManagedTransactionFactory,参考SqlSessionFactoryBean构建SqlSessionFactory时的设置
return environment.getTransactionFactory();
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
}可以看到,这里创建的是DefaultSqlSession类型的对象。绕了一大圈,都是MyBatis对Spring事务机制的适配,MyBatis主要的实现还是在DefaultSqlSession中。
DefaultSqlSession
@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) {
// 期待查询1条记录,结果返回了多条则抛出异常
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}@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) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 根据mapper方法的ID(mapper接口的全限定名称拼接上方法的名称)来获取映射语句
MappedStatement ms = configuration.getMappedStatement(statement);
// 交给执行器进行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}在这里先获取MappedStatement对象,然后交给Executor来执行。那这里有两个问题,
- 这里的statement变量是什么?MappedStatement又是什么?
- executor属性是什么时候创建的?
溯源statement变量
该变量是从上面MapperMethod中一路传下来的,即SqlCommand的getName()方法的返回值。SqlCommand是在构建MapperMethod对象时创建的(MapperMethod是在上面介绍过的MapperProxy中创建的)。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 获取方法对应的SQL语句
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) { // 没找到语句
// 如果方法被@Flush注解修饰
if (method.getAnnotation(Flush.class) != null) {
name = null;
// 则设置SQL类型为FLUSH
type = SqlCommandType.FLUSH;
} else {
// 没找都映射的语句,则报错。这个错误在学习和工作中比较常见。
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
// 设置SQL语句类型
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
public String getName() {
return name;
}
public SqlCommandType getType() {
return type;
}
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// 计算语句的id,可以看到是mapper接口的全限定名称拼接上接口方法名称
String statementId = mapperInterface.getName() + "." + methodName;
// 从配置中获取语句
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}在resolveMappedStatement方法中,statement变量其实就是mapper接口名称和方法名称的拼接字符串。而SqlCommand和DefaultSqlSession中其实都是通过该拼接串来从Configuration对象中获取MappedStatement对象。
而MappedStatement可以看作对一个mapper方法对应的SQL语句的封装,在这里不宜过度展开,后面介绍mapper文件解析的时候会提到。
溯源Executor
Executor对象实际上是在DefaultSqlSessionFactory中创建DefaultSqlSession对象时创建的,然后作为构造方法参数传入其中的。
Executor是MyBatis四大核心接口,该类型对象是在Configuration中创建的。
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
// 创建执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
Executor executor;
// 根据执行器类型,创建不同的执行器对象
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// 默认情况下使用SIMPLE执行器
executor = new SimpleExecutor(this, transaction);
}
// 如果开启了缓存
if (cacheEnabled) {
// 则封装一下执行器
executor = new CachingExecutor(executor);
}
// 应用拦截器
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}默认情况下创建的是SimpleExecutor类型的执行器,如果启用了缓存机制(cacheEnabled属性来控制,默认是true;可以调整为false来禁用二级缓存;),则会创建CachingExecutor来封装一下。
MyBatis中的四大核心接口:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
这四大接口类型的对象都由Configuration对象来创建,并利用MyBatis的插件机制来处理。插件只应该也只能处理这四种类型的对象。
Executor层
CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取实际的SQL(处理完动态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 {
/*
* 获取二级缓存对象,可见这里与SqlSession无关,具体与什么有关,要看MappedStatement的cache属性的设置逻辑。
* (实际上这里的二级缓存以namespace维度来隔离)
* 所以也说二级缓存与sqlSession无关,与namespace才有关。
*/
Cache cache = ms.getCache();
if (cache != null) {
// 如果mapper中的语句设置了flushCache=true(默认值为false),清空二级缓存;实际上该字段配置还会清空一级缓存(见BaseExecutor)。
flushCacheIfRequired(ms);
/*
* 如果当前语句使用缓存,且没有设置结果处理器
* 如果mapper中的语句配置了useCache=false(默认值是true),则表示关闭二级缓存。
*/
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;
}
}
// 委托给普通执行器查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}public BoundSql getBoundSql(Object parameterObject) {
// 通过SqlSource来创建BoundSql对象,传入实际参数,处理动态SQL
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 获取SQL中的#{}占位符对应的参数信息,被封装为ParameterMapping对象
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
/*
* 什么情况下为空? (如果XML内容中没有#{}占位符,就为empty,倒不会为null,因为有初始化)
* 这里parameterMap返回的ParameterMapping是空列表,具体参考上面Builder内部类的初始化操作。
*
* MyBatis已经不再推荐在sql标签上设置parameterMap属性,实际工作中也基本不会使用该属性
* 所以这里基本上可以理解为无效操作,可以忽略掉!
*/
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
/*
* 获取设置resultMap
* 为什么这里resultMap信息封装在ParameterMapping中的?
* 参考SqlSourceBuilder中的ParameterMappingTokenHandler内部类中的buildParameterMapping方法
*
* 另外可以忽略这里,暂时不知道什么场景会在#{}中指定resultMap。
*/
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}CachingExecutor是MyBatis中的二级缓存实现类。Cache对象是从MappedStatement对象中获取的,但实际上是一个namespace下的所有语句共用一个Cache对象。需要mapper文件中配置有<cache />标签二级缓存才会生效,否则MappedStatement中的引用的cache对象是null。 另外,如果语句标签中配置有flushCache属性且设置为true,则在该语句执行前会清空二级缓存,实际上该属性还会清空一级缓存(见下面介绍)。 另外,还需要语句中的useCache属性为true二级缓存才会生效,只不过该属性默认是true,如果需要对语句级别禁用二级缓存,则可以调整为false。
总结一下二级缓存的开关:
- cacheEnabled:主配置,影响全局;
- <cache />标签,影响namespace;
- useCache属性,影响语句;
这三个开关影响的粒度依次降低,可以很灵活地实现二级缓存开启和关闭。
对于MappedStatement中的Cache对象,这里做简要介绍,主要是在MyBatis的启动阶段,扫描mapper文件时会创建MappedStatement语句,并设置Cache对象。
private void cacheElement(XNode context) {
if (context != null) {
// 解析type属性
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 解析eviction属性
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 解析flushInterval属性
Long flushInterval = context.getLongAttribute("flushInterval");
// 解析size属性
Integer size = context.getIntAttribute("size");
// 即欸新readOnly属性
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 解析blocking属性
boolean blocking = context.getBooleanAttribute("blocking", false);
// 获取子节点配置
Properties props = context.getChildrenAsProperties();
// 构建Cache对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}private String currentNamespace;
private final String resource;
private Cache currentCache;
private boolean unresolvedCacheRef; // issue #676public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
// 构建Cache对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 将Cache对象添加到配置中
configuration.addCache(cache);
currentCache = cache;
return cache;
}// 该方法其实就是在创建一个MappedStatement对象,然后把各种属性设置在其中
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 = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建用于构建statement的builder对象,其实这里往builder上设置的属性都会设置到MappedStatement对象中。
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对象
MappedStatement statement = statementBuilder.build();
/*
* 添加statement
* 其实在启动时的解析阶段,就是为每个语句创建MappedStatement对象;
* 在运行时,Executor从Configuration中获取MappedStatement对象,然后通过其内部的SqlSource对象来创建BoundSql对象。
*/
configuration.addMappedStatement(statement);
return statement;
}最后要注意,二级缓存的数据不是缓存到Cache对象中的,而是存储在一个大Map全局对象中的,Cache只不过是作为namespace级别的key,而在上面query方法中创建的CacheKey对象则相当于是语句级别的key。MyBatis的二级缓存就是这样一个两层缓存结构。
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) {
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) {
return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
}
}
如果二级缓存未命中,或者没有开启二级缓存,则通过delete对象来执行查询(委托模式)。
BaseExecutor
这是一个抽象类,是SimpleExecutor等执行器的父类,其内部实现了CachingExecutor委托调用的query方法,该方法主要是实现了一级缓存(也被成为local缓存,即本地缓存)。各子类继承了该方法,只需实现缓存未被命中时的底层查询过程。
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 <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
/*
* 获取BoundSql对象
* 每次执行都要获取一次BoundSql对象,是因为MyBatis的动态SQL机制,不同参数对应了不同的SQL内容,所以每次执行时才获取。
* 这里内部是通过MappedStatement中的SqlSource来获取的,这一步是分析MyBatis动态SQL的入口。
*
* 如果mapper方法有多个参数,那么这里只有一个Object类型的parameter参数是什么情况?
* 是因为在上层的MapperMethod中会根据参数进行封装,不同情况会有不同的封装,为了兼容,这里使用Object。
*/
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存键
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.");
}
/*
* 清空本地(一级)缓存
* 如果mapper中的语句配置了flushCache=true(默认值是false),则首次执行查询时清空一级缓存,后续的嵌套执行产生的缓存取决于localCacheScope。
* 这里的queryStack == 0用于判断当前查询是否没有被嵌套在其他查询中。
* 如果存在嵌套,是怎么触发嵌套查询的?以及执行流程是怎样的(是怎么执行到这里的)?
*/
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) {
// 处理延迟加载
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空延迟加载对象
deferredLoads.clear();
/*
* 如果一级缓存的级别是语句,则清空(为什么这里要清空,清空了岂不是缓存就没意义了?)
* 如果在主配置中将缓存的scope设置为STATEMENT,那么表示禁用一级缓存,
* 因为上面的queryFromDatabase方法中会将查询结果设置到一级缓存中,所以这里要清空。
* 其实设置一级缓存时就应该判断是否开启了一级缓存,没开启就不用设置,避免无效操作。
*/
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}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 {
// 执行数据库查询,这是一个模板方法,参考默认的执行器SimpleExecutor中的实现
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 abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;在上面介绍溯源Executor时,提到了是在SqlSessionFactory中创建SqlSession对象时创建的Executor对象。所以验证了“MyBatis中的一级缓存与SqlSession有关”的说法。 在query方法中可以看到,如果主配置中的localCacheScope属性是STATEMENT,那么会禁用一级缓存,即每次执行语句时,都会清空localCache。另外,正如上面介绍二级缓存所说到的,如果语句配置了flushCache属性为true,那么也会清空localCache。
一级缓存PerpetualCache内部也有一个Map属性,用来存储数据。
public class PerpetualCache implements Cache {
private final String id;
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();
}
}在query方法中实现了一级缓存的访问,在queryFromDatabase方法中实现了一级缓存的写入。如果一级缓存未被命中,那么则会调用doQuery方法,这是一个模板方法,由各子类来实现。
SimpleExecutor
@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来处理SQL语句
stmt = prepareStatement(handler, ms.getStatementLog());
// 执行查询
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取JDBC连接
Connection connection = getConnection(statementLog);
// 处理语句
stmt = handler.prepare(connection, transaction.getTimeout());
// 为语句设置参数
handler.parameterize(stmt);
return stmt;
}该方法主要执行三个操作:
- 创建StatementHandler(MyBatis四大核心接口之一)对象;
- 创建Statement对象;
- 为Statement对象设置参数;
- 执行语句;
创建StatementHandler对象
// 创建语句处理器
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 对语句处理器应用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}public class RoutingStatementHandler implements StatementHandler {
/*
* 所有的操作都委托给该对象来进行,目的是通过一个对象来封装三种不同类型的对象。
*/
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();
}
}这里创建的其实是RoutingStatementHandler类型的对象,内部封装了实际的StatementHandler对象,这样设计的目的是封装三种不同类型的StatementHandler,本文只会分析PreparedStatementHandler,这也是大多数情况。
创建Statement对象
先是创建了JDBC中的Connection对象(终于接触到JDBC了),然后交给StatementHandler用来创建Statement对象。 三种StatementHandler的prepare实现都在抽象父类BaseStatementHandler中。
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 创建和初始化statement对象
statement = instantiateStatement(connection);
// 设置语句超时时间
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}protected abstract Statement instantiateStatement(Connection connection) throws SQLException;核心步骤在instantiateStatement方法中,这是个模板方法,由子类实现。
/*
* 都是通过JDBC的Connection的prepareStatement方法来创建java.sql.PreparedStatement对象
*/
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
// 获取SQL字符串,参数位置由占位符?代替,后续步骤会调用下面的parameterize方法来用实际参数替换占位符
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);
}
}
可以发现,这里终究还是通过Conection对象创建的Statement对象。
为Statement对象设置参数
@Override
public void parameterize(Statement statement) throws SQLException {
// 通过参数处理器来设置参数
parameterHandler.setParameters((PreparedStatement) statement);
}protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 创建参数处理器
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// 创建结果集处理器
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}实际的参数设置操作是交给ParameterHandler来进行的。
@Override
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);
/*
* 这个mode很少用,一般与存储过程有关,当然没用时getMode返回null,所以这里也是满足条件的
*/
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())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取TypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 通过typeHandler来设置参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}这里会替换PreparedStatement中的参数占位符,这里涉及到了ParameterMapping和TypeHandler,这两个对象是MyBatis中SQL解析阶段会涉及到的两种类型,这里暂时不用关注是怎么来的。接着看TypeHandler内部的参数设置。
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;万变不离其宗,这里仍然是调用ParameterMapping的方法来设置的参数。哪怕是子类实现的setNonNullParameter方法中,仍然如此。
执行语句
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行查询,进入JDBC的范畴了
ps.execute();
// 使用ResultSetHandler来处理结果
return resultSetHandler.handleResultSets(ps);
}终于,执行的主体从这里开始从MyBatis进入底层JDBC。 JDBC执行结束后,MyBatis利用ResultSetHandler来处理结果集,这部分也比较复杂,后续单独开一篇文章来介绍。
总结
本文介绍了MyBatis的mapper方法的执行主体过程,调用链整体还是比较长的,所以本文分为三个部分来介绍。在统一拦截层介绍了mapper方法实参名称的处理;在SqlSession层介绍了MyBatis与Spring事务的整合;在Executor层介绍了MyBatis一二级缓存的实现原理。后续的动态SQL处理和结果集映射分析都会基于本文。