Java – SpringAOP – 基于注解的声明式事务

简介

在Spring中,提供了一种JDBC操作封装 JDBCTemplate. JDBCTemplate 需要获得 DataSource 来进行连接池管理,DataSource 默认开启事务自动提交。

关于 JDBCTemplate 可以查看以下文章:

Spring JDBC是Spring 框架对JDBC的简单封装。提供了一个JDBCTemplate对象简化JDBC开发
2022-12-20

 

编程式事务

在JDBC DataSource 中,SQL执行的事务功能的相关操作全部通过自己编写代码来实现:

// 配置 DataSource 相关信息
InputStream jdbcStream = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        properties.load(jdbcStream);
        DataSource druid = DruidDataSourceFactory.createDataSource(properties);
        Connection connection = druid.getConnection();
        JdbcTemplate jdbcTemplate = new JdbcTemplate(druid);

        try {
            // 开启事务:关闭事务的自动提交
            conn.setAutoCommit(false);

            // 核心操作
            String sql = "select * from t_user";
            jdbcTemplate.queryForObject(sql, User.class);

            // 提交事务
            conn.commit();
        } catch (Exception e) {
            // 回滚事务
            conn.rollback();
        } finally {
            conn.close();
        }

编程式事务的管理,使得业务中的非核心业务代码变得繁杂。

 

声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。

我们可以把这些事务式处理抽取出来,封装到一个切面类中进行管理,这样我们只需要在业务方法中写核心代码就可以了。

 

配置声明式事务

Spring 支持利用开启AOP切面管理,提供事务管理器 DataSourceTransactionManager

DataSourceTransactionManager 可以自动化管理SQL事务,把SQL查询业务代码写在核心业务上,提交事务等事情由AOP配合DataSourceTransactionManager 自动管理。

当方法中抛出异常时,DataSourceTransactionManager 会对方法中的操作自动进行rollback 回滚

当方法中没有抛出异常时,DataSourceTransactionManager 会对方法中的操作自动进行commit 事务提交

 

添加事务配置

<!--    导入配置文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>
    
<!--    创建DruidDataSource的Bean类-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
        <property name="initialSize" value="${jdbc.initialSize}"></property>
        <property name="maxWait" value="${jdbc.maxWait}"></property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
    -->
    <!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就
    是这个默认值,则可以省略这个属性 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

DAO层可以通过 @Autowired 自动装配实现自动赋值。

 

 

添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理在BookServiceImpl的buybook()添加注解@Transactional

注意:

1.当@Transactional 注解在方法上时,DataSourceTransactionManager 会自动影响该方法的事务管理

2.当@Transactional 注解在实体类上时,DataSourceTransactionManager 会自动影响该实体类的所有方法的事务管理。

 

事务管理属性

事务属性:readOnly

readOnly 属性默认为 false.其作用是,当我们的查询业务全部都为select时,能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。可以设定该事务为只读,但是如果我们的SQL查询中带有【update】,【delete】,【insert】这些时,若使用 readOnly 会直接报异常。

@Transactional(readOnly = true)
public void selectAll(){
 // 当查询业务全为select时可以设为只读
}

注意:对增删改操作设置只读会抛出下面异常:

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed

 

事务属性:timeout

事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。

此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。

@Transactional(timeout = 3)
public void querySQL(Integer bookId, Integer userId) {
   // 如果查询业务时间 > 3秒时,会强制回滚
}

执行过程中抛出异常:

org.springframework.transaction.TransactionTimedOutException:Transaction timed out:deadline

 

事务属性:回滚策略

声明式事务默认只针对运行时异常回滚,编译时异常不回滚。可以通过@Transactional中相关属性设置回滚策略

rollbackFor 属性:需要设置一个Class类型的对象

rollbackForClassName 属性:需要设置一个字符串类型的全类名

noRollbackFor 属性:需要设置一个Class类型的对象

rollbackFor 属性:需要设置一个字符串类型的全类名

 

@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName ="java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
  // 此处noRollbackFor指定当出现ArithmeticException异常类抛出时不进行回滚
}

Spring 默认只要抛出异常就会回滚,所以通常我们会设置noRollback 不回滚异常。

当抛出相关不回滚异常时,依然会异常,但SQL查询事务不会回滚!

 

事务属性:事务隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

隔离级别一共有四种:

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改,如果Transaction02在此期间进行了回滚,那么Transaction01的数据将没有意义,称为【脏读】

读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。如果Transaction01初期读取的数据后,Transaction02又提交了事务修改数据,那么Transaction01所读取的数据与最新更新的数据不一样了,称为【不可重复读】

可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。在Transaction01读取期间会对字段进行加锁,这样Transaction02就不能修改这个字段,但Transaction02可以修改加锁以外的字段和表,这样有可能会使Transaction01加锁的字段产生变化,即使加了锁。称为【幻读】

串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

 

各种数据库产品对事务隔离级别的支持程度:

隔离级别 Oracle MySQL
READ UNCOMMITTED ×
READ COMMITTED √(默认)
REPEATABLE READ × √(默认)
SERIALIZABLE

 

@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

 

 

事务属性:事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

举例:买多本书事务

1.在Service 中设定了循环购买的方法,购买书时也包含了事务管理

    @Service
    public class CheckoutServiceImpl implements CheckoutService {
        @Autowired
        private BookService bookService;
        @Override
        @Transactional
        //一次购买多本图书
        public void checkout(Integer[] bookIds, Integer userId) {
            for (Integer bookId : bookIds) {
                bookService.buyBook(bookId, userId);
            }
        }
    }

2.在buyBook中,对每一本书进行购,但是buyBook也包含了事务管理,

 

问题:事务管理中,如果有某些书购买失败时,应该是回滚checkout事务,还是回滚buyBook事务呢?

如果是回滚checkout事务,那么只要有其中一本书买不成功,则所有书都买不成功。

如果是回滚buyBook事务,则每一本书的购买都是一个事务,仅影响买不了的那本书。

Spring 默认会回滚checkout事务,也就是只要有一本书买不成功,那么所有书都买不成功。

 

@Transactional(propagation = Propagation.REQUIRED)

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了

 

@Transactional(propagation = Propagation.REQUIRES_NEW)

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本

 

 

完整项目下载

https://www.tzming.com/wp-content/uploads/2023/filesdown/Spring_TransferAccounts.rar

 

如果您喜欢本站,点击这儿不花一分钱捐赠本站

这些信息可能会帮助到你: 下载帮助 | 报毒说明 | 进站必看

修改版本安卓软件,加群提示为修改者自留,非本站信息,注意鉴别

THE END
分享
二维码
打赏
海报
Java – SpringAOP – 基于注解的声明式事务
简介 在Spring中,提供了一种JDBC操作封装 JDBCTemplate. JDBCTemplate 需要获得 DataSource 来进行连接池管理,DataSource 默认开启事务自动提交。 关于 JDBCTemplate 可以查看以下文章: ……
<<上一篇
下一篇>>