事务性
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 标志的情况下运行。
另一种改变事务行为的方法是使用通常涵盖多个仓库的门面或服务实现。其目的是为非 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 才能使基于注解的门面配置生效。请注意,上述示例假定您使用组件扫描。
事务性查询方法
为了使您的查询方法具有事务性,请在您定义的仓库接口上使用 @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
目前不支持在基于字符串的查询上使用 @Lock。使用 @Query 创建的查询方法将忽略 @Lock 提供的锁定信息,在基于字符串的查询上使用 @Lock 将导致日志中出现警告。未来的版本将抛出异常。 |