对于自己每天用的东西,会用不够,并且要知其所以然。这个专题中我会随便写一些关于现在主流框架的源代码研读。
好奇心是最好的老师。
今天从mybatis写起。
我看的是3.4.3版本的mybatis源码,是结合spring-mybatis一起读的。
更重要的是大家自己去一步步跟进代码,研读
1. mapper是如何映射到一个真实可调用的类的
经过一番探索,发现mapper是使用jdk的动态代理实现的。
以前自己写过一个例子如下:
package cn.sxy.SimulateSpring.AOP.DynamicProxy.ProxyPackage;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler {
private Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object object = method.invoke(this.object, args);
after();
return null;
}
private void before() {
System.out.println("Method begin! ");
}
private void after() {
System.out.println("Method end! ");
}
public static void main(String[] args) {
SomeManager someManager = new SomeManagerImpl();
DynamicProxy dynamicProxy = new DynamicProxy(someManager);
SomeManager someManagerProxy = (SomeManager) Proxy.newProxyInstance(
someManager.getClass().getClassLoader(),
someManager.getClass().getInterfaces(),
dynamicProxy
);
System.out.println(someManagerProxy.getClass().toString());
someManagerProxy.treat();
}
}
在这段代码逻辑中,before()和after()被增强到方法本身的前后,这里先不探究jdk动态代理是怎么实现的。
我们发现这段方法中的invoke方法,决定了被增强之后的方法和原方法的关系。
经过一番调试,我们发现了MapperProxy这个类,其中invoke方法揭示了增强与被增强的关系。
@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);
}
// 这两句话是真正执行sql的地方
// 1
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 2.
return mapperMethod.execute(sqlSession, args);
}
1.1 cachedMapperMethod
然后开始看cachedMapperMethod方法。
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
这就是个经典的缓存判断代码。如果缓存里没有,重新构造一个mappermethod对象。重点是这个MapperMethod的构造方法,点进去看。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 1.
this.command = new SqlCommand(config, mapperInterface, method);
// 2.
this.method = new MethodSignature(config, mapperInterface, method);
}
于是我们发现了两句看上去非常关键的话。
这里有一个SqlCommand对象,似乎这个东西是把我们配置的xml映射过来的重要玄机。
1.1.1 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();
// MappedStatemenet里封装了和这个sql语句相关的一切信息
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
command对象,获得了name和commandType。这个commandType在后面是有用的。
1.1.2 MethodSignature
方法签名,似乎是对方法有什么操作,点进去继续看。
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
// 解析返回值的类型,把sql的type映射到javaType
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);
// https://blog.csdn.net/clementad/article/details/50589459
// 这个和mapkey注解有关系,具体可以参考上面的链接
this.mapKey = getMapKey(method);
//
this.returnsMap = (this.mapKey != null);
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 这个其实可以猜猜看,就是mapper里写的那些@Param()的映射关系,维护在这个对象里。
// 这样mybatis才能知道,怎么对应填入后面的sql语句
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
其实根据上面那些我自己写的注释,你会发现,这就是对这个方法的修饰,这些修饰在后面的真正执行sql语句的时候,都是会用上的。
这个resolveReturnType出发,可以引出mybatis的类型映射关系,这个可以以后看。
1.2. execute
经过上面的一轮查看,发现前面的方法,就是生成了方法有关的信息,生成过程是比较费时的,所以mybatis引入了缓存机制,这样下次使用这个方法的时候,就不用重新生成这些信息了。
接下来进入重头戏,execute方法.
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 下面和command以及method有关的信息,都是通过1.1中的过程生成的
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);
}
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的,并且使returnsMany的例子,我们点进去看
1.2.1 executeForMany方法
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.<E>selectList(command.getName(), param, rowBounds);
} else {
// 这里是真正执行的地方
result = sqlSession.<E>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;
}
点入真正执行的地方。我这里是在spring框架中用的mybatis,进入了mybatis-spring的部分。
发现sqlSession被代理了。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
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) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
这里控制了sql的公共流程,比如说获取session,关闭session,提交等操作,所以我们重点关注invoke里的东西即可。说白了这些是spring对mybatis的增强。
点进去会发现,又回到mybatis的模块了。
@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) {
try {
// 又是这个熟悉的东西
// 大家有兴趣可以点进去看,会发现,在xml里定义的那些ResultMap也被包装在里面了
MappedStatement ms = configuration.getMappedStatement(statement);
// 这里真正执行
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 这里是真正获取sql语句的地方,里面涉及到一个StaticSqlSource对象
// 这是在读取xml的时候就生成了的,具体初始化相关的流程,要去builder这个包下看
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// boundSql是把参数填入的sql语句对象,有了这个,就是执行sql的问题了
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
从上文我们发现,其实mybatis的真正执行过程,就是借着jdk动态代理,增强了我们的mapper接口,在初始化的时候,帮我们把sql语句,以及一些映射关系填入相关的对象中,然后执行方法的时候,通过这些对象,拿到真正的sql,交给底层和jdbc相关的模块,去执行sql语句。