MyBatis笔记 三 源码
Mapper代理的创建
MapperProxyFactory
类用于创建Mapper的代理,只使用了Java提供的动态代理技术。
这个类中我们能发现,实际上Mapper接口的实际方法调用被创建出来MapperProxy
接管。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// ...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
下面是MapperProxy的代码
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
// set local attributes
}
@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 if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 先通过缓存系统获取对应的MapperMethod,然后执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
MapperProxy也没有直接执行方法调用,而是又针对每个方法创建了一个MapperMethod
实例并缓存起来,再调用这个mapperMethod
,传入当前的sqlSession
和参数。
下面是MapperMethod中的execute
方法,其中用了命令模式,或者说是策略模式???根据不同的命令类型直接调用sqlSession
中对应的增删改查方法。这是还在iBatis
时留下的API,MyBatis不推荐我们直接使用这些API。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
SELECT
的情况比其他情况复杂,因为它需要返回多种类型的返回值。这里只看其中一个,executeForMany
。
不过也是根据不同情况直接调用sqlSession
的方法,并且对结果做一些转换。
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
Mapper直接调用了SqlSession中的各种增删改查方法,我们的mapper文件如何就能够直接变成增删改查方法被调用呢?
SqlSession下的四大对象
StatementHandler
使用数据库对象的各种Statement进行操作,并且在四大对象中起承上启下的作用ParameterHandler
对SQL中的参数进行处理ResultHandler
对ResultSet进行封装,返回处理Executor
调度前面三个处理器对象
Executor
MyBatis中有三种执行器
- SIMPLE,简易执行器(默认情况下的执行器)
- REUSE,一种针对
prepareStatement
的可重用执行器 - BATCH,批处理执行器,批量处理SQL语句
创建执行器时,会根据类型创建对应的执行器
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这行语句好像是注册什么插件,目前还不太懂
executor = (Executor) interceptorChain.pluginAll(executor);
随便找到SimpleExecutor
中的查询方法,其中就是基于配置来创建不同的StatementHandler
,并且执行,再使用ResultHandler
来转换结果。
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
现在我们的关注点就落在了其他Handler上
StatementHandler
先来看StatementHandler
,这个newStatementHandler
是如何工作的。
它的代码很简单,首先就是创建了一个RoutingStatementHandler
,它是干啥的一会再说,然后就是向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;
}
RoutingStatementHandler
的设计思想很巧妙,因为StatementHandler
处理SQL语句,如果语句中含有占位符,那么就需要使用JDBC的PreparedStatement
进行参数化,如果含有存储过程,那么需要使用另外的东西。
RoutingStatementHandler
提供了一个统一的接口,在其内部会自动根据语句的类型选择该使用哪种行为。我们看下它的构造器。
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());
}
}
根据语句类型的不同,它会选择三种StatementHandler的实现类来处理这些不同的行为。StatementHandler
中有两个重要的方法——prepare
和parameterize
。
Executor
会在创建StatementHandler后自动调用prepare
和parameterize
,对应代码如下:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
prepare
方法的目的就在于创建对应的JDBCStatement
对象,如果你仔细观察prepare
方法的行为,它其实是提供了一个BaseStatementHandler
作为抽象模板父类,前面三种StatementHandler都继承自这个父类,父类再将创建JDBCStatement
的工作留给子类,并做一些通用的繁杂的工作。
而parameterize
的目的在于对语句进行参数化。诶?之前不是说Executor
中的四大对象中有一个ParameterHandler
用于处理参数吗?
是,BaseStatementHandler
在构造时创建了ParameterHandler
,并通过StatementHandler的接口方法getParameterHandler
让子类获取
而不同的子类在parameterize
方法中又有不同的行为,对于SimpleStatementHandler
,它根本没有参数可处理,所以它无需处理。
对于PreparedStatementHandler
,它则是托管给了ParameterHandler
进行了参数处理
而对于存储过程相关的CallableStatementHandler
,它不仅处理了,还实现了输出参数相关的代码。
现在大概StatementHandler搞懂了,下面就是ParameterHandler。
ParameterHandler
ParameterHandler很简单,只有两个接口方法。
getParameterObject
用于获取参数对象,这里提一嘴,不管你在Mapper中怎么设置参数,是通过一个对象也好,是通过基本类型也好,通过Map也好,通过@Param
也好,最终,到了MyBatis中的参数都是一个对象。
对于基本类型,MyBatis会转换成包装类,对于@Param
,会转换成Map
setParameters
就是向语句中设置参数了。
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
MyBatis提供了一个实现类,DefaultParameterHandler
。
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
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())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
这里就是取出参数,然后通过对应的typeHandler来设置参数。
MyBatis中的所有参数设置都要经过typeHandler,包括基本类型,也会通过MyBatis默认提供并已经注册好的typeHandler
。
ResultHandler
ResultHandler
接口如下,前两个用于处理结果集,后一个用于处理存储过程相关的输出参数
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
ResultHandler的实现类是DefaultResultHandler
,其工作过程就是通过CGLIB或JAVASSIST做延迟加载,通过ObjectFactory和TypeHandl组装结果并返回。
然后这应该是一个SqlSession的运行图了