一、概述
前面一篇文章我们已经搭建好了Mybatis的源码调试环境,那么今天我们先来看看MyBatis整体的执行流程是怎样的,先对整体有个了解,后面我们再针对各个细节进行分析。在分析执行流程之前,我们先对Mybatis中几个核心类做个简单的介绍。
二、Mybatis核心类
Mybatis核心类主要有下面几个:
- SqlSessionFactory
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或通过Java的方式构建出 SqlSessionFactory 的实例。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。一个SqlSessionFactory对应配置文件中的一个环境(environment),如果你要使用多个数据库就配置多个环境分别对应一个 SqlSessionFactory。SqlSessionFactory类的主要方法:
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();
}
SqlSessionFactory其实是一个工厂,是为了创建SqlSession对象。SqlSession是MyBatis面向数据库的高级接口,其提供了执行查询sql,更新sql,提交事务,回滚事务,获取映射代理类等等方法。
- SqlSession
SqlSession作为Mybatis的顶层API接口,作为会话访问,完成增删改查操作。它是一个接口,它有2个实现类,分别是DefaultSqlSession(默认使用)和SqlSessionManager。SqlSession通过内部存放的执行器(Executor)来对数据进行CRUD操作。此外SqlSession不是线程安全的,所以每一次操作完数据库后都要调用close对其进行关闭,官方建议通过try-finally来保证总是关闭SqlSession。
我们来看一下SqlSession类中主要的方法:
public interface SqlSession extends Closeable {
/**
* 查询一个结果对象
**/
<T> T selectOne(String statement, Object parameter);
/**
* 查询一个结果集合
**/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
/**
* 查询一个map
**/
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
/**
* 查询游标
**/
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
/**
* 插入
**/
int insert(String statement, Object parameter);
/**
* 修改
**/
int update(String statement, Object parameter);
/**
* 删除
**/
int delete(String statement, Object parameter);
/**
* 提交事物
**/
void commit(boolean force);
/**
* 回滚事物
**/
void rollback(boolean force);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
/**
* 获取映射代理类
**/
<T> T getMapper(Class<T> type);
/**
* 获取数据库连接
**/
Connection getConnection();
}
- Executor
Mybatis执行器,负责SQL动态语句的生成和查询缓存的维护。它有两个实现类,其中BaseExecutor有三个继承类分别是:
- BatchExecutor批处理型执行器(批量操作);
- ReuseExecutor重用型执行器(重用预处理语句prepared statement,跟Simple的唯一区别就是内部缓存statement);
- SimpleExecutor简单型执行器(默认是SimpleExecutor,每次都会创建新的statement);
- MappedStatement
MappedStatement用来封装我们mapper.xml映射文件中的信息,包括sql语句,输入参数,输出参数等。一个SQL节点对应一个MappedStatement对象。
- Handler
处理器,Mybatis中有3种类型的Hanlder,分别为StatementHandler、ParameterHandler、ResultSetHandler。ParameterHandler主要解析参数,为Statement设置参数,ResultSetHandler主要是负责把ResultSet转换成Java对象。其实执行器Executor是调用StatementHandler来执行数据库操作。
- StatementHandler:负责处理JDBC的statement的交互,包括对statement设置参数、以及将JDBC返回的结果集转换为List等;
- ParameterHandler:负责根据传递的参数值,对statement对象设置参数;
- ResultSetHandler:负责解析JDBC返回的结果集;
- TypeHandler:负责jdbcType与javaType之间的数据转换;
接下来我们阅读源码的时候,也基本上是根据这几个关键的核心类为中心,总结一下各个组件分别发挥了什么作用。
三、MyBatis执行流程
public static void main(String[] args) {
//1、读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream;
SqlSession sqlSession = null;
try {
inputStream = Resources.getResourceAsStream(resource);
//2、初始化mybatis,创建SqlSessionFactory类实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
System.out.println(sqlSessionFactory);
//3、创建Session实例
sqlSession = sqlSessionFactory.openSession();
//4、获取Mapper接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//5、执行SQL操作
User user = userMapper.getById(1L);
System.out.println(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
//6、关闭sqlSession会话
if (null != sqlSession) {
sqlSession.close();
}
}
}
第一步:Resources.getResourceAsStream(resource)
首先我们在mybatis-config.xml中环境,映射文件路径、插件,反射工厂,类型处理器等等其它内容,所以第一步肯定也是去读取这个配置。通过Resources加载配置好的mybatis-config.xml配置文件。Resources是ibatis.io包下面的类,也就是一个io流,用于读写文件,通过getResourceAsStream把xml文件加载进来,把配置文件解析为一个流。解析生成的文件流对象用于构建SqlSessionFactory。
第二步:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这里采用了构建者模式,通过上一步解析生成的配置文件流对象,调用SqlSessionFactoryBuilder的build()创建SqlSessionFactory。
我们看一下build()方法的源码:
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
//通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//build()方法创建DefaultSessionFactory对象
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 是用来解析XML文件的一个构建者,通过他的parse()方法解析mybatis配置文件。
我们看一下parse()方法的源码,parse方法返回的是一个configuration对象,我们之前配置的插件,对象工厂,反射工厂,映射文件,类型解析器等等解析完成后都存储在Configuration对象中,方便后续各个对象直接获取。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//解析configuration标签下的所有配置
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
可以看到,里面具体调用了parseConfiguration()方法对configuration标签下的子节点的所有配置进行解析。继续看一下parseConfiguration()方法:
//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
//先解析properties. 这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。
propertiesElement(root.evalNode("properties"));
//解析全局配置settings相关配置
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
//typeAliases别名配置解析, 别名配置有两种方式: a.指定单个实体; b.指定包路径
//类型别名是为Java类型设置一个短的名字, 用来减少类完全限定名的冗余
typeAliasesElement(root.evalNode("typeAliases"));
//插件解析
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// environments里面其实就包含我们的数据库连接信息等的配置,重点,下面详细介绍.
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//类型转换器相关配置解析,用于数据库类型和Java数据类型的转换
typeHandlerElement(root.evalNode("typeHandlers"));
//mapper接口配置解析,重点,下面详细介绍
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
如上可以看到,parseConfiguration()方法其实就是对我们mybatis-config.xml中具体的标签内容属性的解析。如environment、mapper、setting等标签的解析,解析之后的所有配置都将会保存在了全局配置对象configuration中。
parseConfiguration()方法执行完后,意味着parse()方法也执行完成,我们发现parse()最后返回了一个configuration对象,它是用来存放mybatis核心配置文件解析完成后的结果。继续看源码,我们看到这个configuration配置对象返回给了SqlSessionFactory的build()方法,如下所示:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
可以看到, build()方法返回了一个DefaultSqlSessionFactory对象,这是SqlSessionFactory的实现类。如下图,SqlSessionFactory有两个实现类,其中一个就是DefaultSqlSessionFactory,另外一个是SqlSessionManager,默认返回的是DefaultSqlSessionFactory。
到这里,我们的第二步就算完成了。
第三步:SqlSession sqlSession = sqlSessionFactory.openSession();
这一步主要的目的是生成SqlSession会话,通过sqlSessionFactory的openSession()方法开启一个会话。注意,SqlSession不是线程安全的,所以每次使用完记得都关闭它。SqlSession是MyBatis面向数据库的高级接口,其提供了执行查询sql,更新sql,提交事物,回滚事物,获取映射代理类等方法。
下面我们来看openSession()的源码:
获取sqlSession有两种方式,一种是从数据源中获取的,还有一种是从连接中获取,默认是从数据源中进行获取,如下:
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession()
public SqlSession openSession() {
//默认是从数据库连接中获取一个SqlSession会话
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
//org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
final Environment environment = configuration.getEnvironment();
//事务工厂,这里是JbdcTransactionFactory工厂类
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//通过事务工厂创建JbdcTransaction事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建CachingExecutor执行器
final Executor executor = configuration.newExecutor(tx, execType);
//创建DefaultSqlSession会话,属性主要包括Configuration、Executor对象
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();
}
}
可以看到,上面的方法返回了一个DefaultSqlSession对象。看看它里面有些什么。
- 【a】environment
环境对象,这是在mybatis-config.xml中配置的,主要用来生成TransactionFactory,而TransactionFactory是用来生成Transaction事务的。
- 【b】Transaction
事务对象,我们都知道sql执行时涉及到事务,需要提交或回滚什么的。创建Transaction事务对象,需要传入我们在mybatis-config.xml中配置的数据源信息(从environment获取,因为之前解析XML的时候保存进去了),通过这些参数,transactionFactory就可以生成Transaction。
- 【c】executor
执行器,非常重要,它是一个接口,是Mybatis的核心执行器,相当于jdbc中的statement,发送sql语句并执行。executor的继承图如下所示,默认使用的是SimpleExecutor简单类型的执行器。