简介
对于一个成熟的ORM框架来说,数据源的管理以及事务的管理一定是不可或缺的组成,对于Mybatis来说,为了使用方便以及扩展简单也是做了一系列的封装,这一篇主要介绍mybatis是如何管理数据源以及事务的。
数据源DataSource
DataSource的分类
mybatis提供了三种数据源实现:
- UNPOOLED:不使用连接池,每次请求都创建和关闭连接
- POOLED: 使用连接池,避免每次请求创建何关闭连接,浪费资源
- JNDI: 此实现旨在与EJB或应用程序服务器等容器一起使用,这些容器可以集中或外部配置DataSource,并在JNDI上下文中对其进行引用。(可以了解一下JNDI和JDBC的区别)
DataSource的创建过程
要创建哪种类型的DataSource是在mybatis的配置文件中决定的,指定对应的type类型:
<environments default="development">
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
MyBatis在初始化时,根据type属性来创建相应类型的的数据源DataSource,看一下解析dataSource的源码(在XMLConfigBuilder类中的environmentsElement方法中):
private void environmentsElement(XNode context) throws Exception {
for (XNode child : context.getChildren()) {
String id = child.getStringAttribute("id");
if (isSpecifiedEnvironment(id)) {
// Transaction标签的解析
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// DataSource标签的解析
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 获取type属性值
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 根据type创建DataSourceFactory
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
}
在上面的代码中,可以看到mybatis根据配置的type值创建对应的DataSourceFactory,再创建对应的DataSource,mybatis中一共有三种DataSourceFactory,正好对应数据源的类型:
创建DataSourceFactory的代码如下:
protected <T> Class<? extends T> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
// alias就是传进来的type的值
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
protected <T> Class<? extends T> resolveAlias(String alias) {
// 在别名注册器中查找
return typeAliasRegistry.resolveAlias(alias);
}
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) {
return null;
}
String key = string.toLowerCase(Locale.ENGLISH);
Class<T> value;
// 别名注册器中查找,存在则直接返回对应的类型
if (typeAliases.containsKey(key)) {
value = (Class<T>) typeAliases.get(key);
} else {
// 直接返回type的值
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}
别名注册器在Configuration初始化的时候已经将数据源以及事务的别名初始化好了,如下:
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
}
到这里数据源的创建过程就已经分析完了,主要就是解析DatsSource标签,再创建DatsSourceFactory,最后再创建对应DataSource
DataSource在何时被使用
在使用mybatis的时候,需要先获取SqlSessionFactory,再获取SqlSession,通过SqlSession创建mapper接口来执行sql操作,实际上执行逻辑的是mybatis中的执行器Executor,数据源的使用也正是在Executor执行中用来创建连接的,分析一下这段逻辑:
// 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);
// 创建Executor
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();
}
}
// Executor执行的查询逻辑
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 根据StatementHandler创建Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler是mybatis中一个重要的组件,这里先不展开,再创建完StatementHandler之后,需要创建具体的Statement来处理sql语句,数据源的使用也正是在prepareStatement方法中:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
protected Connection getConnection(Log statementLog) throws SQLException {
// 从数据源中获取连接
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
// 创建代理的Connection对象
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
在创建Connection连接时,是通过transaction对象来完成的,transaction对象中拥有初始化好的DataSource以及事物隔离级别或者事务是否提交的属性,到这里Connection对象就已经创建完成了,接下来看一下mybatis中的事物
事务Transaction
事务分类
MyBatis包含两种TransactionManager类型(即type="[JDBC|MANAGED]”)
-
JDBC: JDBC-此配置只是直接使用JDBC提交和回滚。它依赖于从dataSource检索的连接来管理事务的范围。默认情况下,它在关闭连接时启用自动提交,以便与某些驱动程序兼容。然而,对于一些驱动程序来说,启用自动提交不仅是不必要的,而且是一项昂贵的操作。因此,自3.5.10版本以来,可以通过将“skipSetAutoCommitOnClose”属性设置为true来跳过此步骤。
-
MANAGED
这种配置几乎什么都不做。它永远不会提交或回滚连接。相反,它允许容器管理事务的整个生命周期(例如JEE应用程序服务器上下文)。
Transaction的创建逻辑与DataSource是类似的,就不再分析了
Transaction的使用
在Executor执行的时候,创建连接是通过Transaction来构造的,直看创建连接的代码:
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
Transaction在创建连接的时候会指定事务的隔离级别以及是否自动提交
总结
- 数据源创建通过数据源工厂指定,工厂类型是在mybatis配置文件中指定的;数据源获取连接是在Executor执行器获取Statement时调用的
- 事务创建也是通过事务工厂指定,同样是在mybatis配置文件中指定的,Transaction在获取到连接后,再进行设置事物的隔离级别以及是否自定提交