一、概述
前面一篇文章我们已经搭建好了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简单类型的执行器。