沃梦达 / 编程技术 / 数据库 / 正文

基于Spring中的事务@Transactional细节与易错点、幻读

让我们来详细讲解基于Spring中的事务 @Transactional 细节与易错点、幻读的完整攻略。

让我们来详细讲解基于Spring中的事务 @Transactional 细节与易错点、幻读的完整攻略。

什么是事务?

事务是一组操作,这些操作要么全部执行成功,要么全部不执行。如果其中任何一项操作失败,事务会回滚到开始状态,以确保数据在数据库中的完整性。

Spring中的事务管理

Spring是一个开发框架,也提供了很好的事务管理。Spring的事务管理可以统一管理不同类型的事务,无论是JDBC的事务,还是一些支持JTA的应用服务器的事务。 Spring中的事务管理主要由两个重要的接口组成:TransactionManagerPlatformTransactionManager,其中 PlatformTransactionManager 是用来针对具体的持久化技术,向上对接了 TransactionManager。在实践中,我们主要通过配置 DataSourceTransactionManagerJpaTransactionManager 等具体实现类去进行具体的事务管理。

事务的属性配置

@Transactional 常用的属性有 propagationisolationtimeoutreadOnlyrollbackFor 等。

propagation

事务传播行为的属性有 REQUIREDREQUIRES_NEWSUPPORTSNOT_SUPPORTEDNEVERMANDATORYNESTED

通过 REQUIRED 标签指示方法需要事务支持,如果当前上下文中已经有事务了,那么它在当前事务中运行;否则,它将创建一个新的事务。下面的代码演示了 REQUIRED 行为标签的用法:

@Transactional(propagation = Propagation.REQUIRED)

isolation

隔离级别是用来处理并发事务执行所引起的问题,比如脏读、不可重复读、幻读。

常用的隔离级别包括 READ_COMMITTEDREAD_UNCOMMITTEDREPEATABLE_READSERIALIZABLE,其中 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 的异常,事务将不会自动回滚。需要在 @TransactionalrollbackFor 中声明,例如:

@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 调用 UserServicesaveUser() 方法,由于 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细节与易错点、幻读

基础教程推荐