保姆级教程:怒肝一夜,深入Mybatis源码SqlSession
前言
它是一款半自动的ORM持久层框架,具有较高的SQL灵活性,支持高级映射(一对一,一对多),动态SQL,延迟加载和缓存等特性,但它的数据库无关性较低。
配置文件解析,并保存于Configuration中
第一步是读取mybatis-config.xml配置文件,这里我们就没必要阅读这部分源码了,这里解析转换成InputStream,接下来就是基于这个输入流进行一系列牛逼的操作。
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSessionFactory是一个接口,没有构造方法
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.ibatis.session;
import java.sql.Connection;
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean var1);
SqlSession openSession(Connection var1);
SqlSession openSession(TransactionIsolationLevel var1);
SqlSession openSession(ExecutorType var1);
SqlSession openSession(ExecutorType var1, boolean var2);
SqlSession openSession(ExecutorType var1, TransactionIsolationLevel var2);
SqlSession openSession(ExecutorType var1, Connection var2);
Configuration getConfiguration();
}
直接点build方法进去,发现它调用了另外一个build方法
// 第一个build
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
// 第二个build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 创建XMLConfigBuilder对象 parser
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 第三个build
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
// 第三步的build 创建了一个DefaultSqlSessionFactory实例
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
使用alt + 7 查看SqlSessionFactoryBuilder类所有方法
SqlSessionFactoryBuilder提供了三种读取配置信息的重载方法,方法传参分别是:Reader、InputStream、Configuration。
创建XMLConfigBuilder对象,我们查看该对象的关系图
BaseBuilder的子类基本都是以Builder结尾的,这里使用的是建造者模式
这里我们大致猜出这个类是用来解析Mybatis全局配置文件用的,直接点进去看一下
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
Mybatis对应解析包如下,路径org.apache.ibatis.parsing
XPathParser基于XPath语法解析,用于解析Mybatis中
mybatis-config.xml以及mapper.xml文件。
XPathParser主要功能如下:
继续源码分析
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
this.localReflectorFactory = new DefaultReflectorFactory();
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
构造了一个XMLConfigBuilder实例,给属性设置相应值。
再回到上一层代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 上面已经分析完了,接下来我们看parser.parse()方法
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
上面已经分析完了,接下来我们看parser.parse()方法
先看parser.parse()
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
// 该方法就是将mybatis-config.xml配置填充到configuration对象这一步
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
这张图找的网图,如有侵权联系删除
结合下面这张图,可以很直观的理解源码
parseConfiguration该方法就是将mybatis-config.xml配置填充到configuration对象,下面来看下这些标签内容是如何存入到configuration中?
propertiesElement()
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = this.configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
this.parser.setVariables(defaults);
this.configuration.setVariables(defaults);
}
}
typeAliasesElement()
private void typeAliasesElement(XNode parent) {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String alias;
if ("package".equals(child.getName())) {
alias = child.getStringAttribute("name");
this.configuration.getTypeAliasRegistry().registerAliases(alias);
} else {
alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
this.typeAliasRegistry.registerAlias(clazz);
} else {
this.typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException var7) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + var7, var7);
}
}
}
}
}
pluginElement()
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration.addInterceptor(interceptorInstance);
}
}
}
这里只介绍配置文件是如何解析填充到configuration对象中,我还会出一篇专门讲解parseConfiguration方法的博客,即下面的文章,请务必加关注。
位于XMLConfigBuilder.java类
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
接下来我们看下Mybatis是如何解析mapper.xml文件的
先展示下mybatis-config.xml中的配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=false" />
<property name="username" value="root" />
<property name="password" value="xxx" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
解析mapper源码分析
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(true) {
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String resource;
// 首先判断mapper.xml配置方式是否是package
// 如果是,取出package对象包路径存入configuration对象中
if ("package".equals(child.getName())) {
resource = child.getStringAttribute("name");
this.configuration.addMappers(resource);
} else {
// 说明不是package的配置方式
// 接下来还有三种配置方式,统统取出来对应的值
// 1.resource 2.url 3.class
resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
XMLMapperBuilder mapperParser;
InputStream inputStream;
// 根据resource方式存放
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
// 根据resource存放目录,读取***mapper.xml
inputStream = Resources.getResourceAsStream(resource);
// 映射器比较复杂,调用XMLMapperBuilder
// 注意在循环中,每一个<mapper>标签都需要重新new XMLMapperBuilder对象,来解析
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
mapperParser.parse();
// 根据url方式存放
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
inputStream = Resources.getUrlAsStream(url);
// 映射器比较复杂,调用XMLMapperBuilder
mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
mapperParser.parse();
// 根据class方式存放,使用类路径
} else {
if (resource != null || url != null || mapperClass == null) {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 直接把这个映射存入configuration
// 把这些UserMapper接口保存到configuration对象中
this.configuration.addMapper(mapperInterface);
}
}
}
return;
}
}
}
配置文件mybatis-config.xml和我们定义的映射文件*mapper.xml文件至此就全部解析完毕了。最终都是保存到Configuration大对象中,Configuration在这里是单例模式,整个Mybatis中只有一个Configuration对象。
回到SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 上面所有说的代码都是这里的
// 这里的build方法就是传入一个Configuration对象,然后构建一个DefaultSqlSession对象
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
}
}
return var5;
}
再回到程序结构上一层,就是我们的起始点
读取配置文件mybatis-config.xml
@Before
public void init() throws IOException {
String resource = "/";
InputStream resourceAsStream = Resources.getResourceAsStream(resource);
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
这里的代码
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
相当于
SqlSessionFactory build = new DefaultSqlSessionFactory();
至此,配置文件解析完毕。
配置文件解析全过程
网图,如有侵权联系删除
如何构建SqlSession
上述我们已经解析完成配置文件,并拿到了想要的SqlSessionFactory对象,下面我们看看构建SqlSession对象全过程。
上菜!
@Test
public void testFindById() {
SqlSession sqlSession = factory.openSession();
sqlSession.selectOne("findById", 1);
}
上面我们知道factory的实现是DefaultSqlSessionFactory
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.ibatis.session.defaults;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.managed.ManagedTransactionFactory;
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
public SqlSession openSession(boolean autoCommit) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, autoCommit);
}
public SqlSession openSession(ExecutorType execType) {
return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, false);
}
public SqlSession openSession(TransactionIsolationLevel level) {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), level, false);
}
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return this.openSessionFromDataSource(execType, level, false);
}
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return this.openSessionFromDataSource(execType, (TransactionIsolationLevel)null, autoCommit);
}
public SqlSession openSession(Connection connection) {
return this.openSessionFromConnection(this.configuration.getDefaultExecutorType(), connection);
}
public SqlSession openSession(ExecutorType execType, Connection connection) {
return this.openSessionFromConnection(execType, connection);
}
public Configuration getConfiguration() {
return this.configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
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;
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
DefaultSqlSession var8;
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException var13) {
autoCommit = true;
}
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
Transaction tx = transactionFactory.newTransaction(connection);
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var14, var14);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
return (TransactionFactory)(environment != null && environment.getTransactionFactory() != null ? environment.getTransactionFactory() : new ManagedTransactionFactory());
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException var3) {
}
}
}
}
重点关注这块代码
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
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;
}
创建事务Transaction
TransactionFactory关系图
事务工厂类型有两种实现:ManagedTransactionFactoryJdbcTransactionFactory
第一种:ManagedTransactionFactory生产ManagedTransaction,会把事务交给容器来管理,比如JBoss、Weblogic。
第二种:JdbcTransactionFactory生产JdbcTransaction,会交由Connection对象的commit、rollback、close方法来管理事务。
但是,如果我们是使用Spring+Mybatis,则没有必要配置,因为我们会在application.yml中配置数据源和事务管理器,从而覆盖Mybatis的配置,即mybatis的配置文件配置。
把事务作为参数传递给对象configuration中方法newExecutor创建执行器对象Executor
Executor executor = this.configuration.newExecutor(tx, execType);
创建执行器
// 在Configuration类中
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? this.defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Object executor;
// first
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// second 如果缓存开启,这里使用装饰者模式对executor进行装饰
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
// last 这里会对Executor植入插件逻辑
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
执行器共有三种类型,SIMPLE是默认类型
Executor类关系图
至此,执行器创建完成了。
创建DefaultSqlSession
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
直接点进去
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
发现这个对象最重要的两个属性configuration配置类、Executor执行器
至此,SqlSession对象创建也搞定了。