跳到主要内容

事务性

Doubao 中英对照 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 标志。

另一种改变事务行为的方式是使用一个外观或服务实现,它通常涵盖多个仓库。其目的是为非 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,才能让基于注解的配置对外观生效。另请注意,上面的示例假定您使用组件扫描。

事务性查询方法​

要让您的查询方法成为事务性的,请在您定义的仓库接口上使用 @Transactional,如下例所示:

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

List<User> findByLastname(String lastname);

@Modifying
@Transactional
@Query("DELETE FROM user WHERE active = false")
void deleteInactiveUsers();
}
java

通常,您希望将 readOnly 标志设为 true,因为大多数查询方法只读取数据。与此不同,deleteInactiveUsers() 使用 @Modifying 注解并覆盖事务配置。因此,该方法的 readOnly 标志设为 false。

备注

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

备注

通过设置 readOnly 标志将只读查询标记为只读是完全合理的。然而,这并不充当您不会触发修改查询的检查(尽管某些数据库会拒绝只读事务中的 INSERT 和 UPDATE 语句)。相反,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
备注

@Lock 目前不支持基于字符串的查询。用 @Query 创建的查询方法会忽略 @Lock 提供的锁定信息。在基于字符串的查询上使用 @Lock 会在日志中产生一条警告。未来版本将抛出异常。