概述
通过上篇文章,我们已经大概了解了Mybatis的大致使用过程。从这篇文章开始,我们将通过源码的形式来更深入的了解Mybatis的使用。
注:我们目前学习的是基于不使用spring的情况下mybatis的源码。
流程
首先,我们先看一下我们上次使用的例子:
public static void main(String[] args) {
String resource = "config/mybatis-config.xml";
InputStream inputStream;
SqlSession session = null;
try {
inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSession工厂
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取sqlSession
session = sqlSessionFactory.openSession();
// 方法1
String statement = "com.mapper.IStudentMapper.getAll";
List<Student> student = session.selectList(statement);
System.out.println(student);
// 方法2
IStudentMapper sessionMapper = session.getMapper(IStudentMapper.class);
List<Student> list = sessionMapper.getAll();
System.out.println(list);
} catch (IOException e) {
e.printStackTrace();
} finally {
session.close();
}
}
上述代码基本上就是Mybatis的整个流程了,接下来,我们来结合源码来看下,Mybatis是如何初始化,如何获取SqlSession的。
- 通过mybatis的Resources来读取全局配置文件,然后以流的形式作为SqlSessionFactoryBuilder的build方法的参数传入;
- SqlSessionFactoryBuilder根据参数创建XMLConfigBuilder对象,然后调用XMLConfigBuilder的parse方法生成Configuration对象;
- 然后SqlSessionFactoryBuilder根据Configuration对象作为参数,调用重载的另一个方法build,生成DefaultSqlSessionFactory对象。
- 最终,返回DefaultSqlSessionFactory对象。
我们来简单看下代码:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 根据输入流等参数生成XMLConfigBuilder对象,而XMLConfigBuilder用来解析全局配置
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 通过parser.parse方法生成Configuration对象,然后再调用重载方法
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.
}
}
}
/**
* 最后调用该方法生成factory,所以这里我们也可以手动传入Configuration对象来构建SessionFactory
*/
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
上述过程中,涉及到以下几个对象:
- XMLConfigBuilder 该对象的目的就是负责解析配置文件的数据,解析成Configuration对象;
- Configutation,该对象是全局配置文件mybatis-config.xml所对应的Java对象;
- SqlSessionFactoryBuilder,该对象很明显就是为了构建sessionFactory的构造器。
Conguration对象
这里可以注意下,XMLConfigBuilder在这里使用了设计模式中的建造者模式,基本建造者基类是BaseBuilder,XMLConfigBuilder是BaseBuilder类的实现之一,就是具体建造者的角色,所负责的就是解析mybatis-config.xml这个配置文件。
public class XMLConfigBuilder extends BaseBuilder {
//标识是否已经解析过配置文件
private boolean parsed;
// 用于解析配置文件的解析对象
private XPathParser parser;
// 对应配置文件中的<environment>配置
private String environment;
private ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
}
而XMLConfigBuilder的解析是通过parseConfiguration来进行的。
public Configuration parse() {
// 判断是否已经解析过
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在配置文件中查找<configuration>节点,然后通过调用parseConfiguration方法来解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 解析<settings>节点
Properties settings = settingsAsPropertiess(root.evalNode("settings"));
// 解析<properties>节点
propertiesElement(root.evalNode("properties"));
loadCustomVfs(settings);
// 解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
// 解析<plugins>节点
pluginElement(root.evalNode("plugins"));
// 解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
// 解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析<reflectionFactory>节点
reflectionFactoryElement(root.evalNode("reflectionFactory"));
settingsElement(settings);
// 解析<environments>节点
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
// 解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以看到,parseConfiguration方法将每个节点的解析都封装成来一个个具体的方法,还是很好理解的,不过有一点,我没看太明白,就是对reflectionFactory
节点的解析,我记得<configuration>节点中是没有该节点的,但这里解析的是哪个节点呢???
我们拿其中的一个 typeAliases
解析的方法来看一下实现即可,剩余的如有兴趣,大家可自行查看其源码。先看一下配置:
<typeAliases>
<typeAlias type="com.entity.Student" alias="Student"/>
<package name="com.entity"/>
</typeAliases>
private void typeAliasesElement(XNode parent) {
if (parent != null) {
// 遍历全部子节点
for (XNode child : parent.getChildren()) {
// 处理package节点
if ("package".equals(child.getName())) {
// 获取包名
String typeAliasPackage = child.getStringAttribute("name");
// 通过TypeAliasRegistry扫描指定包中的所有的类,并解析@Alias注解,完成别名注册
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
// 处理typeAlias节点,获取指定别名
String alias = child.getStringAttribute("alias");
// 获取别名对应的类型
String type = child.getStringAttribute("type");
try {
// 通过名称加载对应的类
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 扫描@Alias注解,完成别名注册
typeAliasRegistry.registerAlias(clazz);
} else {
// 注册别名
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
至于typeAliases
中的配置规则,大家可参考我最开始发出来的官方文档,这里我大致截个图来说明下:
这里面涉及到了一种设计模式,就是建造者模式,大家有兴趣的可以去查看我的另一篇设计模式的文章。
SqlSession
在获取到Configuration对象之后,SqlSessionFactoryBuilder就可以通过build方法进行构建工厂对象了。
其实SqlSessionFactoryBuilder很简单,目的就是构建SqlSessionFactory工厂对象,然后从工厂里取出SqlSession,该类提供了各种参数的build的重载方法,大家可以根据需要自行调用来学习。
接下来,我们首先来了解一下SqlSession及SqlSessionFactory的各自的继承关系。
从继承关系上我们可以看到,这里使用了工厂方法的设计模式。我们所使用的SqlSession的实现大多是DefaultSqlSession,同样,SqlSessionFactory的实现也是DefaultSqlSessionFactory。我们先看下SessionFactory的源码:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
- 首先,SqlSeesionFactory目的就是创建SqlSession,从源码中我们可以看到,SqlSeesionFactory包含了多个openSession的重载方法。可以通过其参数来指定事务级别,底层使用的Executor的类型,是否自动提交事务等等。
- 而在SqlSession中我们可以看到,SqlSession中定义的全是对数据库增删改查的各种方法。这些方法我们稍后会挨个学习使用。
DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
// 全局配置解析后的对象
private Configuration configuration;
// 底层依赖的Executor对象
private Executor executor;
// 是否自动提交事务
private 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;
}
}
DefaultSqlSession实现类SqlSession的方法,并且提供了多种重载方法。但真正对数据库操作却是通过Executor对象来进行的。并且这里使用到了策略模式。
还有一点需要注意,DefaultSqlSession不是线程安全的。
DefaultSqlSessionFactory
DefaultSqlSessionFactory是工厂类的具体实现,该类实现了多种openSession重载方法,但最终都是基于以下两种方式来创建DefaultSqlSession:
- 一种是通过数据源获取数据库连接,并创建Executor对象及DefaultSqlSession对象。
- 另一种是用户提供数据库连接对象,然后DefaultSqlSessionFactory使用该连接进行创建操作。
我们分别来看下源码实现。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取mybati-config.xml中配置的environment对象
final Environment environment = configuration.getEnvironment();
// 获取事务工厂对象
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据配置创建Executor对象
final Executor executor = configuration.newExecutor(tx, execType);
// 创建DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
来看第二种方式:
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
// 当前事务提交方式是否是自动提交
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
// 如果当前数据库驱动提供的连接不支持事务
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 根据connction创建事务Transaction
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
SqlSessionManager
SqlSessionManager同时实现类SqlSessionFactory和SqlSession两个接口,那么它也就同时具有来创建SqlSession以及使用SqlSession处理数据库的功能。
我们先看一下继承结构及属性:
public class SqlSessionManager implements SqlSessionFactory, SqlSession {
// 底层封装的SqlSessionFactory对象
private final SqlSessionFactory sqlSessionFactory;
// SqlSession的代理对象,在该类初始化时,会使用JDK动态代理的方式为localSqlSession创建代理对象
private final SqlSession sqlSessionProxy;
// 与当前线程绑定的SqlSession对象
private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();
private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
// 创建代理类,通过内部类SqlSessionInterceptor来实现
this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[]{SqlSession.class},
new SqlSessionInterceptor());
}
}
SqlSessionManager提供了两种模式:
- 第一种与DefaultSqlSessionFactory相同,同一线程每次通过SqlSessionManager对象访问数据库时,都会创建新的DefaultSqlSession对象来完成对数据库的操作;
- 第二种是SqlSessionManager通过localSqlSession变量,记录来与当前线程绑定的SqlSession对象,供当前线程循环使用,从而避免在同一线程多次创建SqlSession对象带来的性能损失。
SqlSessionManager特性:
- 构造方法私有,我们只能通过newInstance来获取对象;
- SqlSessionManager的openSession方法全是借助于底层的sqlSessionFactory的openSession方法来创建SqlSession的。
- 该类所实现的SqlSession的方法,如select,update等方法全是借助于内部的SqlSession的代理类来实现的。
我们来看一下SqlSessionInterceptor的invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取当前线程绑定的SqlSession对象
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
// 如果不等于空,则是第二种模式
if (sqlSession != null) {
try {
// 调用真正的sqlSession来完成相应的操作
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 否则是第一种模式
} else {
// 如果当前线程未绑定SqlSession对象,则创建新的SqlSession对象
final SqlSession autoSqlSession = openSession();
try {
// 通过新建的对象来完成对应的操作
final Object result = method.invoke(autoSqlSession, args);
// 提交事务
autoSqlSession.commit();
return result;
} catch (Throwable t) {
// 回滚事务
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
} finally {
// 关闭连接
autoSqlSession.close();
}
}
}
通过对以上代码分析可知,第一种模式中的SqlSession在使用完成后会立即进行关闭。而第二种模式中,与当前线程绑定的sqlSession对象需要先通过startManagedSession方法进行设置:
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
当需要提交,回滚事务或是关闭localSqlSession中记录的SqlSession对象时,需要通过SqlSessionManager.commit,rollback以及close方法来完成。
所以说,SqlSessionManager可以看作是DefaultSqlSessionFactory的一个优化类了,它所做的就是通过SqlSessionManager自己维护的一个ThreadLocal,实现session的复用,所以也是一种线程安全的实现方式。所以我们前面的例子也可以改成如下:
inputStream = Resources.getResourceAsStream(resource);
// 构建sqlSession工厂
//SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionManager sessionManager = SqlSessionManager.newInstance(inputStream);
sessionManager.startManagedSession();
String statement = "com.mapper.IStudentMapper.getAll";
List<Student> student = sessionManager.selectList(statement);
总结
对于获取SqlSession,我们可以稍微总结一下:
- 获取SessionFactory的方式有两种,一种是通过SqlSessionFactoryBuilder来进行构建,获取DefaultSessionFactory,另一种是通过SqlSessionManager来进行获取,其实,SqlSessionManager获取SessionFactory底层也是通过SqlSessionFactoryBuilder的build方法来获取的,不过多封装了一层。SqlSessionManager优于Builder的方面在于session复用和线程安全,所以我们还是建议使用后者;
- 无论使用哪种方式获取SessionFactory,获取SqlSession的方式都只有一种,就是通过SessionFactory的openSession方法来获取;
- 仅仅获取SqlSession这一块就使用了工厂方法模式,建造者模式,代理模式,策略模式等多种设计模式,值得我们了解和学习。
本文参考自:
终结篇:MyBatis原理深入解析(一)
《MyBatis技术内幕》一书
网友评论