トランザクション性

CrudRepository インスタンスのメソッドは、デフォルトでトランザクションです。読み取り操作の場合、トランザクション構成 readOnly フラグは true に設定されます。他のすべては、デフォルトのトランザクション構成が適用されるように、プレーンな @Transactional アノテーションで構成されています。詳細については、SimpleJdbcRepository (Javadoc) の Javadoc を参照してください。リポジトリで宣言されたメソッドの 1 つのトランザクション構成を微調整する必要がある場合は、次のように、リポジトリインターフェースでメソッドを再宣言します。

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 は異なる接続でクエリを実行します。これにより、接続プールに過度の負担がかかる可能性があり、複数のメソッドが 1 つの接続を保持している間に新しい接続をリクエストすると、デッドロックが発生する可能性もあります。
readOnly フラグを設定することにより、読み取り専用クエリをそのようにマークすることは間違いなく合理的です。ただし、これは、操作クエリをトリガーしないことのチェックとしては機能しません(ただし、一部のデータベースは、読み取り専用トランザクション内の INSERT および UPDATE ステートメントを拒否します)。代わりに、readOnly フラグは、パフォーマンスを最適化するためのヒントとして、基盤となる JDBC ドライバーに伝達されます。

JDBC ロック

Spring Data JDBC は、派生クエリメソッドのロックをサポートします。リポジトリ内の特定の派生クエリメソッドのロックを有効にするには、@Lock でアノテーションを付けます。型 LockMode の必須値には、読み取っているデータが変更されないことを保証する PESSIMISTIC_READ と、データを変更するためのロックを取得する PESSIMISTIC_WRITE の 2 つの値があります。一部のデータベースでは、この区別がされません。その場合、両方のモードは 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