目录
1. JdkDynamicAopProxy#invoke
2. ReflectiveMethodInvocation.proceed()
3. invokeJoinpoint
3.1 SqlCommand
3.2 MethodSignature
4 MapperMethod#execute
4.1 代理方法执行
4.2 目标方法执行
4.2.1 准备查询事宜
4.2.2 动态SQL替换
4.3 prepareStatement
4.4 执行和返回
5. 附录:项目文档
前面两篇系列文章已经详细介绍了Mapper接口的扫描过程和对应bean的创建过程。本文重点介绍mybatis执行SQL的过程。
DaoService的selectByName方法,拿到的personMapper对象已经是上文介绍的那个superMapperProxy对象。执行personMapper.selectByName(personName)方法,入参personName是"Hodey",其实就是执行的superMapperProxy的InvocationHandler.invoke(Object proxy, Method method, Object[] args)方法。
public String selectByName(String personName){
PersonInfo personInfo = personMapper.selectByName(personName);
return personInfo.toString();
}
我们进入JdkDynamicAopProxy的invoke方法(代理类为什么是JdkDynamicAopProxy,请参见上一篇文章)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
// 如果原始方法是equals(),hashcode(),则直接执行代理类的对应方法返回。
// 如果原始方法所在类实现了DecoratingProxy.class接口,做相应的处理后返回类对象
// 如果原始方法是Advised.class的实现类,那就直接返回对应的代理结果。
// 以上4条并不重要!读不懂不影响后续的理解。
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
// 主流程不会进入该分支
if (this.advised.exposeProxy) {
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
target = targetSource.getTarget();
Class> targetClass = (target != null ? target.getClass() : null);
// 根据配置,为给定方法获取一个拦截表。
// persistenceExceptionTranslationInterceptor
List
TargetSource targetSource就是Mapper的代理对象MapperProxy@5773。try语句块中的前4个if..else分支不重要,读不懂没关系。先往下走,this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)根据配置,为给定方法method获取一个拦截表。这里获取到的chain中只有一个拦截器persistenceExceptionTranslationInterceptor,这个拦截器就是为代理targetSource而生的。接下来的invocation对象仅仅是将proxy, target, method, args, targetClass, chain这些参数用ReflectiveMethodInvocation类型封装起来。retVal = invocation.proceed()才是重点需要介绍的方法,它处理完成后会得到SQL查询的结果。最后通过method.getReturnType()拿到所需的查询对象类型,将得到的结果转换成该返回值类型后返回上层完成结果的输出。
public Object proceed() throws Throwable {
// 这里其实就是找到当前需要哪个拦截器去执行
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// 重点在这里
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
// 因为persistenceExceptionTranslationInterceptor没有实现InterceptorAndDynamicMethodMatcher,故会走else分支
// PersistenceExceptionTranslator是异常处理器用于处理DataAccessException 。如果后续流程中出现了异常,那么该拦截器可以进行相应的异常转换处理。
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
return proceed();
}
}
else {
// PersistenceExceptionTranslationInterceptor.invoke处理完成后又回走进该方法,但是那个时候currentInterceptorIndex=0了。
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
process方法比较绕,处理过程也很“奇葩”。从整体上看作者是使用了责任链模式+观察者模式的实现。下面我们一起来分析这个奇葩的实现:
this.currentInterceptorIndex默认值是-1。假如interceptorsAndDynamicMethodMatchers.size=1。那就会先去执行this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex)即get(0)的那个dm.interceptor.invoke(this)。然后调用最后一行((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this)。实际执行的是PersistenceExceptionTranslationInterceptor的invoke方法。
// org.springframework.dao.support.PersistenceExceptionTranslationInterceptor#invoke public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } // 这估计是我第一次在系列文章中对异常进行保留和注释 // 只要后续过程中有RuntimeException ex发生,那么PersistenceExceptionTranslationInterceptor就能进行相应的处理 catch (RuntimeException ex) { // Let it throw raw if the type of the exception is on the throws clause of the method. if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) { throw ex; } else { PersistenceExceptionTranslator translator = this.persistenceExceptionTranslator; if (translator == null) { translator = detectPersistenceExceptionTranslators(this.beanFactory); this.persistenceExceptionTranslator = translator; } throw DataAccessUtils.translateIfNecessary(ex, translator); } } }
该方法在正常情况下啥也不做,直接调用mi.proceed()方法回到上面的proceed方法中。因为proceed方法是第二次进入了,currentInterceptorIndex的值从-1变为了0。这样使得this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1成立,所以流程会进入invokeJoinpoint()方法。PersistenceExceptionTranslationInterceptor的目的仅仅是尝试catch下层返回的数据库操作异常,然后将其异常信息进行转化后再向上抛出。
protected Object invokeJoinpoint() throws Throwable {
// target = MapperProxy对象
// method = com.Hodey.analysemvc.dao.mapper.PersonMapper#selectByName
// arguments = "Hodey"
return AopUtils.invokeJoinpointUsingReflection(this.target, this.method, this.arguments);
}
public static Object invokeJoinpointUsingReflection(@Nullable Object target, Method method, Object[] args)
throws Throwable {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
// 最终会执行到这个org.apache.ibatis.binding.MapperProxy#invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这里
return mapperMethod.execute(sqlSession, args);
}
最终调用的invoke方法是Mapper代理对象MapperProxy的invoke方法。第一次调用selectByName方法时cachedMapperMethod方法会创建一个MapperMethod对象用于封装{mapperInterface(mapper接口类型), method(调用方法selectByName),mybatis的配置信息},并且额外创建了SqlCommand对象和MethodSignature对象。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public SqlCommand(Configuration configuration, Class> mapperInterface, Method method) {
// selectByName
final String methodName = method.getName();
// interface PersonMapper
final Class> declaringClass = method.getDeclaringClass();
// 获取在configuration.mappedStatements map中缓存的MappedStatement对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
}
} else {
//com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
name = ms.getId();
// SELECT
type = ms.getSqlCommandType();
}
}
resolveMappedStatement方法是从代理对象的configuration.mappedStatements中获取MappedStatement对象ms。然后使用ms初始化SqlCommand成员属性name=com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName, type=SELECT。
public MethodSignature(Configuration configuration, Class> mapperInterface, Method method) {
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);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
// 参数解析器
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
MethodSignature对象主要是封装参数和返回类型的。比如返回值类型returnType设置为PersonInfo。paramNameResolver是一个参数解析器,它维护了一个SortedMap
aMethod(@Param("M") int a, @Param("N") int b) -----> map{{0, "M"}, {1, "N"}}
aMethod(int a, int b) -----> {{0, "0"}, {1, "1"}}
创建完成后将其缓存到MapperProxy对象的methodCache map中,key是方法名selectByName。然后调用mapperMethod.execute(sqlSession, args)。
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);
// statement:com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
// parameter:Hodey
// sqlSession: sqlSessionTemplate
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
return result;
}
根据MethodSignature对象的属性类型,选择不同的执行策略执行。method.convertArgsToSqlCommandParam(args)会根据arges入参利用paramNameResolver.getNamedParams转换成Sql命令参数。然后执行result = sqlSession.selectOne(command.getName(), param)。sqlSession是sqlSessionTemplate,入参是:
statement:com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName
parameter:Hodey
sqlSession.selectOne方法内部实际调用的是代理对象的sqlSessionProxy的selectOne(statement, parameter)方法。sqlSessionProxy的SqlSessionInterceptor,所以我们进入SqlSessionTemplate.SqlSessionInterceptor#invoke方法
public T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy. selectOne(statement, parameter);
}
//org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
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) {
//如果开启了事务,那就减去sqlSessionHolder的引用计数。如果没有开启事务,那就直接关闭创建的DefaultSqlSesstion对象
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
getSqlSession方法会获取或者创建一个SqlSession对象,默认会返回一个DefaultSqlSession对象。这个对象中封装了configuration,executor,dirty,autoCommit属性。其中executor分为3种:SimpleExecutor,ReuseExecutor和BatchExecutor。SimpleExecutor是默认的,每次执行完成后都会关闭Statement对象;ReuseExecutor与SimpleExecutor的区别是每次执行完成后都不会关闭Statement对象;BatchExecutor是批处理执行器,可以缓存多个Statement后一并发送处理拿回结果。可以在配置文件中添加mybatis.executor-type=reuse类切换mybatis的执行器。如果开启了缓存(默认开启),那么不管是哪种执行器,在封装到DefaultSqlSession之前都会被包装成CachingExecutor,使新创建的executor被缓存起来方便下次使用。
在getSqlSession方法中还创建了一个SqlSessionHolder holder对象。它和事务相关,用于在事务中获取defaultSqlSession对象。如果有事务存在,那么他会和defaultSqlSession绑定,事务的引用计数会+1。如果下次SQL执行还在这个事务中,则会通过holder对象获取到正在执行的事务所对应的defaultSqlSession,事务引用计数会再次+1。如果下一次SQL执行开启了新事务(或者没有事务),那么则会重新创建defaultSqlSession和对应的SqlSessionHolder holder。
接下来就到了method.invoke(sqlSession, args),即原始目标方法sqlSession.selectOne(command.getName(), param)执行了。
public T selectOne(String statement, Object parameter) {
List list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else {
return null;
}
}
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// ms中啥都有, parameter是入参Hodey, rowBounds是limit的值,这里没有就是默认值{0, 2147483647},没有结果就返回null
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();
}
}
入参statement是com.Hodey.analysemvc.dao.mapper.PersonMapper.selectByName,在configuration中获取之前缓存好的MappedStatement对象。上一篇文章《【二】MyBatis-Spring最全源码详解之Mapper的自动注入》已经介绍了MappedStatement对象被缓存的时机,这里就不再赘述了。
接下来执行executor.query方法。
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 缓存一下执行脚本和参数,这里也还没替换动态sql
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 拿到这些信息就可以执行了。跳过中途一些啰啰嗦嗦的流程,直接进入到干货所在地org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 要替换的参数表
List parameterMappings = boundSql.getParameterMappings();
// 如果没有placeholder或者入参列表没有值,那就不用替换或者说没法替换了。
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// 这里检查嵌套的result maps,没有进行动态sql替换
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
ms.getBoundSql方法会新建一个BoundSql对象。它内部存放了动态SQL的基本信息。比如:
sql = "select * from person where name=?;"
parameterMappings = 参数信息,比如参数名personName,参数类型String等等
parameterObject = "Hodey"
additionalParameters = 空对象
随后createCacheKey方法缓存执行脚本和参数。然后执行另外一个重载的query方法,我们跳过中途一些啰啰嗦嗦的流程,最终执行到
public List 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 list;
try {
queryStack++;
// 尝试拿到缓存的结果
list = resultHandler == null ? (List) 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();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
首先尝试看看之前是否有执行过相同的SQL语句,如果有则不用去数据库查询了。否则需要执行queryFromDatabase方法去真正的盘上查数据。
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
queryFromDatabase方法进来就将cacheKey作为key,一个占位对象EXECUTION_PLACEHOLDER作为值加入了localCache中缓存起来,即所谓的mybatis一级缓存。再查询完成后,再将结果替换掉EXECUTION_PLACEHOLDER。执行查询的方法是在list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql)中,走进去瞧瞧。
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 在里面创建StatementHandler。
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 完成动态sql的placeholder的参数替换
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
doQuery方法会新创建一个StatementHandler handler对象。我们知道JDBC的Statement是用来执行SQL语句的载体。StatementHandler顾名思义是mybatis用来和JDBC打交道的。
public interface StatementHandler {
Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;
void parameterize(Statement statement) throws SQLException;
void batch(Statement statement) throws SQLException;
int update(Statement statement) throws SQLException;
List query(Statement statement, ResultHandler resultHandler) throws SQLException;
Cursor queryCursor(Statement statement) throws SQLException;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
StatementHandler有三种:SimpleStatementHandler(没有预编译sql语句功能),PreparedStatementHandler(有预编译sql语句功能),CallableStatementHandler(存储过程相关)。默认是创建PreparedStatementHandler。然后执行prepareStatement方法。
prepareStatement主要有2个重要功能:1.预编译SQL,提高重复或相似SQL语句的执行效率。2.规避SQL注入风险。
// SimpleExecutor的prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取数据库连接
Connection connection = getConnection(statementLog);
// 处理出数据库需要的Statement
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
// 返回是动态SQL的占位符已经被替换了,变成了最终可执行的SQL语句。
return stmt;
}
最终在handler.parameterize方法中完成了动态SQL的替换。
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 拿出需要替换的参数:place holder是personName, 类型是String
List 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;
// personName
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) { //parameterObject = "Hodey"
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// value = "Hodey"
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// typeHandler类型是String
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 这行代码将动态SQL替换为了最终需要执行的语句:select * from person where name='Hodey';
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (Exception e) {
throw new XXX;
}
}
}
}
}
执行完prepareStatement方法,将动态SQL的参数替换后回到doQuery方法,就调用handler.
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 下面交给数据库进行查询
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
ps.execute()方法会就调用数据库的方法实现SQL语句。结果会封装在ps对象中返回。getFirstResultSet方法是从stmt中获取原始的返回值。然后封装到ResultSetWrapper对象中。该对象中封装了数据库的返回结果,和对应在数据库中的属性。比如:columeNames依次存放的值是:id,name,age。 分别对应查询结果在person表中的列名。classNames依次存放的是:java.lang.Integer, java.lang.String, java.lang.Integer。 分别对应查询结果类型。jdbcTypes依次存放的是:INTEGER, VARCHAR, INTEGER。 分别对应查询结果在person表中的字段类型。之后新建一个DefaultResultHandler来对结果进行整合,将结果按照用户自定义的类型返回。如果结果只有1个,就返回单个对象,否则以List的数据结构返回。数据库结构如下
执行完成返回结果的过程中,当返回到doQuery方法时会关闭prepareStatement对象和持有的jdbc连接。返回到SqlSessionInterceptor#invoke方法后,会执行事务的提交sqlSession.commit(true)。最后会在finally方法中执行closeSqlSession方法,会执行session.close()方法,将sql执行过程中创建的DefaultSqlSession对象关掉。这也是就是直接导致所谓的mybatis一级缓存失效的本质原因!为什么mybatis-spring会主动关闭这个session对象呢?
我的理解是:如果这里mybatis-spring放过了关闭session的时机。那么在返回结果给到用户的应用层后,还需要给用户商量:“兄弟,你看我都把结果给您了,您行行好帮我把session关了呗?”其实这个要求用户主动关闭session的约定是很难奏效,或者说是不符合用户编程习惯的。首先很多用户不知道mybatis-spring是否帮我关闭了session,其次如果在查询过程中有异常,那么到底是在创建DefaultSqlSession之前就发生了异常还是之后才发生的呢?如果发生在DefaultSqlSession创建之前就产生了异常,那么关闭一个本就不存在的session的后果又会是什么?如果异常在DefaultSqlSession对象创建之后发生,那么异常情况下没有回滚之前的状态,那就是一个错误的编程范例!所以mybatis-spring的作者直接帮用户关闭了session。奥卡姆的刮胡刀了解一下?
最终在一切正常的情况下,查询的数据就返回到用户的应用层程序了。到此SQL执行的流程分析结束。虽然本文以最简单的select语句进行查询,但是整个MyBatis-Spring所经历的流程却是大同小异的。整个流程同样适用于update,insert和delect等流程,这几个就留给读者自行分析了。
到此MyBatis-Spring的源码分析系列文章就写完了。感谢您的阅读,希望对您有所收获。
【一】MyBatis-Spring最全源码详解之@MapperScan到底在弄啥 |
https://blog.csdn.net/wuyuwei/article/details/88539725 |
【二】MyBatis-Spring最全源码详解之Mapper的自动注入 |
https://blog.csdn.net/wuyuwei/article/details/88547585 |
【三】MyBatis-Spring最全源码详解之SQL执行流程 | 本文 |
@MapperScan("com.Hodey.analysemvc.dao")
@SpringBootApplication
public class AnalyseMvcApplication {
public static void main(String[] args) {
SpringApplication.run(AnalyseMvcApplication.class, args);
}
}
@Service
public class DaoService {
@Autowired
private PersonMapper personMapper;
public String selectByName(String personName){
PersonInfo personInfo = personMapper.selectByName(personName);
return personInfo.toString();
}
public List selectAll(){
List personInfoList = personMapper.selectAll();
return personInfoList;
}
}
@Repository
public interface PersonMapper {
@Results(id = "personMap", value = {
@Result(property = "id", column = "id"),
@Result(property = "personName", column = "name"),
@Result(property = "age", column = "age")
})
@Select("select * from person where name=#{personName}; ")
public PersonInfo selectByName(String personName);
@Select("select * from person;")
@ResultMap(value = "personMap")
public List selectAll();
}
public class PersonInfo {
private int id;
private String personName;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "PersonInfo{" +
"id=" + id +
", personName='" + personName + '\'' +
", age=" + age +
'}';
}
}
@RestController
@RequestMapping("/dao")
public class DaoContorller {
@Autowired
private DaoService daoService;
@RequestMapping(value = "/getPersonName", method = RequestMethod.GET)
public String getPersonName(@Param("personName") String personName){
return daoService.selectByName(personName);
}
@RequestMapping(value = "/getAll", method = RequestMethod.GET)
public List getAll(){
return daoService.selectAll();
}
}
application.properties
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=******
spring.datasource.password=******
数据库:
执行结果:
PersonInfo{id=1, personName='Hodey', age=17}