事务性
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 WHERE 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 会在日志中产生一条警告。未来版本将抛出异常。