数据库事务(Database Transaction):将有限系列的执行命令作为单个逻辑执行单元,单元内的任务要么全部成功,要么全部失败。数据库事务拥有四大特性,通常称为ACID,具体说明如下(摘自维基百科):
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
1. JDBC实现事务管理
Java 底层API JDBC对数据CURD操作提供了最基础的支持,jdbc 同时也提供数据库CRUD操作的手动提交(commit)和回滚(rollback)方法,用来自定义实现事务管理。
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:mysql://192.168.1.1:3306/dbname", "user", "password"); // 创建数据库连接
} catch (SQLException e) {
//e.printStackTrace()
}
PreparedStatement ps = null;
try {
conn.setAutoCommit(false); // false CURD操作不会自动提交 不设置默认自动提交
ps = conn.prepareStatement("insert table ....."); // 新增操作
ps.execute(); // 执行sql
ps.close();
ps = null;
ps = conn.prepareStatement("update table ....."); // 更新操作
ps.execute(); // 执行sql
conn.commit(); // 将insert update事务单元提交至数据库
}catch(SQLException e) {
conn.rollback();
} finally {
if (ps != null) ps.close();
if (conn != null) conn.close();
}
使用JDBC实现事务管理的关键点在于:setAutoCommit、commit和rollback三个方法,只有正确使用才能保证事务操作的完整性,缺一不可,方法说明如下:
- setAutoCommit方法:设置提交方式true/false,手动提交/自动提交,JDBC默认自动提交
- commit方法:手动提交,需结合setAutoCommit使用,设置为false
- rollback方法:将SQL执行序列回滚,不提交更新至数据库,需结合setAutoCommit,设置为false
从JDBC事务管理的实现上来看,事务管理代码还是想对很简单的,但是在所有需要事务管理的地方,都必须使用setAutoCommit、commit、rollback操作,存在大量重复代码;所以未来更方便的管理事务,各种JAVA开发框架实现了更便捷的事务管理,比如Spring框架,只需要使用简单的注解Transactional就能完成事务管理的工作。
2.Spring 注解@Transactional事务管理
先看看Spring @Transactional的使用,代码如下:
@Transactional
public void updateUserAndAccount(String user) {
dao1.updateUserAccount();
dao2.updateAccountAmount()
}
当然在xml中配置spring事务管理类DataSourceTransactionManager
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dynamicDataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" />
虽然Spring只需要提供简单的配置,就能完成这个事务管理的工作,给实际开发节省了不少时间。合理的利用Spring的事务注解,也确实能提高不少效率。但是在最近的工作项目中,有不少开发人员不是太理解@Transactional的工作原理,导致不少业务数据操作的事务管理失效。
经过分析,spring的注解@Transactional使用也存在一些限制和注意的问题还是来看看具体的例子,看看到底是什么原因导致事务失效呢?
定义Service服务类TestTransactionServiceImpl,以下方法实现都在此类中实现
1.方法不加注解抛异常,执行有异常抛出,z1表成功新增一条记录
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) { // 空指针异常
db.update("insert into z2(c1,c2) values('2','3')");
}
}
2.方法加注解抛异常,执行有异常抛出,z1、z2表都没有新增记录,事务正常
@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) { // 空指针异常
db.update("insert into z2(c1,c2) values('2','3')");
}
}
3.方法加注解不抛异常,正常执行无异常,z1、z2表都新增了一条记录
@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
String str = "111";
if (str.startsWith("111")) {
db.update("insert into z2(c1,c2) values('2','3')");
}
}
4.方法加注解并调用该类其他方法并抛异常,执行有异常抛出,z1、z2表无新增记录
@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
testTransaction2();
}
public void testTransaction2() {
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) {
db.update("insert into z2(c1,c2) values('2','3')");
}
}
5.调用含注解的方法并抛异常,执行有异常抛出,z1表新增记录、z2表无新增记录,事务失效
public void testTransaction() {
testTransaction3();
}
@Transactional
public void testTransaction3() {
db.update("insert into z1(c1) values('2')");
// 测试
String str = null;
if (str.startsWith("111")) {
db.update("insert into z1(c1) values('adff2')");
}
}
为什么事务会失效呢?关于这一点Spring Transactional官方说明如下:
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with @Transactional.
在代理下(默认或当配置为proxy-target-class=”true”),只有当前代理类的外部方法调用注解方法时代理才会被拦截。事实上,这意味着:一个目标对象的方法调用该目标对象的另外一个方法,即使被调用的方法已使用了@Transactional注解标记,事务也不会有效执行。
另外关于@Transactional的说明,Spring 有一段描述关于方法可见性:
Method visibility and @Transactional
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
@Transactional注解只对代理类时的public方法有效,被protected、private、package-visible修饰的方法使用@Transactional注解无效,对这类方法使用事务注解,推荐使用AspectJ进行事务管理。Spring框架虽然提升了效率,偶尔也会产生意外的问题,且行且研究。