事务性

默认情况下,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