Mybatis的执行流程
没有看的很仔细,描述可能有误…
以下代码拷贝自mybatis-plus的源代码,和mybatis的应该大同小异。
1,MybatisSqlSessionFactoryBean
这个类实现了FactoryBean,在调用getObject()方法的时候会返回一个SqlSessionFactory的对象,默认实现是DefaultSqlSessionFactory。
//MybatisSqlSessionFactoryBean类
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
//afterPropertiesSet()方法会调用buildSqlSessionFactory()方法,生成SqlSessionFactory对象
//这里摘出部分代码
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
//创建配置文件MybatisConfiguration对象,其继承自mybatis的Configuration类
//别名、自定义类型处理器的注册等等,并set到配置文件里面
//1、这一步是事务处理,SpringManagedTransactionFactory会和spring的事务管理进行联动
((Configuration)targetConfiguration).setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(), (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
//2、这一步会读取mapperLocations的xml文件路径,并格式化xml文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), (Configuration)targetConfiguration, mapperLocation.toString(), ((Configuration)targetConfiguration).getSqlFragments());
xmlMapperBuilder.parse();
//创建SqlSessionFactory对象并返回
SqlSessionFactory sqlSessionFactory = (new MybatisSqlSessionFactoryBuilder()).build((Configuration)targetConfiguration);
return sqlSessionFactory;
}
1-1,事务实现
Spring的注解事务机制是先生成数据库连接,设置自动提交为false,然后把连接存到ThreadLocal对象里面,方法如果需要使用数据库连接,会从这个ThreadLocal对象里面取出,这样就实现了事务的控制
本篇不针对spring的事务部分代码做过多分析
//SpringManagedTransactionFactory类
//这一步会在获取SqlSession的时候调用
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
//SpringManagedTransaction
//获取数据库连接,这一步会在执行的时候调用,后续说明
public Connection getConnection() throws SQLException {
if (this.connection == null) {
this.openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
//这一步会取出spring预先创建的数据库连接
//需要注意的是,这里的dataSource对象和事务事务管理器使用的dataSource是同一个对象,如果不是,应该会导致事务失效,简而言之,使用@Configuration,保证程序的dataSource对象都是同一个。
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> {
return "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring";
});
}
1-2,格式化xml文件
XMLMapperBuilder会把xml文件转化为MappedStatement,然后加入configuration对象中,MappedStatement的完整key的格式是namespace+“.”+id,key好像分为长key和短key,长key必须保持唯一,MappedStatements的数据存储格式为map,实现对象是mybatis的Configuration类的内部类StrictMap,继承自HashMap对象,重写了部分方法,以实现长短key的获取。
xml文件的namespace之所以写成mapper接口的全路径,是为了能把接口类加入配置文件,后续能从配置类获取对应的代理对象,之后的代理对象能顺利找到对应的sql语句。
//XMLMapperBuilder
//看的不是很明白,所以不做过多说明,总之就是把xml的文件内容加入configuration对象
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
//获取各个节点的数据,并格式化,加载进configuration对象,可以重点跟进这个方法
this.configurationElement(this.parser.evalNode("/mapper"));
//记录已加载过的资源
this.configuration.addLoadedResource(this.resource);
//绑定命名空间
this.bindMapperForNamespace();
}
this.parsePendingResultMaps();
this.parsePendingCacheRefs();
this.parsePendingStatements();
}
2,SqlSessionTemplate
自动配置类里面向spring环境注入的bean对象是SqlSessionTemplate,而非DefaultSqlSession,实测多线程下,如果不使用SqlSessionTemplate对象而是使用DefaultSqlSession,有时候会报错,没有找错误原因…
SqlSessionTemplate其实就是对SqlSessionFactory进行了一次代理,最终使用的对象还是DefaultSqlSession。
猜测是应该为了和spring的事务做关联。
//获取mapper的代理对象,这里不做@Mapper和@MapperScan的注解逻辑的分析
sqlSessionTemplate.getMapper(MapperTest2.class);
//SqlSessionTemplate,获取代理对象,这里是通过配置信息获取的
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
//Configuration,获取代理对象,这里贴的是mybatis的获取代理对象方法,mybatis-plus重写了这个方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
//MybatisConfiguration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mybatisMapperRegistry.getMapper(type, sqlSession);
}
//mapperRegistry对象和mybatisMapperRegistry对象在new MybatisConfiguration()的时候就已经初始化了
//MybatisMapperRegistry继承自MapperRegistry,逻辑很类似
//MapperRegistry类
//MapperRegistry的获取代理对象方法,最终由mapperProxyFactory生成接口的代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
//knownMappers在上述的xml解析等逻辑中会加入要处理的Mapper接口类
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
//生成代理对象,这一步mybatis-plus和mybatis的实现有些不同,逻辑都是类似的
//MybatisMapperProxyFactory生成代理对象
public T newInstance(SqlSession sqlSession) {
MybatisMapperProxy<T> mapperProxy = new MybatisMapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
//MapperProxyFactory生成代理对象
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
3,mapper执行方法
因为生成的mapper是代理对象,所以会进入代理类(MapperProxy或MybatisMapperProxy)中,执行代理方法
最终执行的是MapperMethod或MybatisMapperMethod的execute()方法
//MapperProxy类
//代理方法,按照上述逻辑,这里的sqlSession对象是sqlSessionTemplate对象,进入MapperMethod.execute()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
return Object.class.equals(method.getDeclaringClass()) ? method.invoke(this, args) : this.cachedInvoker(method).invoke(proxy, method, args, this.sqlSession);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
//走的应该是else分支,会创建一个MapperMethod对象去执行
//if分支什么时候走,不清楚....之前的版本应该不是这么设计的...
private MapperProxy.MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return (MapperProxy.MapperMethodInvoker)MapUtil.computeIfAbsent(this.methodCache, method, (m) -> {
if (m.isDefault()) {
try {
return privateLookupInMethod == null ? new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava8(method)) : new MapperProxy.DefaultMethodInvoker(this.getMethodHandleJava9(method));
} catch (InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException var4) {
throw new RuntimeException(var4);
}
} else {
return new MapperProxy.PlainMethodInvoker(new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()));
}
});
} catch (RuntimeException var4) {
Throwable cause = var4.getCause();
throw (Throwable)(cause == null ? var4 : cause);
}
}
MapperMethod类包含两个对象MapperMethod.SqlCommand和MapperMethod.MethodSignature
//MapperMethod的execute方法,代码太多,只贴出部分,以查询逻辑为例
public Object execute(SqlSession sqlSession, Object[] args) {
//转换参数
param = this.method.convertArgsToSqlCommandParam(args);
//查询,这里的sqlSession是sqlSessionTemplate对象
result = sqlSession.selectOne(this.command.getName(), param);
}
//paramNameResolver对象在new MapperMethod.MethodSignature的时候,也new 好了
//new paramNameResolver对象的时候,对@param注解进行了处理
//这一步也会对RowBounds参数进行处理,这个是mybatis提供的分页,但感觉不太好用,是从结果集中获取需要的数据
//反射好像不能拿到方法参数的名称,所以才用的注解...反正我是没拿到
//所以mapper接口方法的参数要么是一个实体类,要么是一个map对象,要么使用注解...
//spring的配置类@Bean的方法之所以能按参数名查找bean,是因为其解析了class文件,才拿到的参数名
//这个方法逻辑有点长,应该就是返回了一个实体类参数或一个ParamMap对象
public Object convertArgsToSqlCommandParam(Object[] args) {
return this.paramNameResolver.getNamedParams(args);
}
//MapperMethod.SqlCommand,这一步是为了找MappedStatement和其类型(增删改查)
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
String methodName = method.getName();
Class<?> declaringClass = method.getDeclaringClass();
//获取MappedStatement对象
MappedStatement ms = this.resolveMappedStatement(mapperInterface, methodName, declaringClass, configuration);
//省略部分代码...
}
//获取MappedStatement对象
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
//这一步可以看出,key的组成形式是接口名+"."+方法名,和上述xml文件的格式化逻辑一致
//这样,就找到了接口和实际sql语句的关系
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
} else {
Class[] var6 = mapperInterface.getInterfaces();
int var7 = var6.length;
for(int var8 = 0; var8 < var7; ++var8) {
Class<?> superInterface = var6[var8];
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
4,进入查询逻辑
这部分的逻辑很多,省略的部分也很多…
//SqlSessionTemplate
//这里进入代理对象的执行逻辑,代理对象产生了一个DefaultSqlSession对象,用于执行sql
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
//代码过长,这里只贴出DefaultSqlSessionFactory的openSession最终调用的方法逻辑
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
//从配置文件中获取事务工厂,上述第一步中,设置了SpringManagedTransactionFactory,这里会拿出来
//然后创建一个事务对象,设置进执行器
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建执行器,这里的执行器类型是SqlSessionTemplate传递的,但创建SqlSessionTemplate时候,使用的是一个参数的构造方法,其实执行器的类型还是默认类型ExecutorType.SIMPLE
//这里如果有插件,比如分页插件PageHelper或者自定义的插件,都会在这一步进行组装,是按照顺序来的
//所以说,插件的顺序可能会影响到插件的预期结果
//如果启用了二级缓存,会进行二级缓存的包装,cacheEnabled默认是true,但是也需要在xml文件中开启,否则二级缓存是不生效的
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
//DefaultSqlSession
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
List var6;
try {
//获取MappedStatement
MappedStatement ms = this.configuration.getMappedStatement(statement);
//这一步如果打断点,如果有插件,就会先进入插件的逻辑而不是默认执行器的逻辑
var6 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, handler);
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;
}
//CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取查询使用的sql,这一步会对if等标签进行处理,处理ognl表达式等
//逻辑很长,可以作为重点看看
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存键
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
//如果xml文件没有配置缓存,直接走BaseExecutor
if (cache != null) {
//是否需要刷新缓存
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
//二级缓存的获取,二级缓存是在SqlSession关闭或提交的时候写入MappedStatement的
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
//真正的查询,进入BaseExecutor
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
//真正的查询,进入BaseExecutor
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor
//查询方法,省略部分代码
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
try {
++this.queryStack;
//一级缓存
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//数据库查询,调用doQuery抽象方法
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
return list;
}
}
//SimpleExecutor,继承自BaseExecutor
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//准备Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());
//查询并处理结果,mybatis自带的分页逻辑会在这个方法中处理,这部分的实现代码很长...
//懒加载的处理逻辑好像也是在这里
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
//准备Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
//获取连接
Connection connection = this.getConnection(statementLog);
//获取Statement
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
//设置Statement使用的参数,如果需要的话
handler.parameterize(stmt);
return stmt;
}
//从事务中获取数据库连接
protected Connection getConnection(Log statementLog) throws SQLException {
//这里对应了上文中的事务处理,SpringManagedTransaction的getConnection()方法
Connection connection = this.transaction.getConnection();
//这一步会对数据库连接进行代理,一步步直到ResultSet,从而实现sql日志的打印
//默认的打印是Slf4jImpl.class,ResultSetLogger代理类会判断isTraceEnabled(),实现查询结果的打印
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}