MyBatis源码解析
先看看不整合Spring,mybatis原生的基本用法:
//1.第一步获取SqlSessionFactory工厂
String resource = "conf.xml";
//使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
//构建sqlSession的工厂
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
//第二步:创建能执行映射文件中sql的sqlSession
SqlSession session = sessionFactory.openSession();
//第三步:获取mapper的代理类
UserMapper mapper = session.getMapper(UserMapper.class);
//第四步: 执行查询返回一个唯一user对象的sql
User user = mapper.selectUser(1);
再来看看整合了Spring后,mybatis的用法
@Autowire
UserMapper userMapper;
public User selectUser(int id){
User user = userMapper.selectUser(1);
return user;
}
接下来具体开始源码分析了:
1.1 获取SqlSessionFactory工厂
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
SqlSessionFactory var5;
try {
// 用xmlConfigBuilder读取配置文件
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
var5 = this.build(parser.parse());
}
return var5;
}
其中的关键处 XMLConfigBuilder .parse()
/*
这个方法把mybatis的所有xml都解析成Configuration对象,其中 每一个mapper.xml 解析放在MapperRgistry中。
每一个mapper.xml中的 <select>|<update>等节点都解析成MappedStatement对象存放在Configuration.mappedStatements中。
MappedStatement对象是执行真正查询所需要的重要对象。
*/
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
得到Configuration对象,再通过 SqlSessionFactoryBuilder().build(Configuration); 就得到了SqlSessionFactory对象了。
在整合Spring之后,这一步在Spring容器初始化的时候,就会进行,并生成SqlSessionFactory实例放在Ioc容器中。
1.2 获取会话SqlSession(相当于JDBC的Connection)
sqlSessionFactory.openSession(); 实际是调用了openSessionFromDataSource方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
// 根据Configuration获取环境
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
// 创建一个事务 这里面是一个sqlsession就创建一个事务,且是自动提交的。
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 创建一个Executor,将事务和Executor绑定到一起。
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
}
return var8;
}
最重要的是Executor对象在获取SqlSession的时候同时也创建了,且作为构造器参数,创建了SqlSession对象。这说明一个SqlSession对象对应一个Executor。
接下来看Executor的初始化过程
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 根据Executor的类型配置来选择创建哪一种Executor,默认是 SimpleExecutor
Object executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 如果全局配置中开启了二级缓存,就将上面的SimpleExecutor再包装一层。
if (this.cacheEnabled) {
executor = new CachingExecutor((Executor)executor);
}
// 插件拦截器将其再包装一次。
Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
return executor;
}
这段代码中药注意的是,Executor根据全局配置生成的不同类型,因为执行操作是Executor来完成的,不同的Executor查询方法不同。且Executor最后还被插件拦截器再包装了一次。
在整合Spring后,这一步不会再显示调用,而是会根据使用条件来调用,具体什么条件后面运行那一步会具体讲到的
1.2.1 两种查询的对比
如果是SimpleExecutor(没有开启二级缓存)查询的方法如下:
SimpleExecutor(BaseExecutor)查询 一级缓存
//这里只是简单提一下一级缓存的问题
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
.....
List list;
try {
++this.queryStack;
// 从一级缓存中获取,如果有,那么直接返回。
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 如果没有那去数据库中查询
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
......
}
CachingExecutor查询 二级缓存
public class CachingExecutor implements Executor {
// delegate 相当于SimpleExecutor ,CachingExecutor 只是对其做了一个包装。
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
.......
// 二级缓存的查询方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 获取二级缓存对象。
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);
// 从二级缓存中查询
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
// 如果没有,再转到上面那个方法,即去一级缓存中获取。
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
二级缓存中的数据必须在SqlSession关闭后,由一级缓存中迁移过去,而不是开启了二级缓存,就会在查询到数据后,存入一级缓存,再存入二级缓存。
使用二级缓存,数据库映射的model必须序列化。
1.3 得到Mapper接口的代理对象
sqlSession.getMapper(xxMapper.class);
//实质还是调用了Configuration中的MapperRegistry获取Mapper
this.configuration.getMapper(type, this);
this.mapperRegistry.getMapper(type, sqlSession);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
.....
//就是通过反射生成了一个xxxMapper对应的代理类 MapperProxy<T>
return mapperProxyFactory.newInstance(sqlSession);
.....
}
}
}
从上面代码可以看出,所有的Mapper接口最后都生成MapperProxy,先具体来看一下MapperProxy的构成
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
// 包含一个SqlSession
this.sqlSession = sqlSession;
// 相应的Mapper接口
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
......
}
从MapperProxy的构成可以看出MapperProxy,每一个Mapper接口都有对应的MapperProxy,但是重点是:包含SqlSession,说明每一个会话的MapperProxy都是不同的!!
在整合Spring后,这一步都已经在Spring IOC 初始化的时候,已经完成了,每一个Mapper接口都有一个对应的MaperProxy代理类放在Ioc容器中,供后面调用。 但是根据上面分析,每一个会话的MapperProxy都是不同的,即不可能做成单例形式。
Spring是如何解决这个问题的呢?
Spring在初始化生成MapperProxy代理类的时候,其中的SqlSession全部用一个单例类SqlSessionTemplate代替了。其中的妙处后面运行的时候会说到。
1.4 执行操作(select为例)
xxxMapper.selectUser(1);
在前面分析了,这个xxxMapper实际上已经是代理对象 MapperProxy
MapperProxy.invoke()
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object的方法,如getClass() 之类的就直接放行。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
}
// 将方法封装成mapperMethod对象
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
mapperMethod.execute(this.sqlSession, args)
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
// 根据执行类型 <select> | <update> 等来区别处理
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
// 查询类型
case SELECT:
// 根据返回值来处理。
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
// 封装参数
param = this.method.convertArgsToSqlCommandParam(args);
// 开始使用MapperProxy中的sqlSession开始执行了。
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
......
}
DefaultSqlsession.selectOne(this.command.getName(), param)
public <T> T selectOne(String statement, Object parameter) {
// 实际是调用selectList方法
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
}
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
// 获取到<select>节点代表的 MappedStatement 对象
MappedStatement ms = this.configuration.getMappedStatement(statement);
// 利用executor开始执行查询。
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
.....
return var5;
}
this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
上面的代码这一行,会根据Executor的类型不同而有不同情况,前面也介绍了,两种缓存情况下的查询。但如果从两级缓存中都没有获取到,那么去数据库中查询的方法是
BaseExecutor.queryFromDatabase();
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
// 获取结果
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
// 将结果存到一级缓存中去。
this.localCache.putObject(key, list);
....
return list;
}
上面的代码主要是说明查询到的数据会存储到一级缓存中去,真正执行继续看doQuery方法
doQuery方法是由BaseExecutor的实现类重写的,默认生成的是SimpleExecutor,就继续分析这里面的代码
SimpleExecutor.doQuery()
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
// Jdbc的用来执行操作的Statement类
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
// 通过Configuration获取到StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//通过StatementHandler 来预处理sql语句
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 执行查询,实际是调用jdbc的statement.execute();
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
运行到这里,整个执行过程就走完了。其中有几个细节需要讲一下:
1.4.1 四大插件相关对象
除了前面一个Executor,还有三个是StatementHandler、ParemeterHandler、ResultSetHandler
其中 ParemeterHandler、ResultSetHandler 都在StatementHandler创建的时候,一起创建并作为属性放到了StatementHandler中。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 关注点
parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 关注点
ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 上面两个方法都会在 new RoutingStatementHandler() 构造方法中出现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 关注点
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看出三个关注点,都有同样的一行代码
this.interceptorChain.pluginAll(statementHandler); 前面Executor创建的时候也有。插件的原理,不是本文重点,就不细讲。
1.4.2 真正的执行对象statement是哪里来的?
Statement stmt = this.prepareStatement(handler, ms.getStatementLog());
具体看这个方法
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 获取到Connenction,又是一个jdbc对象
Connection connection = this.getConnection(statementLog);
// 实际就是由connection.prepareStatement()得到的。
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
通过上面的代码,可以看出Statement是由Connection来的,那Connection又是哪里来的呢?
protected Connection getConnection(Log statementLog) throws SQLException {
// 通过事务得到的,前面提到了sqlSession和事务是绑定的
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
至此,已经明白了,Mybatis中获取SqlSession的同时,获取到事务,事务中含有代表数据源的DataSource对象,Connenction对象就是通过dataSource.openConnection()获取到的。这里涉及到事务相关,这里不细讲,因为在和Spring整合后,事务由Spring来管理。
1.4.3 Spring整合后,这一步的变化
在最前面的代码中也可以看出执行操作这一步,Spring整合后,依然会有,所以整合后的运行流程和分析的并没有什么变化。唯一不同的是,前面提到了MapperProxy中Sqlsession被SqlSessionTemplate 取代了。
这导致的就是:
执行过程中的 MappedMethod.execute() 中的 sqlSession.selectOne() 方法不再是接下来的步骤了,而中间插了一段动态代理产生的切面
先来看SqlSessionTemplate的构成
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// sqlSessionProxy 是 SqlSession的代理类,
// 其中具体切面在new SqlSessionTemplate.SqlSessionInterceptor() 中
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
public <T> T selectOne(String statement) {
// 如果是sqlSessionTemplate调用selectOne,实质是它里面的属性sqlSessionProxy调用
return this.sqlSessionProxy.selectOne(statement);
}
}
从上面的代码中可以看出 关键的切面代码在 new SqlSessionTemplate.SqlSessionInterceptor() 中,来继续看这个类的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 先执行SqlSessionUtils.getSqlSession()获取SqlSession,再去执行sqlSession中真正的selectOne()
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
.....
unwrapped = result;
}
return unwrapped;
}
SqlSessionUtils.getSqlSession()
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 从当前线程中获取是否有SqlSession,TransactionSynchronizationManager中都是ThreadLocal对象
SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
} else {
// 如果没有,就用到了openSession()方法
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
}
到这里,应该明白了,为什么可以用一个SqlSessionTemplate来作为MapperProxy中的SqlSession,因为实际操作用到的SqlSession,是在运行时获取到的。
1.5 多线程下事务和SqlSession的处理机制
Spring中对于多线程的处理,贯彻了两句话:
1.绑定到线程上的资源和事务的生命周期与线程并不是等长的,且线程生命周期与资源和事务没有必然关系
2.资源和事务的生命周期是等长的
SqlSession并不是多线程安全的,因此每一个线程的会话都应该是线程相关的,为了达到这个目的,通常是用ThreadLocal来实现的。SqlSession一般伴随着事务一起。因此,会把事务和资源SqlSession绑定到一起,通过ThreadLocal和线程绑定,但并不意味着它们和线程的生命周期一样。
前面提到了,事务管理通常由Spring来实现,Spring主要是通过注解 @Transactional ,放在方法上,以声明这个方法是需要事务的。
在运行的时候,如果碰到这个方法,根据不同的事务传播机制,进行不同的处理,这里假设,在这之前并没有事务,因此会出现下列几步:
当前线程执行到带@Transactional注解的方法,且之前并没有事务,通过Spring aop 机制会向TransactionManage中的数据源申请一个资源(SqlSession),不同的数据层框架资源表现形式不一样,hibernate就是Session。
成功获取到资源后,开启一个事务,和资源一起通过ThreadLocal绑定到当前线程中去。
在前面看到 SqlSessionUtil.getSqlSession()中先从当前线程中获取,如果没有再去通过openSession()来获取SqlSession。通常如果方法加了事务注解,那么一定可以从当前线程中取得到。如果没加,那就会每一次查询都获取一次新的SqlSession.
/*
一级缓存失效问题
下面这个方法,没有加注解,因此每次查询的时候,都是用新的SqlSession,因此一级缓存就"失效";
但是如果userInfoMapper是通过原生的sqlSession.getMapper()得到的,那么一级缓存就有效了。
*/
public UserInfo getSecond(){
UserInfo userInfo = userInfoMapper.selectByPrimaryKey((long)1);
userInfo = userInfoMapper.selectByPrimaryKey(1L);
return userInfo ;
}
- 事务aop是个环绕型的,因此方法执行完成后,会提交事务,并释放资源,即从当前线程中删去。事务和资源的生命周期到头了。
Spring事务传播机制:
主要说的是,一个带事务的方法去调用另一个带事务方法的处理机制。Spring事务的隔离级别:
对数据库隔离级别的一个封装,如果配置了,以Spring隔离级别为准,实际用途不大,还是用数据库来做隔离级别吧。