一、 事务概念
概念:数据库事务是构成单一逻辑工作单元的操作集合。
一个典型的数据库事务如下所示:
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
解释:
- 数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。
- 构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行。
- 构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
- 以上即使在数据库出现故障以及并发事务存在的情况下依然成立。
事务的ACID特性
- 原子性(Atomicity)
事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。 - 一致性(Consistency)
事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等) 2.系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。 - 隔离性(Isolation)
并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。 - 持久性(Durability)
事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障都不会导致数据丢失。
事务的隔离级别
- 事务具有隔离性,理论上来说事务之间的执行不应该相互产生影响,其对数据库的影响应该和它们串行执行时一样。
- 完全的隔离性会导致系统并发性能很低,降低对资源的利用率,因而实际上对隔离性的要求会有所放宽,这也会一定程度造成对数据库一致性要求降低
- SQL标准为事务定义了不同的隔离级别,从低到高依次是
- 读未提交(READ UNCOMMITTED)
- 读已提交(READ COMMITTED)
- 可重复读(REPEATABLE READ)
- 串行化(SERIALIZABLE)
事务的隔离级别越低,可能出现的并发异常越多,但是通常而言系统能提供的并发能力越强。
不同的隔离级别与可能的并发异常的对应情况如下表所示,有一点需要强调,这种对应关系只是理论上的,对于特定的数据库实现不一定准确,比如mysql的Innodb存储引擎通过Next-Key Locking技术在可重复读级别就消除了幻读的可能。
二、 Mybatis中的数据库事务
在Mybatis中的数据库事务模块也采用了工厂模式,而且是最简单的工厂模式的使用。工厂模式在数据源模块已经简单了解,这里不再重复了解了。在Mybatis的数据库事务模块提供了JdbcTransaction 、ManagedTransaction 两个实现,在下面分析源码结构的时候分别进行介绍。
三、 类结构
Mybatis的数据库事务模块的包目录:org.apache.ibatis.transaction。具体包结构如下图所示:
其中,TransactionFactory是数据库事务中的工厂接口,Transaction是数据库事务的接口定义,JdbcTransactionFactory、ManagedTransactionFactory是数据库事务中具体的工厂实现,ManagedTransaction、JdbcTransaction是具体的数据库事务的实现类。
四、 JdbcTransactionFactory、JdbcTransaction
- JdbcTransactionFactory
JdbcTransactionFactory继承自TransactionFactory接口,实现了其中的所有方法。用于创建JdbcTransaction实例
public class JdbcTransactionFactory implements TransactionFactory {
/**
* JDBC事务模型,该方法没有执行任何代码,也就说明JDBC事务模块并不支持设置属性的功能,
* 即使你在配置文件中设置的一些信息,也不会有任何作用。
*/
@Override
public void setProperties(Properties props) {
}
@Override
public Transaction newTransaction(Connection conn) {
return new JdbcTransaction(conn);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
- JdbcTransaction
JDBC类型的事务类,MyBatis实现的事务模型,因为在这个模型中定义了事务的各个方面,使用它可以完成事务的各项操作,JDBC事务类型在正式环境中很少使用到。
- 字段、构造函数
protected Connection connection; //事务对应的数据库连接
protected DataSource dataSource; //数据库连接所属的DataSource
protected TransactionIsolationLevel level; //事务隔离级别
// MEMO: We are aware of the typo. See #941
protected boolean autoCommmit; //是否自动提交
/**
* 构造器
* @param ds
* @param desiredLevel
* @param desiredAutoCommit
*/
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommmit = desiredAutoCommit;
}
/**
* 构造器
* @param connection
*/
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
- 其他方法
该方法都比较简单,不再详细分析,直接贴出代码:
/**
* 获取connection数据库连接
*/
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
/**
* 设置是否自动提交
* @param desiredAutoCommit
*/
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException("Error configuring AutoCommit. "
+ "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e, e);
}
}
/**
* 通过对connection实例中的自动提交设置(真或假)进行判断,如果为false,表明不执行自动提交,则复位,重新将其设置为true。
* (自动提交的默认值为true)这个操作执行在connection关闭之前。可以看做是连接关闭之前的复位操作。
*/
protected void resetAutoCommit() {
try {
if (!connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed.
// Some databases start transactions with select statements
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true "
+ "before closing the connection. Cause: " + e);
}
}
}
/**
* 根据数据源(dataSource)获取连接(connection),如果存在事务隔离级别,则设置隔离级别
* 设置事务是否自动提交
* @throws SQLException
*/
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
//设置是否自动提交
setDesiredAutoCommit(autoCommmit);
}
/**
* JDBC事务模型,该方法没有执行任何有效代码,也就说明JDBC事务模块并不支持该功能,
*/
@Override
public Integer getTimeout() throws SQLException {
return null;
}
五、 ManagedTransactionFactory、ManagedTransaction
- ManagedTransactionFactory
托管模式,表示托管于其他框架,比如Spring来完成事务功能。
public class ManagedTransactionFactory implements TransactionFactory {
private boolean closeConnection = true;
@Override
public void setProperties(Properties props) {
if (props != null) {
String closeConnectionProperty = props.getProperty("closeConnection");
if (closeConnectionProperty != null) {
closeConnection = Boolean.valueOf(closeConnectionProperty);
}
}
}
@Override
public Transaction newTransaction(Connection conn) {
return new ManagedTransaction(conn, closeConnection);
}
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
// Silently ignores autocommit and isolation level, as managed transactions are entirely
// controlled by an external manager. It's silently ignored so that
// code remains portable between managed and unmanaged configurations.
return new ManagedTransaction(ds, level, closeConnection);
}
}
- ManagedTransaction
ManagedTransaction 的实现非常简单,它依赖其中的dataSource 宇段获取连接,其中connection连接还是在该方法中进行初始化或配置,其他方法比如commit()、rollback()方法都是空实现,事务的提交和回漆都是依靠容器管理的。ManagedTransaction中通过closeConnection宇段的值控制数据库连接的关闭行为。
public class ManagedTransaction implements Transaction {
private static final Log log = LogFactory.getLog(ManagedTransaction.class);
private DataSource dataSource;
private TransactionIsolationLevel level;
private Connection connection;
private final boolean closeConnection;
public ManagedTransaction(Connection connection, boolean closeConnection) {
this.connection = connection;
this.closeConnection = closeConnection;
}
public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
this.dataSource = ds;
this.level = level;
this.closeConnection = closeConnection;
}
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
@Override
public void close() throws SQLException {
if (this.closeConnection && this.connection != null) {
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + this.connection + "]");
}
this.connection.close();
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}