让我们来详细讲解基于Spring中的事务 @Transactional
细节与易错点、幻读的完整攻略。
什么是事务?
事务是一组操作,这些操作要么全部执行成功,要么全部不执行。如果其中任何一项操作失败,事务会回滚到开始状态,以确保数据在数据库中的完整性。
Spring中的事务管理
Spring是一个开发框架,也提供了很好的事务管理。Spring的事务管理可以统一管理不同类型的事务,无论是JDBC的事务,还是一些支持JTA的应用服务器的事务。 Spring中的事务管理主要由两个重要的接口组成:TransactionManager
、PlatformTransactionManager
,其中 PlatformTransactionManager
是用来针对具体的持久化技术,向上对接了 TransactionManager
。在实践中,我们主要通过配置 DataSourceTransactionManager
或 JpaTransactionManager
等具体实现类去进行具体的事务管理。
事务的属性配置
@Transactional
常用的属性有 propagation
、 isolation
、 timeout
、 readOnly
、 rollbackFor
等。
propagation
事务传播行为的属性有 REQUIRED
、 REQUIRES_NEW
、 SUPPORTS
、 NOT_SUPPORTED
、 NEVER
、 MANDATORY
和 NESTED
。
通过 REQUIRED
标签指示方法需要事务支持,如果当前上下文中已经有事务了,那么它在当前事务中运行;否则,它将创建一个新的事务。下面的代码演示了 REQUIRED
行为标签的用法:
@Transactional(propagation = Propagation.REQUIRED)
isolation
隔离级别是用来处理并发事务执行所引起的问题,比如脏读、不可重复读、幻读。
常用的隔离级别包括 READ_COMMITTED
、 READ_UNCOMMITTED
、 REPEATABLE_READ
和 SERIALIZABLE
,其中 READ_COMMITTED
级别是最常用的,默认级别。
下面的代码演示了隔离级别的用法:
@Transactional(isolation = Isolation.REPEATABLE_READ)
timeout
timeout
属性指定了事务的超时时间。超时就会抛出异常,回滚事务。
下面的代码演示了超时时间 5 秒的用法:
@Transactional(timeout=5)
readOnly
readOnly
属性说明当前事务是否只读。如果只读,那么不允许事务在执行期间修改任何数据。
下面的代码演示了 readOnly
属性的实现:
@Transactional(readOnly = true)
public List<Customer> listCustomers() {
return customerDao.findAll();
}
rollbackFor
默认情况下, Spring 事务只有在出现 Runtime 异常才回滚,在出现其他类型异常时不回滚。通过 rollbackFor
属性显式设置一些异常,可以实现在遇到这些异常时回滚事务。下面的代码演示了回滚指定异常的用法:
@Transactional(rollbackFor = {Exception.class})
public void save(Customer customer) throws Exception {
// do something
}
常见易错点
非 Runtime 异常导致事务不回滚
事务默认只回滚 RuntimeException
异常,对于 Exception
异常不会自动回滚,因此如果你写了一个抛出 Exception
的异常,事务将不会自动回滚。需要在 @Transactional
的 rollbackFor
中声明,例如:
@Transactional(rollbackFor = Exception.class)
异常被吃掉导致事务不回滚
当某个异常没有被正确的抛出,甚至被吃掉了,事务也会失效。
比如在以下的代码中,由于异常被 try-catch 了,导致事务不会回滚:
@Transactional
public void test() {
try {
// do something
} catch (Exception e) {
// 异常被捕获
}
}
外部调用类、方法没有加事务注解,导致事务失效
Spring 的事务管理针对 @Transactional
的注解才会生效,如果被调用方法所在的类没有加上事务注解,事务会失效。例如:
@Transactional
public class UserService {
public void saveUser() {
// do something
}
}
public class UserController {
private UserService userService;
public void saveUser() {
userService.saveUser();
}
}
在这段代码中,UserController
调用 UserService
的 saveUser()
方法,由于 UserService
类没有使用事务注解,事务不会生效。
幻读问题
幻读问题出现的原因是在并发情况下事务只锁住语句执行的数据行,而没有锁定数据页,因此其他事务可以在数据页中插入数据。这样就导致了在读取数据的时候,出现了虚假的行的情况,就好像魔幻般出现了新的行。
以下是一个处理幻读问题的示例:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void handlePhantomRead() {
// Step 1: 查询一次结果集
List<Customer> customers = customerDao.findCustomersByAddress("xxx");
// Step 2: 其他事务新增数据
customerDao.insertCustomer(new Customer("xxx", "male", 30));
// Step 3: 再次查询,导致出现幻读
List<Customer> customers2 = customerDao.findCustomersByAddress("xxx");
}
以上代码中的第二步插入数据的操作,会在第一步查询数据之后被执行,从而导致第三步查询出现了幻读。要避免幻读,可以在查询前先执行 SELECT … FOR UPDATE
操作来加锁:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void handlePhantomRead() {
// Step 1: 查询一次结果集,并且在查询前先执行 SELECT … FOR UPDATE 操作来加锁
List<Customer> customers = customerDao.findCustomersByAddressForUpdate("xxx");
// Step 2: 其他事务新增数据
customerDao.insertCustomer(new Customer("xxx", "male", 30));
// Step 3: 再次查询,由于在查询时加锁,避免了幻读问题
List<Customer> customers2 = customerDao.findCustomersByAddress("xxx");
}
以上便是基于Spring中的事务 @Transactional
细节与易错点、幻读的完整攻略,希望对你有所帮助。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:基于Spring中的事务@Transactional细节与易错点、幻读 - Python技术站