事务性

CrudRepository 实例的方法默认情况下是事务性的。对于读取操作,事务配置的 readOnly 标志设置为 true。所有其他操作都配置了普通的 @Transactional 注解,以便应用默认的事务配置。有关详细信息,请参阅 SimpleJdbcRepository 的 Javadoc。如果需要调整存储库中声明的方法之一的事务配置,请在存储库接口中重新声明该方法,如下所示

CRUD 的自定义事务配置
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
@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 标志将只读查询标记为只读绝对是合理的。但是,这并不能检查您是否没有触发操作查询(尽管某些数据库在只读事务中拒绝 INSERTUPDATE 语句)。相反,readOnly 标志作为提示传播到底层 JDBC 驱动程序以进行性能优化。

JDBC 锁定

Spring Data JDBC 支持在派生查询方法上进行锁定。要在存储库中的给定派生查询方法上启用锁定,您可以使用 @Lock 对其进行注释。类型为 LockMode 的必需值提供了两个值:PESSIMISTIC_READ 保证您正在读取的数据不会被修改,以及 PESSIMISTIC_WRITE 获取锁定以修改数据。某些数据库不区分这两个值。在这种情况下,两种模式都等同于 PESSIMISTIC_WRITE

在派生查询方法上使用 @Lock
interface UserRepository extends CrudRepository<User, Long> {

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

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

MySQL 方言生成的 SQL 查询
Select * from user u where u.lastname = lastname LOCK IN SHARE MODE