mybatis源码分析—运行流程及原理

Posted 算法技术猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis源码分析—运行流程及原理相关的知识,希望对你有一定的参考价值。


1、配置配置文件,扫描mapper文件  

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="configLocation" value="classpath:XXX.xml"/>
</bean>


2、配置SqlSessionFactoryBean(创建mybatis的工厂类),这里dataSource的配置就不贴出来了  

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="XXX.mapper" />
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>

SqlSessionFactoryBean类afterPropertiesSet方法在执行之前所有属性都已设置完成,其功能是校验dataSource(配置文件配置的)和sqlSessionFactoryBuilder(默认new出来的一个新对象),然后创建sqlSessionFactory(后面再讲这个)  。

启动服务时,就会将所有的mappedStatements加载到sqlSession中了  


3、将扫描的mapper接口都一一进行注册代理,调用流程如下: MapperFactoryBean.getObject() --> SqlSessionTemplate.getMapper(Class<T> type) --> Configuration.getMapper(Class<T> type, SqlSession sqlSession) --> MapperRegistry.getMapper(Class<T> type, SqlSession sqlSession)   

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
if (!knownMappers.contains(type))
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
 try {
return MapperProxy.newMapperProxy(type, sqlSession);
 } catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
 }
}

MapperProxy类代理代码:MapperProxy.newMapperProxy(type, sqlSession);`

public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
ClassLoader classLoader = mapperInterface.getClassLoader();
 Class<?>[] interfaces = new Class[]{mapperInterface};
 MapperProxy proxy = new MapperProxy(sqlSession);
 return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
}

 将所有Mapper接口都进行代理后,后续调用接口中的方法时,都会进入到MapperProxy.invoke()中执行相应逻辑 。


4、启动服务完成后,调用mapper接口时,MapperProxy会自动进行代理,调用invoke方法(因为在服务启动时,就将所有的Mapper接口设置了MapperProxy代理)  

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
 }
//通过代理类和方法名(全路径方法名) 获取Mapper接口类
final Class<?> declaringInterface = findDeclaringInterface(proxy, method);
 //设置statement、sqlSession、declaringInterface等,为执行sql做准备
 final
MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
 //执行sql,并返回结果  
 final
Object result = mapperMethod.execute(args);
 if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) {
throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
 }
return result;
}

MapperMethod类中execute方法,具体执行操作类;如果返回的是list,则对应的是returnsMany,返回的是Map,则对应的是returnsMap  

public Object execute(Object[] args) {
Object result = null;
 if (SqlCommandType.INSERT == type) {
Object param = getParam(args);
   result = sqlSession.insert(commandName, param);
 } else if (SqlCommandType.UPDATE == type) {
Object param = getParam(args);
   result = sqlSession.update(commandName, param);
 } else if (SqlCommandType.DELETE == type) {
Object param = getParam(args);
   result = sqlSession.delete(commandName, param);
 } else if (SqlCommandType.SELECT == type) {
if (returnsVoid && resultHandlerIndex != null) {
executeWithResultHandler(args);
   } else if (returnsMany) {
result = executeForMany(args);
   } else if (returnsMap) {
result = executeForMap(args);
   } else {
Object param = getParam(args);
     result = sqlSession.selectOne(commandName, param);
   }
} else {
throw new BindingException("Unknown execution method for: " + commandName);
 }
return result;
}

最后具体执行sql语句都是调用sqlSession中的方法:sqlSession.insert、sqlSession.update、sqlSession.delete、sqlSession.select、sqlSession.<E>selectList、sqlSession.selectOne等。 

 

5、sqlSession执行具体sql操作过程  

执行sqlSession.<E>selectList时,需要到SqlSession的实现类(SqlSessionTemplate)中调用方法,注意看下面代码,这里面是通过sqlSessionProxy来调用selectList,说明在此做了一个代理  

public <E> List<E> selectList(String statement, Object parameter) {
return this.sqlSessionProxy.<E> selectList(statement, parameter);
}

进入SqlSessionTemplate类后,此类有个构造方法

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
   PersistenceExceptionTranslator exceptionTranslator) {

notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
 notNull(executorType, "Property 'executorType' is required");

 this.sqlSessionFactory = sqlSessionFactory;
 this.executorType = executorType;
 this.exceptionTranslator = exceptionTranslator;
 this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
     new Class[] { SqlSession.class },
     new SqlSessionInterceptor());
}

看最后一句代码,sqlSessionProxy新创建了一个代理实例,执行具体sqlSessionProxy中的方法(sqlSessionProxy.<E> selectList)时,进入到SqlSessionInterceptor.invoke()方法中   

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 //获取一个sqlSession
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
     SqlSessionTemplate.this.executorType,
     SqlSessionTemplate.this.exceptionTranslator);
 try {
//真正执行selectList方法
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) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
     if (translated != null) {
unwrapped = translated;
     }
}
throw unwrapped;
 } finally {
//执行完成后,需要关闭SqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
 }
}

在invoke方法中,首先需要获取一个SqlSession,通过sessionFactory.openSession(executorType)获取,该代码在SqlSessionUtils类中

SqlSession session = sessionFactory.openSession(executorType);

debug到上面的Object result = method.invoke(sqlSession, args);后,继续往下运行,就进入了DefaultSqlSession中的selectList中  

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
   List<E> result = executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
   return result;
 } catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
 } finally {
ErrorContext.instance().reset();
 }
}

此处代码`executor.<E>query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);`继续执行。如果在配置文件中配置有Plugin,则会进入到你配置的插件类中, 在进入配置的插件类中之前,会到代理工具类Plugin(org.apache.ibatis.plugin.Plugin.java)里面   

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
   if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
   }
return method.invoke(target, args);
 } catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
 }
}

然后才通过interceptor.intercept(new Invocation(target, method, args))进入到你配置的插件类里面去,直到配置的插件类代码执行完毕后,才真正执行`executor.<E>query()`方法,此时继续运行,则进入到BaseExecutor中的query()方法

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();
   }
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
   }
}
return list;
}

注意看,这里会先从本地缓存中获取你想要的结果,这个key的值对应的就是namespace+statementId+sql语句,如果缓存中没有(需要将缓存功能开启后,才会将结果缓存到本地缓存中),再去查询数据库queryFromDatabase()

在queryFromDatabase()中doQuery()进行查询,跳到SimpleExecutor类中的doQuery()中  

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(this, ms, parameter, rowBounds, resultHandler, boundSql);
   stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>query(stmt, resultHandler);
 } finally {
closeStatement(stmt);
 }
}

在执行handler.<E>query时,会将其委托给PreparedStatementHandler执行query()  

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
 //执行查询操作
 ps.execute();
 return resultSetHandler.<E> handleResultSets(ps);
}

执行操作ps.execute()时,进入到PreparedStatement中的execute()方法,该方法中有句代码

rs = executeInternal(this.maxRows, sendPacket, doStreaming, (this.firstCharOfStmt == 'S'),
     metadataFromCache, false);

进入到executeInternal方法后,具体执行代码是

rs = locallyScopedConnection.execSQL(this, null, maxRowsToRetrieve, sendPacket,
  this.resultSetType, this.resultSetConcurrency,
  createStreamingResultSet, this.currentCatalog,
  metadataFromCache, isBatch);

继续执行此句代码,进入到ConnectionImpl类中的execSQL方法中,该方法中return执行的结果

return this.io.sqlQueryDirect(callingStatement, null, null,
     packet, maxRows, resultSetType,
     resultSetConcurrency, streamResults, catalog,
     cachedMetadata);

然后进入到mysqlIO类中的sqlQueryDirect方法,下面代码就是发送执行语句

Buffer resultPacket = sendCommand(MysqlDefs.QUERY, null, queryPacket,
     false, null, 0);

继续往下到sendCommand方法中

send(this.sendPacket, this.sendPacket.getPosition());

进入到send()方法,最后通过socket连接数据库,将需要执行的语句发给数据库服务器,让其执行,然后将结果返回给mysqlOutput,最后将结果回填回去。

执行前:

执行后:


到此,mybatis运行流程基本上就结束了。


6、总结  

通过对mybatis源码的分析,可以看出其整个流程很多地方都用了动态代理,注册Mapper接口是使用了代理,执行sqlSession中的具体操作时,也使用了代理,其他地方也有用到代理;从Mybatis的源码分析,其实其整个运行流程也不难理解,只要理解其原理后,在后续的使用中才能得心应手。


有理解不到位的地方,还望各位大佬指正


欢迎关注


以上是关于mybatis源码分析—运行流程及原理的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis源码分析select源码分析及小结

mybatis关于二级缓存的配置及源码分析

MyBatis源码分析插件实现原理

Spring事务源码分析专题Mybatis的使用及跟Spring整合原理分析

精华推荐 | 深入浅出 RocketMQ原理及实战「底层源码挖掘系列」透彻剖析贯穿RocketMQ的消费者端的运行核心的流程(上篇)

源码学习之MyBatis的底层查询原理