什么是事务?
在计算机术语中是指访问数据库,并对数据进行操作,完成单一逻辑功能的一个程序执行单元(unit)。
通常的概念认为,事务仅与数据库相关。
事务必须服从 ISO/IEC 制定的ACID原则。ACID是指 atomicity (原子性)、consistency(一致性)、isolation(隔离性)、durability(持久性)。
1):原子性(atomicity):事务是数据库的逻辑工作单元,而且必须是原子工作单元,对于数据的修改,要么全部执行,要么全部不执行。
举例:
在银行系统中,有两个储蓄账户A和B,A账户中有 3000 元人民币,B账户中有 1000 元人民币。
现在,A想要从自己的账户中转 1000 元到B的账户中,那么从A开始转账,到转账结束的这一过程,称之为一个事务。
在这个事务中,需要做的操作如下:
①:从A的账户中减去 1000 元。A账户中原有 3000 元,减去 1000 元后应该剩余 2000 元。
②:在B的账户中增加 1000 元。B账户中原有 1000 原,增加 1000 元后应该剩余 2000 元。
如果在A的账户已经减去 1000 元的时候,忽然发生了意外,比如银行转账系统出现问题,导致转账事务意外终止,而此时,B的账户中还没有增加 1000 元,那么,我们认为这个操作失败了,事务需要回滚,回滚就是需要回到事务执行之前的状态,也就是要回到A账户 3000 元余额,B账户 1000 元余额的时候。
我们把要么一起成功(A账户减去 1000 元,同时B账户相应增加 1000 元),要么一起失败(A账户回到原来状态,B账户同样回到原来状态)的操作叫做事务的原子性操作。
2):一致性(consistency):事务在完成时,必须是所有的数据都保持一致的状态。在相关数据库中,所有的规则都必须应用于事务的修改,以保持数据的完整性。
举例:转账操作中,两个账户A\B的余额相加,总额不变。
账户A要给账户B转账 1000 元。事务要做的是从A的账户减去 1000 元,相应的在B的账户中增加 1000 元。一致性的含义是,其他事务要么看到的是A还没有给B转账的状态,要么看到的是A已经成功给B转账的状态,而对于A账户已减去 1000 元,B账户还没有增加 1000 元的这个中间状态,其他事务是不可见的。
3):隔离性(isolation):一个事务的执行,不能被其他事务所影响。
4):持久性(durability):一个事务一旦提交,事务对数据的操作便永久性的保存在DB中。即便是在数据库系统遇到故障的情况下,也不会丢失事务的操作。
JAVA事务的类型
JAVA事务的类型有3种:JDBC事务、JTA(java transaction api)事务、容器事务。
①:JDBC事务
在JDBC中处理事务,都是通过Connection对象进行事务管理。同一事务中所有的操作,都在使用同一个Connection对象。JDBC事务默认是开启的,并且是默认提交。
JDBC Connection 接口提供了两种事务模式:自动提交和手工提交。
常用的事物相关方法是:setAutoCommit\commit\rollback等。
setAutoCommit(boolean):设置是否为自动提交事务,如果true(默认值为true)表示自动提交,也就是每条执行的SQL语句都是一个单独的事务,如果设置为false,需要手动提交事务。
commit():提交结束事务。
rollback():回滚结束事务。
JDBC事务的优缺点:
JDBC为使用Java进行数据库的事务操作提供了最基本的支持。
通过JDBC事务,我们可以将多个SQL语句放到同一个事务中,保证其ACID特性。
JDBC事务的主要优点就是API比较简单,可以实现最基本的事务操作,性能也相对较好。
不支持多数据库的事务,一个 JDBC 事务不能跨越多个数据库。所以,如果涉及到多数据库的操作或者分布式场景,JDBC事务就无能为力了。
简单的JDBC示例:
public void JdbcTransfer() {
java.sql.Connection conn = null;
Statement stmt = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
conn = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521:orcl", "username", "password");
// 将自动提交设置为 false,
// 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 将 A 账户中的金额减少 1000
stmt.execute("update t_account set amount = amount - 1000 where account_id = 'A'");
// 将 B 账户中的金额增加 1000
stmt.execute("update t_account set amount = amount + 1000 where account_id = 'B'");
// 提交事务
conn.commit();
// 事务提交(转账的两步操作同时成功)
} catch (SQLException e) {
try {
// 发生异常,回滚本事务中的操做
conn.rollback();
// 事务回滚:转账的两步操作完全撤销
stmt.close();
conn.close();
} catch (Exception ignore) {
}
e.printStackTrace();
}
}
②:Java transaction api (JTA)事务
Java transaction api (Java事务API)和Java transaction service (Java事务服务)共同为J2EE平台提供了分布式事务管理。JTA只是单纯的接口,没有事务的具体实现,需要J2EE服务提供商根据JTS规范提供。
常见的JTA实现方式有:J2EE容器提供的实现(如 JBOSS)、独立的JTA实现(如JOTM、Atomikos),这些实现可以应用在那些不适用J2EE应用服务器的环境里,以提供分布式事务保证(如Tomcat、Jetty以及普通的java应用)。
JTA提供了 java.transaction.UserTransaction接口,里面定义了下面的方法:
begin:开启一个事务。
commit:提交一个事务
rollback:回滚一个事务。
setRollBackOnly:把当前事务标记为回滚。
setTransactionTimeout:设置事务的时间,超过这个时间,就抛出异常,回滚事务。
值得注意的是,并不是实现了UserTransaction接口就能把普通的JDBC操作直接转成JTA操作,JTA对DataSource、Connection与Resource都是有要求的,只有符合XA规范,并且实现了XA规范的相关接口的类才能参与到JTA事务中来。(PS:主流的数据库都支持XA规范)。
JTA的优缺点:
JTA的优点很明显,就是提供了分布式事务的解决方案,严格的ACID。
JTA的缺点是实现复杂。
JTA本身就是个笨重的API,通常JTA只能在应用服务器环境下使用,因此使用JTA会限制代码的复用性。
简单示例:
public void JtaTransfer() {
javax.transaction.UserTransaction tx = null;
java.sql.Connection conn = null;
Statement stmt = null;
try {
// 取得JTA事务,本例中是由Jboss容器管理
tx = (javax.transaction.UserTransaction) context.lookup("java:comp/UserTransaction");
// 取得数据库连接池,必须有支持XA的数据库、驱动程序
javax.sql.DataSource ds = (javax.sql.DataSource) context .lookup("java:/XAOracleDS");
tx.begin();
conn = ds.getConnection();
// 将自动提交设置为 false,
// 若设置为 true 则数据库将会把每一次数据更新认定为一个事务并自动提交
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 将 A 账户中的金额减少 1000
stmt.execute("update t_account set amount = amount - 1000 where account_id = 'A'");
// 将 B 账户中的金额增加 1000
stmt.execute("update t_account set amount = amount + 1000 where account_id = 'B'");
// 提交事务
tx.commit();
// 事务提交:转账的两步操作同时成功
} catch (SQLException sqle) {
try {
// 发生异常,回滚在本事务中的操做
tx.rollback();
// 事务回滚:转账的两步操作完全撤销
stmt.close();
conn.close();
} catch (Exception ignore) {
}
sqle.printStackTrace();
}
}
③:容器事务
容器事务主要是J2EE应用服务提供的,容器事务大多基于JTA实现,是基于JNDI的。
spring 容器事务
spring并不直接管理事务,而是提供了一致的事务模型。不管是使用 JDBC、hibernate或者是使用mybatis来操作数据,也不管使用的是DataSource事务或者JTA事务,spring将事务管理的职责委托给例如 JDBC、hibernate或JTA等持久化机制所提供的框架来处理事务。
spring事务抽象管理接口:
spring 事务抽象管理接口 org.springframework.transaction.PlatformTransactionManager 为各种平台(如:JDBC\hibernate\JTA)提供了对应的事务管理器,对事务的具体实现,将交由各平台。
例如,如果使用DataSource,我们可以配置DataSourceTransactionManager;如果使用 hibernate ,我们可以配置 HibernateTransactionManager;使用JTA的话则可以配置 JtaTransactionManager。
Public interface PlatformTransactionManager{
// 由TransactionDefinition得到TransactionStatus对象
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
// 提交
Void commit(TransactionStatus status) throws TransactionException;
// 回滚
Void rollback(TransactionStatus status) throws TransactionException;
}
由以上接口我们可以看到,事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,参数是TransactionDefinition类对象,这个参数类中定义了一些基本的事务属性(事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上)。
public interface TransactionDefinition {
int getPropagationBehavior(); // 返回事务的传播特性
int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
int getTimeout(); // 返回事务必须在多少秒内完成
boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
}
事务属性包含:传播特性、隔离级别、回滚规则、事务超时、是否只读7种传播特性:
PROPAGATION_REQUIRED:(required) 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。( 当前有事务就用当前,没有就用新的。)
PROPAGATION_SUPPORTS:(supports) 支持当前事务,如果当前没有事务,就以非事务方式执行。(事务可有可无,不是必须的。)
PROPAGATION_MANDATORY:(mandatory) 支持当前事务,如果当前没有事务,就抛出异常。(当前一定要有事务,不然就抛出异常。)
PROPAGATION_REQUIRES_NEW:(required_new) 新建事务,如果当前存在事务,把当前事务挂起。(无论当前是否有事务,都起一个新的事务。)
PROPAGATION_NOT_SUPPORTED:(not_supports) 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(不支持事务,按非事务方式运行。)
PROPAGATION_NEVER:(never) 以非事务方式执行,如果当前存在事务,则抛出异常。 (不支持事务,如果有事务则抛出异常。)
PROPAGATION_NESTED:(nested) 当前有事务就在当前事务内再起一个事务。
虽然有7种,但是常用的就第一种REQUIRED和第四种REQUIRES_NEW。
五个隔离级别:
ISOLATION_DEFAULT:(default) 默认的隔离级别,使用数据库默认的事务隔离级别。
ISOLATION_READ_UNCOMMITTED:未提交读 (read_uncommitted) 事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。
ISOLATION_READ_COMMITTED:读取已提交(read_committed)保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ:可重复读(repeatable_read) 保证一个事务不能读取另一个事务未提交的数据外。
ISOLATION_SERIALIZABLE:连续(serializable)事务被处理为顺序执行(花费最高代价但是最可靠)。