本系列以Mybatis 3.3.X分支源码作为分析源,mybatis源码Git地址:https://github.com/mybatis/mybatis-3.git。
SqlSessionFactory 作为Mybatis的应用入口,主要提供了各种获取SqlSession实例的方法。SqlSessionFactory在大部分情况下,建议在系统中生成唯一实例(读写分离、多库连接等除外)。
SqlSession主要提供了常用的数据库增删查改操作。
一、 SqlSessionFactory类
SqlSessionFactory主要定义了几个重载的openSession方法,其主要实现为org.apache.ibatis.session.defaults.DefaultSqlSessionFactory。
首先描述下SqlSessionFactory的所有方法涉及到的参数:
boolean autoCommit:主要为connection.setAutoCommit(autoCommit)的参数,表示是否自动提交
Connection connection:数据库连接
TransactionIsolationLevel level:事务隔离级别,其为enum参数,主要定义了五种事务隔离级别与Connection中五种隔离级别一一对应。这里简要描述下各个级别的含义:
TransactionIsolationLevel.NONE 表示不支持事务
TransactionIsolationLevel.READ_UNCOMMITTED 表示可能发生脏读、不可重复读和幻读, 属于限制性最弱的隔离级别,并发性较高
TransactionIsolationLevel.READ_COMMITTED 表示可能发生不可重复读和幻读。SqlServer和Oracle默认采用此隔离级别
TransactionIsolationLevel.REPEATABLE_READ 表示可能发生幻读。Mysql默认采用此隔离级别
TransactionIsolationLevel.SERIALIZABLE 最高的事务隔离级别,通过强制对事务排序来避免幻读。缺点是在大并发下容易导致大量超时和锁竞争
下面给出脏读,不可重复读和幻读的基本描述:
脏读:指事务读取了其他事务未提交的数据。比如一个事务正在访问并修改数据,此修改还未提交到数据库中,此时另外一个事务需要访问此数据,正好读取到修改后的数据并使用,此时第一个事务因为某些原因进行回滚,最终导致数据的不正确性。
不可重复读:指同一个事务内,多次读取同一数据得到不同的结果。比如当前一个事务在一开始读取了数据并执行其他逻辑操作,此时另外一个事务修改了此数据,在第一个事务内再次读取数据时,发现结果与第一次不一致。
幻读:与不可重复读类似,主要指一个事务操作一组数据时,其他事务新增或者删除了该组部分数据,导致第一个事务读取时发现与想要的结果不一致,出现幻读现象。比如一个事务获取表中标志位为0的数据有100条,然后更新所有数据的标志位为1,此时其他事务添加了一条标志位为0的数据并提交,第一个事务再读取标志位为0的数据时发现还有1条。
ExecutorType execType:定义执行类型的枚举,进入源码可以看到仅定义了SIMPLE, REUSE, BATCH三种类型。其中SIMPLE表示每个语句的执行会创建一个新的预处理器;REUSE表示复用预处理器;BATCH表示执行器批量执行更新语句
不带参数的openSession是大部分情况下的使用模式,默认采用配置文件的执行器,采用数据库默认事务隔离级别且关闭自动提交。
分析了参数后,再看看DefaultSqlSessionFactory默认不带参数的openSession是如何使用的:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
不带参数的openSession是大部分情况下的使用模式,默认采用配置文件的执行器,采用数据库默认事务隔离级别且关闭自动提交。
DefaultSessionFactory主要实现逻辑有两个方法openSessionFromDataSource和openSessionFromConnection,从方法名和参数可以看出,前者主要采用配置的dataSource获取数据库连接,后者为人工设置数据库连接。因为两者实现类同,这里描述下openSessionFromDataSource的实现。
1 获取事务对象
final Environment environment = configuration.getEnvironment();
//获取事务工厂,优先从Environment中获取,不存在则新建ManagedTransactionFactory实例
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//获取事务对象
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
事务对象共有两种JdbcTransaction和ManagedTransaction,二者的区别详见mybatis官方文档的描述:
- JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务范围。
- MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
//通过ExecutorType和事务构建执行器
final Executor executor = configuration.newExecutor(tx, execType);
其主要调用了Configuration的newExecutor方法实现:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//如果没有传递执行器类型,则采用默认的类型,默认为SIMPLE类型
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
//构建执行器
Executor 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);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
执行器类型在之前已经描述过,共有
SIMPLE, REUSE, BATCH三种。这里注意下如果设置为cacheEnabled,则会将执行器包装生成CachingExecutor。CachingExecutor通过代理原有执行器,实现缓存的处理逻辑,后续会针对缓存进行单独的博文描述。
3 生成SqlSession实例
主要利用Configuration,Executor和autoCommit构造DefaultSqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
SqlSession主要定义了常用的数据库访问方法主要分为select、update两大类,其主要实现为org.apache.ibatis.session.defaults.DefaultSqlSession。SqlSession主要利用Executor执行逻辑,其本身的实现相对比较简单,Executor将在下一篇分析说明。
首先描述下DefaultSqlSession的属性定义:
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
其中:前三者属性已经多次了解过,configuration表示配置信息,executor表示执行器,autoCommit表示是否自动提交。dirty参数主要用来标记当前的提交或者回滚是否需要数据库执行,仅当非自动提交下,dirty=true时,在调用commit和rollback时会执行数据库的commit和rollback。
1 select方法体描述
DefaultSqlSession提供了常用的查询操作,包括selectOne、selectList、selectMap、select几个重载方法。
selectOne方法主要通过调用selectList实现数据库的查询操作,其源码如下所示:
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
从源码可以看出,selectOne通过调用selectList实现查询,并对返回值进行判断处理,如果返回数据为1,则认为查询成功;返回数据大于1,则抛出TooManyResultsException异常;没有查询到数据则返回NULL
selectList、selectMap和select方法的实现都大同小异,select主要多了ResultHandler参数,从名称就可以看出为结果回调处理方法。这里仅以select作为简要分析,其源码如下所示:
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
第一步通过statement的id获取到相应的Statement对象,其中MapperdStatement包括了本次执行请求的许多重要信息,将在后续文章中一一描述。
第二步调用执行器执行实际的查询操作,需要注意下select操作因为传递handler,其返回值为void,而其他三类查询方法直接返回查询结果。
2 update方法体
update方法体系包括insert、delete、update方法。其中insert、delete都是通过调用update方法来完成实际的更新操作。update方法实现源码如下所示:
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
第一步将dirty置为true,便于后续的数据库commit和rollback
第二步获取实际的statement对象
第三步调用执行器的update方法执行实际的更新操作
SqlSession实现相对比较简单,其核心的Executor将会在下一篇文章中详细描述。