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文件如何就能够直接变成增删改查方法被调用呢?
StatementHandler
使用数据库对象的各种Statement进行操作,并且在四大对象中起承上启下的作用ParameterHandler
对SQL中的参数进行处理ResultHandler
对ResultSet进行封装,返回处理Executor
调度前面三个处理器对象MyBatis中有三种执行器
prepareStatement
的可重用执行器创建执行器时,会根据类型创建对应的执行器
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
,这个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很简单,只有两个接口方法。
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
接口如下,前两个用于处理结果集,后一个用于处理存储过程相关的输出参数
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的运行图了