概述
本文我们来阐述Mybatis从启动到执行一条Sql的流程,我们以前文Mybats快速入门涉及到的代码为例,如下代码:
public static void main(String[] args) {
// 创建MyBatis的SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
// 获取SqlSession对象
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
// 获取UserMapper接口的实现类
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 查询id为1的用户数据
User user = userMapper.getUserById(1);
System.out.println(user);
}
}
大致步骤可以总结为下图:
分解
下面我们针对每个步骤结合源码来分析,本文只介绍大致的步骤流程,我们先有个大致总的流程,后续针对各个部分感兴趣的点再深挖~
加载全局文件
// 通过建造者模式创建SqlSessionFactoryBuilder
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 进入build()
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 继续进入,这里有两行关键代码
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
...
}
// 先来看第一行,这里也使用建造者模式创建XMLConfigBuilder
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// ->创建XPathParser对象:XPathParser是一个XML解析,用于解析XML文件
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
// 进入this(...)
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 创建Configuration对象
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
// 到这里第一行代码就结束了,这里总结下:
1.创建了一个XMLConfigBuilder的对象
2.在XMLConfigBuilder对象里构建了XPathParser对象(后续用于解析),
3.在XMLConfigBuilder对象里构建Configuration对象,初始化了一些默认值(后续所有的配置都会放到这个对象里)
解析全局配置文件
// 我们接着看下一行代码
...
return build(parser.parse());
// 来看parser.parse()
public Configuration parse() {
// 是否解析过
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析配置文件
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// 这里的parser就是前面构建的XPathParser对象
// -> parseConfiguration(parser.evalNode("/configuration"));
private void parseConfiguration(XNode root) {
try {
// 解析并读取properties元素,加载属性配置
propertiesElement(root.evalNode("properties"));
// 解析并读取settings元素,加载全局配置属性
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载自定义的VFS(Virtual File System)
loadCustomVfs(settings);
// 加载自定义的Log实现类
loadCustomLogImpl(settings);
// 解析并读取typeAliases元素,加载类型别名配置
typeAliasesElement(root.evalNode("typeAliases"));
// 解析并读取plugins元素,加载插件配置
pluginElement(root.evalNode("plugins"));
// 解析并读取objectFactory元素,加载对象工厂配置
objectFactoryElement(root.evalNode("objectFactory"));
// 解析并读取objectWrapperFactory元素,加载对象包装器工厂配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析并读取reflectorFactory元素,加载反射器工厂配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 解析并设置全局配置属性
settingsElement(settings);
// 解析并读取environments元素,加载环境配置
// issue #631: 先解析objectFactory和objectWrapperFactory后再加载environments
environmentsElement(root.evalNode("environments"));
// 解析并读取databaseIdProvider元素,加载数据库标识提供者配置
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析并读取typeHandlers元素,加载类型处理器配置
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析并读取mappers元素,加载Mapper接口配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
// 如果解析过程中发生异常,抛出BuilderException,并将原始异常包装进去
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 大致总结上面代码
就是通过XPathParser对象将mybatis-config.xml里的配置信息全都解析为Java对象,然后封装到Configuration对象.
创建SqlSessionFactory对象
// 继续往后走,parser.parse()方法返回的结果就是Configuration对象
// ->
build(parser.parse());
// ->
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
// 总结
这里通过Configuration构建了一个DefaultSqlSessionFactory,Configuration中包含前面解析到的所有配置信息.
创建SqlSession对象
// 来到下面代码,通过sqlSessionFactory创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// ->
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获取当前配置对象的环境配置信息
final Environment environment = configuration.getEnvironment();
// 根据环境配置获取事务工厂
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 使用事务工厂创建一个新的事务对象tx,并指定数据源、事务隔离级别、是否自动提交事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据事务对象和执行器类型创建一个新的Executor对象,Executor是MyBatis的核心执行器
final Executor executor = configuration.newExecutor(tx, execType);
// 使用Executor对象创建一个新的DefaultSqlSession对象,并返回
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
// 如果在打开Session过程中出现异常,则关闭事务并抛出异常
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
// 重置ErrorContext,清空错误信息
ErrorContext.instance().reset();
}
}
// 总结:MyBatis中的一个关键方法,用于打开一个新的SqlSession对象
// 主要步骤为:
1.获取当前配置对象的环境配置信息,包括数据源、事务管理器等
2.根据环境配置获取事务工厂,用于创建新的事务对象。
3.使用事务工厂创建一个新的事务对象tx,并指定数据源、事务隔离级别、是否自动提交事务。
4.根据事务对象和执行器类型创建一个新的Executor对象,Executor是MyBatis的核心执行器,用于执行SQL语句
5.使用Executor对象创建一个新的DefaultSqlSession对象,并返回。DefaultSqlSession是MyBatis对外暴露的主要接口,用于进行数据库操作
6.如果在打开SqlSession过程中出现异常,则关闭事务并抛出异常。同时,通过ExceptionFactory.wrapException()方法将原始异常包装,使得异常信息更加明确和易于定位。
7.最后,重置ErrorContext,清空错误信息,以防止错误信息在不正确的地方被累积。
SqlSession获取Mapper代理对象
// 来到下列代码,获取Mapper对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// ->
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 从knownMappers中获取对应Mapper接口的MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// 如果MapperProxyFactory为空,说明该Mapper接口未在knownMappers中注册,抛出BindingException异常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 使用mapperProxyFactory创建并返回一个新的Mapper接口的代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
// 如果在创建Mapper代理对象过程中出现异常,则抛出BindingException异常,同时将原始异常包装以提供更详细的错误信息
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// -> mapperProxyFactory.newInstance(sqlSession);
public T newInstance(SqlSession sqlSession) {
// 创建一个MapperProxy对象,传入SqlSession、Mapper接口类型以及方法缓存对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 使用MapperProxy创建并返回一个新的Mapper接口的代理对象
return newInstance(mapperProxy);
}
// 总结:创建Mapper接口的代理对象
代理对象执行sql
// 来到下列代码
User user = userMapper.selectById(1);
// 由于userMapper对象是个代理对象,所以它会先来到代理对象MapperProxy逻辑
// -> MapperProxy.invoke()
// -> mapperMethod.execute(sqlSession, args);
// -> result = sqlSession.selectOne(command.getName(), param);
// -> 底层调用jdbc逻辑,返回结果集
// 这里后续会详细探究,这里暂时简单了解它的过程即可
总结
上述大致描述了Mybatis从启动到执行sql的流程, 后面我们再从细节来一一揭开它的面纱~