Mybatis源码日记(一)
Mybatis执行对数据库的操作主要通过Sqlsession完成的,Sqlsession是个接口,它有两个实现类,DefaultSqlSession和SqlSessionManager。
-
DefaultSqlSession:Mybatis在构建的时候,默认创建了DefaultSqlSessionFactory,DefaultSqlSessionFactory的openSesstion()方法返回DefaultSqlSession也就是说Mybatis默认的SqlSession实现通常使用的就是这个,非线程安全。
-
SqlSessionManager:实现了SqlSession,SqlSessionFactory,这个可以开启线程安全的Sqlsession,通过startManagedSession()方法开启线程安全的SqlSession。也可以直接使用sqlSessionProxy对象执行sql语句。通过startManagedSession()开启线程安全后,切记不能openSession(),或者startManagedSession()再次获取新的SqlSession,而是通过getConnection()获取数据库连接来执行Sql。
SqlSessionManager有三个成员变量
// 用来创建Sqlsession
private final SqlSessionFactory sqlSessionFactory;
// SqlSession的实现通过这个动态代理session实例执行
private final SqlSession sqlSessionProxy;
// 先通过ThreadLocal<SqlSession> localSqlSession获取sqlsession,如果获取不到则通过:openSession新开一个session,如果存在就使用。
private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<>();
一般情况下,我们通过以下方法可以获取到Sqlsession对象
Reader resourceAsStream = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
在调用SqlSessionFactoryBuilder的build()方法时,通过源码可以发现,build方法被重载了9次。
org.apache.ibatis.session.SqlSessionFactoryBuilder:
public SqlSessionFactory build(Reader reader);
public SqlSessionFactory build(Reader reader, String environment);
public SqlSessionFactory build(Reader reader, Properties properties);
// 上面三种方法其实最后都会调用这个方法
public SqlSessionFactory build(Reader reader, String environment, Properties properties);
public SqlSessionFactory build(InputStream inputStream);
public SqlSessionFactory build(InputStream inputStream, String environment);
public SqlSessionFactory build(InputStream inputStream, Properties properties);
// 上面这三种方法最后都会调用这个方法,和第4行方法的区别是,这里接收的是一个字节流,而第4行接收的是一个字符流
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties);
// 这个方法最终都会被第4个和第8个方法通过build(parser.parse())方法调用
public SqlSessionFactory build(Configuration config) {
// 这里返回了DefaultSqlSessionFactory
return new DefaultSqlSessionFactory(config);
}
第4行和第8行的具体实现其实都是一样的,只是接收的流不一样,一个是字节流,一个是字符流,以第8行方法实现为例:
org.apache.ibatis.session.SqlSessionFactoryBuilder:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 解析mybatis-config.xml配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 调用第9行的build方法返回DefaultSqlSessionFactory对象,通过DefaultSqlSessionFactory的openSession()方法可以返回DefaultSqlSession对象
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
先来简单看下XMLConfigBuilder,可以看到XMLConfigBuilder和SqlSessionFactoryBuilder有些类似,XMLConfigBuilder的构造方法被重载了7次。
public XMLConfigBuilder(Reader reader);
public XMLConfigBuilder(Reader reader, String environment);
// 上面两个构造方法也是调用这个,用的是字符流
public XMLConfigBuilder(Reader reader, String environment, Properties props);
public XMLConfigBuilder(InputStream inputStream);
public XMLConfigBuilder(InputStream inputStream, String environment);
// 上面两个构造方法也是调用这个,用的是字节流
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props);
// 第3行和第6行方法都是调用的这个方法。这个方法被私有化了,应该是作者不允许有人从外部直接调用
private XMLConfigBuilder(XPathParser parser, String environment, Properties props);
第7行XMLConfigBuilder构造方法实现
org.apache.ibatis.builder.xml.XMLConfigBuilder
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
// parsed标识是否被解析
this.parsed = false;
this.environment = environment;
// xpath解析器
this.parser = parser;
}
回到SqlSessionFactoryBuilder,接下来是parser.parse()实现,这个方法主要是对mybatis-config.xml配置文件做了解析
org.apache.ibatis.builder.xml.XMLConfigBuilder:
public Configuration parse() {
// 判断是否已经被解析过了
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 接下来要开始解析了,所以提前将它标识为已解析,避免后续调用parse时又被解析一遍
parsed = true;
// mybatis-config.xml根节点就是configuration
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
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>
<properties></properties>
<settings>
<setting name="" value=""/>
</settings>
<typeAliases></typeAliases>
<plugins>
<plugin interceptor=""></plugin>
</plugins>
<objectFactory type=""></objectFactory>
<objectWrapperFactory type=""></objectWrapperFactory>
<reflectorFactory type=""></reflectorFactory>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value=""/>
<property name="url" value=""/>
<property name="username" value=""/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<databaseIdProvider type=""></databaseIdProvider>
<typeHandlers></typeHandlers>
<mappers>
<!-- <mapper resource=""/> -->
<!-- mapper和package二选一 -->
<package name=""/>
</mappers>
</configuration>
最后看下parseConfiguration()方法做了哪些事,parseConfiguration()将mybatis-config.xml配置所有的子节点一一解析。
org.apache.ibatis.builder.xml.XMLConfigBuilder:
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
最后,parse()方法返回Configuration对象,在SqlSessionFactoryBuilder中build方法接收这个对象并调用,返回DefaultSqlSessionFactory对象。通过openSession()方法可以返回DefaultSqlSession,DefaultSqlSession继承自SqlSession。
参考: