事务性
CrudRepository
实例的方法默认是事务性的。对于读取操作,事务配置的 readOnly
标志被设置为 true
。所有其他操作则使用普通的 @Transactional
注解进行配置,以便应用默认的事务配置。有关详细信息,请参阅 SimpleJdbcRepository 的 Javadoc。如果需要调整在仓库中声明的某个方法的事务配置,可以在你的仓库接口中重新声明该方法,如下所示:
interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
List<User> findAll();
// Further query method declarations
}
前面的代码会导致 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);
}
}
前面的示例导致 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();
}
通常情况下,你会希望将 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);
}
如上所示,方法 findByLastname(String lastname)
将会在执行时使用悲观读锁。如果你使用的是 MySQL 方言的数据库,这将导致执行如下查询:
Select * from user u where u.lastname = lastname LOCK IN SHARE MODE