Java – SpringAOP – 基于注解的声明式事务
简介
在Spring中,提供了一种JDBC操作封装 JDBCTemplate. JDBCTemplate 需要获得 DataSource 来进行连接池管理,DataSource 默认开启事务自动提交。
关于 JDBCTemplate 可以查看以下文章:
编程式事务
在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
共有 0 条评论