本次讲解的环境是在spring与mybatis整合的情况下进行的源码分析,整合的部分就不细说了。
可以看到,我们自己定义的接口其实最终成为了 mapperProxy 类型的代理对象
这个类实现了 InvocationHandler接口,我们在调用我们编写的接口方法的时候实际上就是调用了这个类的invoke方法。这个类还封装了一些信息,使用的sqlsession,我们mapper接口的Class对象,还有缓存了我们接口的方法信息。
执行的时候会进入到 mapperProxy的invoke方法里
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 这里会首先判断:执行的方法是不是来自于object类的,如果是那么直接执行就完事了
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
// 然后判断是不是default修饰的 默认方法,如果是直接执行
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 最后看mapperCache 里有没有对应的mapperMethod ,有的话直接取出来,拿去执行,没有的话就创建一个,缓存取来,然后去执行
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
这里我要额外说一下上面提到的cachedMapperMethod方法
private MapperMethod cachedMapperMethod(Method method) {
// 这里使用了map接口提供的computeIfAbsent方法
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
// 这个方法我觉得mybatis使用的很巧妙。。。
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
// 先判断给定的Function函数是不是null,是的话直接抛出异常
Objects.requireNonNull(mappingFunction);
V v;
// 之后判断这个map里根据给定的key能不能取到值
// 如果取不到,是null,就调用给定的Function的apply方法生成一个值并且这个值也不是null的话把它存起来,然后返回去,如果这个function产生的值也是null的话那么就不会保存,最终返回去的那个V也会是null
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
// 取到了当然直接返回值就可以了
return v;
}
接下来就该执行mapperMethod.execute方法了。
public Object execute(SqlSession sqlSession, Object[] args) {
// 首先这个 result 来保存最终的处理结果
Object result;
// 然后判断执行的这个sql是什么类型的,增删改查等等类型,如果没有对应上的就报错了
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;
}
由于我这个是一个查询全部的操作,所以走到了executeForMany这里,将查询到的结果返回。
再额外说一些:
可以看到,SqlSessionTemplate封装了sqlSessionFactory,ExecutorType 和 一个内部的sqlSessionProxy。注释说明SqlSessionTemplate是线程安全的,由spring管理的,可以和spring的事务关联在一起的,内部管理者与数据库打交道的session的生命周期,提交或者回滚等信息,也就是sqlSessionProxy。
/**
* Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
* unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to the
* {@code PersistenceExceptionTranslator}.
*/
private class SqlSessionInterceptor implements InvocationHandler {
// 最终 SqlSessionTemplate 操作的方法会执行到这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 首先根据sqlSessionFactory,executorType等信息创建出一个真正的SqlSession,也就是mybatis提供的DefaultSqlSession,然后管理者这个DefaultSqlSession的生命周期
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 这里开始使用DefaultSqlSession来执行了
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来提交事务,我使用的mysql不用的
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
// 出异常了就关闭连接,sqlsession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
// 最后还要将这个异常跑出去
throw unwrapped;
} finally {
// 确保最终关闭SqlSession,无论有没有异常
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
在我这个案例中事务是由spring进行管理的,所以这里你并没有看到DefaultSqlSession的关于事务的操作。原始是spring的事务管理器会根据这个方法是不是成功的执行了,如果没有抛出异常,就认为是成功了,提交事务,有异常就失败了,关闭事务。代码层面就是一个try…cache…过程。所以我们开发中service方法产生的异常如果自己try…cache了有可能导致提交了而不是回滚!
通过层层的包装,最终还是来到了mybatis的DefaultSqlSession来执行。
来到了DefaultSqlSession的selectList方法。
这里对我们的方法参数进行了一些封装,最终形成了一个map,,反回了。你可以看到对集合类型和数组类型的包装,以便于我们在写mapper.xml文件的时候进行动态的获取其中的值。
执行之后就获取到了查询结果
我们一直跟进下去,会发现最终这个执行器是SimpleExecutor,也就是mybatis的默认执行器,来调用他的query方法。
// 首先这个方法传入了必要的信息,我们的mapper,参数等等
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.");
}
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();
// 这里还判断了缓存是 session 级别的还是 statment 级别的,也就是mybatis的一级缓存和二级缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
我们再看一下 queryFromDatabase
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 {
// 这里获取到了从数据库的查询结果
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 将结果放入了缓存中 也就是一级缓存中,本质是一个 hashMap
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
这里可以看到是使用了PreparedStatement来执行,也就是JDBC api里的对象,mybatis对此进行了封装,之后将查询结果也进行了处理,最终返回给了我们。
到此一次查询就结束了!!!
我们在调用我们接口的方法时,本质上是调用了代理对象的方法(mapperProxy),进而调用了sqlSessionTemplate来执行,而它的内部维护了一个sqlsession。每次我们对数据库操作的时候都会生成一个sqlsession,而底层则是对JDBC进行了封装,从而获得处理结果。