事务性

`CrudRepository` 实例的方法默认是事务性的。对于读取操作,事务配置的 `readOnly` 标志设置为 `true`。所有其他操作都配置了普通的 `@Transactional` 注解,因此应用默认的事务配置。详情请参阅 SimpleJdbcRepository 的 Javadoc。如果您需要调整 Repository 中某个方法的事务配置,请在您的 Repository 接口中重新声明该方法,如下所示

CRUD 的自定义事务配置
interface UserRepository extends CrudRepository<User, Long> {

  @Override
  @Transactional(timeout = 10)
  List<User> findAll();

  // Further query method declarations
}

上述配置使 `findAll()` 方法以 10 秒的超时时间运行,并且没有 `readOnly` 标志。

另一种改变事务行为的方式是使用通常覆盖多个 Repository 的 Facade(门面)或服务实现。其目的是为非 CRUD 操作定义事务边界。以下示例展示了如何创建这样的 Facade

使用 Facade 为多个 Repository 调用定义事务
@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(…)` 的调用在事务中运行(参与现有事务或在没有事务运行时创建新事务)。Repository 的事务配置被忽略,因为外部事务配置决定了实际使用的 Repository。请注意,您必须显式激活 `<tx:annotation-driven />` 或使用 `@EnableTransactionManagement` 才能使基于注解的 Facade 配置生效。请注意,前面的示例假设您使用组件扫描。

事务性查询方法

为了使您的查询方法具有事务性,请在您定义的 Repository 接口上使用 `@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` 标志来标记只读查询是完全合理的。然而,这并不能确保您不会触发操作数据的查询(尽管有些数据库在只读事务中拒绝 `INSERT` 和 `UPDATE` 语句)。相反,`readOnly` 标志会作为性能优化的提示传播给底层的 JDBC 驱动程序。

JDBC 锁定

Spring Data JDBC 支持对派生查询方法进行锁定。要在 Repository 中的给定派生查询方法上启用锁定,您需要使用 `@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 Dialect 的数据库,例如,将生成以下查询

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