Transactionality

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

The methods of CrudRepository instances are transactional by default. For reading operations, the transaction configuration readOnly flag is set to true. All others are configured with a plain @Transactional annotation so that default transaction configuration applies. For details, see the Javadoc of SimpleJdbcRepository. If you need to tweak transaction configuration for one of the methods declared in a repository, redeclare the method in your repository interface, as follows:

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 标记的情况下运行。

The preceding causes the findAll() method to be run with a timeout of 10 seconds and without the readOnly flag.

更改事务行为的另一种方法是使用通常涵盖多个仓库的立面或服务实现。其目的是定义非 CRUD 操作的事务边界。以下示例展示了如何创建这样的立面:

Another way to alter transactional behavior is by using a facade or service implementation that typically covers more than one repository. Its purpose is to define transactional boundaries for non-CRUD operations. The following example shows how to create such a facade:

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,以便为立面工作获取基于注释的配置。请注意,前面的示例假设你使用了组件扫描。

The preceding example causes calls to addRoleToAllUsers(…) to run inside a transaction (participating in an existing one or creating a new one if none are already running). The transaction configuration for the repositories is neglected, as the outer transaction configuration determines the actual repository to be used. Note that you have to explicitly activate <tx:annotation-driven /> or use @EnableTransactionManagement to get annotation-based configuration for facades working. Note that the preceding example assumes you use component scanning.

Transactional Query Methods

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

To let your query methods be transactional, use @Transactional at the repository interface you define, as the following example shows:

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

Typically, you want the readOnly flag to be set to true, because most of the query methods only read data. In contrast to that, deleteInactiveUsers() uses the @Modifying annotation and overrides the transaction configuration. Thus, the method is with the readOnly flag set to false.

强烈建议使查询方法具有事务性。这些方法可能会执行多个查询,以填充一个实体。如果没有公共事务,Spring Data JDBC 将在不同的连接中执行查询。当多个方法在持有连接的同时请求一个新的连接时,这可能会给连接池带来过度的压力,甚至可能导致死锁。

It is highly recommended to make query methods transactional. These methods might execute more than one query in order to populate an entity. Without a common transaction Spring Data JDBC executes the queries in different connections. This may put excessive strain on the connection pool and might even lead to dead locks when multiple methods request a fresh connection while holding on to one.

通过设置 readOnly 标志将只读查询标记为只读查询,这绝对是合理的。但是,这并不能保证你不会触发操作查询(尽管一些数据库会在只读事务中拒绝 INSERTUPDATE 语句)。相反,readOnly 标志会作为提示传播给底层的 JDBC 驱动程序,以进行性能优化。

It is definitely reasonable to mark read-only queries as such by setting the readOnly flag. This does not, however, act as a check that you do not trigger a manipulating query (although some databases reject INSERT and UPDATE statements inside a read-only transaction). Instead, the readOnly flag is propagated as a hint to the underlying JDBC driver for performance optimizations.

JDBC Locking

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

Spring Data JDBC supports locking on derived query methods. To enable locking on a given derived query method inside a repository, you annotate it with @Lock. The required value of type LockMode offers two values: PESSIMISTIC_READ which guarantees that the data you are reading doesn’t get modified and PESSIMISTIC_WRITE which obtains a lock to modify the data. Some databases do not make this distinction. In that cases both modes are equivalent of 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 方言的数据库,这将产生以下查询:

As you can see above, the method findByLastname(String lastname) will be executed with a pessimistic read lock. If you are using a databse with the MySQL Dialect this will result for example in the following query:

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

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

Alternative to LockMode.PESSIMISTIC_READ you can use LockMode.PESSIMISTIC_WRITE.