目的
分析在sql
执行时,mybatis
的底层做了哪些处理,为啥mapper
接口能执行?这就是本文的目标,mapper
执行过程都有哪些关键信息。
如果需要了解mybaits
启动流程,整体架构情况可以看另外一篇mybatis 启动流程分析(原理)
mapper
接口的动态代理实现
在使用mapper
对象的时候,我们只是定义了接口,并没有实现类,但是在实际项目我们却是直接使用接口的。这里就涉及到动态代理的相关知识。本文的分析中,我们是通过
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
的方式获得动态代理对象。如果是mybaits
和spring
结合使用,那么这些mapper
接口的动态代理过程就交给了spring
来控制。我们只需要在用的地方通过注解@Autowire
引入mapper
即可。下图展示了动态代理实现的调用流程。
我们看看实现动态代理的部分核心代码
// 1
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
// 2
protected T newInstance(MapperProxy<T> mapperProxy) {
//用JDK自带的动态代理生成映射器
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
这里我们看到最底层还是调用了JDK动态代理方式生成代理对象。我们知道代理模式一般是将原类进行增强,比如说增加了日志,增加了事务,增加了切面等。那么这里增加了什么呢?我们看看这个增强对象MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
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) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行
return mapperMethod.execute(sqlSession, args);
}
//去缓存中找MapperMethod
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//找不到才去new
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
动态代理的原理这里不多说,可以参考本人的另外一篇文章Spring Aop JDK动态代理实现原理分析(源码) 深入讨论,简而言之就是代理对象在程序运行时才生产,这个对象实现了目标接口(这里是mapper
对象),并且持有MapperProxy
的引用,在几乎每个方法中都会调用下MapperProxy
的invoke
方法。下图证明了mapper
接口生成了动态代理对象。
如何执行
我们前面的文章已经分析了在启动阶段,mybatis
是如何将mapper
里面的所有接口转成mappedstatement
存储到Configuration
对象中保存。
而且存储在一个map
中,key
就是我们的方法签名。所有我们需要从map
中拿到对应的mappedstatement
。
//从Configuration对象中拿到对应的mappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
ms中存在一些sql的信息,保存sql语句,参数类型等,接下来就需要了解如何通过传入的参数,动态创建sql。
这里一共3个大步骤
- 1、创建
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;
}
- 2、准备
Statement
包括获得数据库连接、参数设置等
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//实例化Statement
statement = instantiateStatement(connection);
//设置超时
setStatementTimeout(statement);
//设置读取条数
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);
}
}
然后是给preparedStatement
设置参数,这个和我们jdbc
一样的顺序
//设置参数
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
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) {
//如果不是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) {
//若参数为null,直接设null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
//若参数有相应的TypeHandler,直接设object
value = parameterObject;
} else {
//除此以外,MetaObject.getValue反射取得值设进去
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
//不同类型的set方法不同,所以委派给子类的setParameter方法
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
- 3、执行sql (基本也和原生差不多)
@Override
public int update(Statement statement) throws SQLException {
//调用PreparedStatement.execute和PreparedStatement.getUpdateCount
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
至此一条完整的sql就执行完了,还有很多细节我们可以深入研究,但是这里就不详细展开了。后续继续跟进。