跳到主要内容

事务性

DeepSeek V3 中英对照 Transactionality

CrudRepository 实例的方法默认是事务性的。对于读取操作,事务配置的 readOnly 标志被设置为 true。所有其他操作则使用普通的 @Transactional 注解进行配置,以便应用默认的事务配置。有关详细信息,请参阅 SimpleJdbcRepository 的 Javadoc。如果需要调整在仓库中声明的某个方法的事务配置,可以在你的仓库接口中重新声明该方法,如下所示:

interface UserRepository extends CrudRepository<User, Long> {

@Override
@Transactional(timeout = 10)
List<User> findAll();

// Further query method declarations
}
java

前面的代码会导致 findAll() 方法在 10 秒的超时时间内运行,并且不使用 readOnly 标志。

另一种改变事务行为的方式是使用通常覆盖多个存储库的外观(facade)或服务实现。其目的是为非 CRUD 操作定义事务边界。以下示例展示了如何创建这样的外观:

@Service
public class UserManagementImpl implements UserManagement {

private final UserRepository userRepository;
private final RoleRepository roleRepository;

UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}

@Transactional
public void addRoleToAllUsers(String roleName) {

Role role = roleRepository.findByName(roleName);

for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
java

前面的示例导致 addRoleToAllUsers(…) 的调用在事务内运行(参与现有事务或创建一个新的事务,如果没有事务正在运行)。仓库的事务配置被忽略,因为外部事务配置决定了实际使用的仓库。请注意,你必须显式激活 <tx:annotation-driven /> 或使用 @EnableTransactionManagement 才能使基于注解的 facade 配置生效。请注意,前面的示例假设你使用了组件扫描。

事务查询方法

为了让你的查询方法具有事务性,在你定义的仓库接口上使用 @Transactional,如下例所示:

@Transactional(readOnly = true)
interface UserRepository extends CrudRepository<User, Long> {

List<User> findByLastname(String lastname);

@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
java

通常情况下,你会希望将 readOnly 标志设置为 true,因为大多数查询方法只读取数据。与之相反,deleteInactiveUsers() 使用了 @Modifying 注解并覆盖了事务配置。因此,该方法将 readOnly 标志设置为 false

备注

强烈建议将查询方法设置为事务性方法。这些方法可能会执行多个查询以填充实体。如果没有一个共同的事务,Spring Data JDBC 会在不同的连接中执行这些查询。这可能会对连接池造成过大的压力,甚至在多个方法在持有连接的同时请求新连接时可能导致死锁。

备注

通过设置 readOnly 标志来标记只读查询无疑是合理的。然而,这并不能作为确保你不会触发修改性查询的检查(尽管某些数据库会拒绝在只读事务中执行 INSERTUPDATE 语句)。相反,readOnly 标志会作为提示传递给底层的 JDBC 驱动程序,以进行性能优化。

JDBC 锁定

Spring Data JDBC 支持在派生查询方法上启用锁。要在存储库内的某个派生查询方法上启用锁,可以使用 @Lock 注解对其进行标记。LockMode 类型的必需值提供了两种选择:PESSIMISTIC_READ 确保你正在读取的数据不会被修改,而 PESSIMISTIC_WRITE 则获取一个锁以修改数据。某些数据库并不区分这两种模式,在这种情况下,两种模式都等同于 PESSIMISTIC_WRITE

interface UserRepository extends CrudRepository<User, Long> {

@Lock(LockMode.PESSIMISTIC_READ)
List<User> findByLastname(String lastname);
}
java

如上所示,方法 findByLastname(String lastname) 将会在执行时使用悲观读锁。如果你使用的是 MySQL 方言的数据库,这将导致执行如下查询:

Select * from user u where u.lastname = lastname LOCK IN SHARE MODE
sql