Transactionality

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

Custom transaction configuration for CRUD
interface UserRepository extends CrudRepository<User, Long> {

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

  // Further query method declarations
}

在前面的示例中,导致 findAll() 方法在 10 秒超时,并且没有 readOnly 标记的情况下运行。 更改事务行为的另一种方法是使用通常涵盖多个仓库的立面或服务实现。其目的是定义非 CRUD 操作的事务边界。以下示例展示了如何创建这样的立面:

Using a facade to define transactions for multiple repository calls
@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 Query Methods

若要让你的查询方法具有事务性,请在定义的存储库界面中使用 @Transactional,如下面的示例所示:

Using @Transactional at query methods
@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 Locking

Spring Data JDBC 支持锁定派生查询方法。若要启用在存储库内对给定派生查询方法进行锁定,请使用 @Lock 对其进行注释。类型为 LockMode 的必需值提供两个值:PESSIMISTIC_READ(保证所读数据不会被修改)和 PESSIMISTIC_WRITE(获取一个锁定以修改数据)。一些数据库没有区分这两者。在这种情况下,这两种模式都等效于 PESSIMISTIC_WRITE

Using @Lock on derived query method
interface UserRepository extends CrudRepository<User, Long> {

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

如你在上面所见,将会对 findByLastname(String lastname) 执行一个悲观读锁定。如果你使用带有 MySQL 方言的数据库,这将产生以下查询:

Resulting Sql query for MySQL dialect
Select * from user u where u.lastname = lastname LOCK IN SHARE MODE

你可以使用 LockMode.PESSIMISTIC_WRITE 替代 LockMode.PESSIMISTIC_READ