当前位置: 首页>>编程语言>>正文


数据库事务Spring @Transactional注解失效原因分析

yangfan 编程语言 , , , 去评论

数据库事务(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框架虽然提升了效率,偶尔也会产生意外的问题,且行且研究。

本文由《纯净的天空》出品。文章地址: https://vimsky.com/article/3431.html,未经允许,请勿转载。