前言
最近在阅读MyBatis框架的源码。发现它其实是一个非常值得阅读的框架。它灵活得运用了常见的设计模式去设计。值得我们去学习。我还是比较喜欢以debug阅读MyBatis的源码。下面,就一起来看看吧。
首先,我们先写一个demo,以供调试使用
public class Demo1SessionFactory {
public static void main(String[] args) throws IOException {
String resource = “mybatis/conf/mybatis-config.xml”;
InputStream inputStream = Resources.getResourceAsStream(resource);
//从 XML 中构建 SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1L);
System.out.println(blog);
blog = mapper.selectBlog(1L);
System.out.println(blog);
} finally {
session.close();
}
}
}
从demo分析上层流程
在demo中,我们先从最上层分析一下流程。这里写图片描述
我们首先是创建SqlSessionFactory,这其中需要使用配置信息。然后从SqlSessionFactory获取SqlSession,再从SqlSession中获取Mapper。这是我们最上层的流程。那这这几步操作中,内部操作了什么呢?我们需要debug去分析。在分析之前,我们需要先了解一下MyBatis的架构和使用的设计模式,这样能帮助我们更轻松的去了解它的源码。
作用域(Scope)和生命周期
SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建。使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。因此 SqlSessionFactory 的最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。
SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的管理作用域中,比如 Servlet 架构中的 HttpSession。如果你现在正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象相似的作用域中。换句话说,每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。这个关闭操作是很重要的,你应该把这个关闭操作放到 finally 块中以确保每次都能执行关闭。下面的示例就是一个确保 SqlSession 关闭的标准模式:
其实我感觉这个跟Servlet中的生命周期类似,SqlSessionFactory当做上下文(Application),SqlSession当做一个会话(session),这样就好理解了。一个SqlSession就是与数据库进行一次交互。
从作用域和生命周期的分析,我们可以分析出,它的核心是SqlSession。
查看org.apache.ibatis.session.defaults.DefaultSqlSessionFactory源码,查看SqlSession的获取实现过程
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
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();
}
}
再看SqlSession类的结构:
SqlSession 主要由Configuration和Executor构成,其中有增删改查的各种方法。
这里写图片描述
由此我们可以推理出我们用MyBatis进行操作的主要操作入口都在这里。那么,接下来就涉及到我们是如何获取到具体的Mapper
具体Mapper的获取过程
查看 org.apache.ibatis.session.SqlSession ->getMapper() 找到实现
@Override
public T getMapper(Class type) {
return configuration.getMapper(type, this);
}
继续深入,找到org.apache.ibatis.binding.MapperRegistry ->getMapper()
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException(“Type " + type + " is not known to the MapperRegistry.”);
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
可以分析出这是通过动态代理获取的,而它在获取的时候需要两个参数Class type, SqlSession sqlSession。SqlSession是作为被代理的对象。type作为key去Map中拿MapperProxyFactory来产生代理类需要的参数。
Mapper的操作流程
现在,我们已经知道具体的Mapper的来历了,那么就可以去了解一下它是如何使用的。因为它是SqlSession进行了动态代理类,所以,我们debug SqlSession,进行一次查询操作。就如下图所示进入调试。
这里写图片描述
debug下去,你会看到,底层是通过Executor执行的
这里写图片描述
然后继续深入,可以看到sql的组装过程:
这里写图片描述
Executor是MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
接下来就是类似传统JDBC的操作了
这里写图片描述
MyBatis核心构件
名称 作用
SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合
ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数
ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
MappedStatement MappedStatement维护了一条select
SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
BoundSql 表示动态生成的SQL语句以及相应的参数信息
Configuration MyBatis所有的配置信息都维持在Configuration对象之中