| この記事の要点 |
|
エラー内容
Spring Data JPA のリポジトリで @Query + UPDATE/DELETE を実行すると以下が発生:
Caused by: java.lang.RuntimeException: Executing an update/delete query;
nested exception is javax.persistence.TransactionRequiredException:
Executing an update/delete query
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(...)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(...)
at ...
原因
JPA の仕様では UPDATE / DELETE / INSERT クエリは必ずトランザクション内で実行する必要があります。SELECT は不要ですが、データ変更系には明示的なトランザクション境界が要求されます。
Spring Data JPA はデフォルトでリポジトリメソッドに read-only トランザクションを開きますが、@Modifying 付きの更新クエリには別途指定が必要です。
発生する典型コード
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// ❌ NG: @Modifying と @Transactional がない
@Query("UPDATE User u SET u.lastLoginAt = :now WHERE u.id = :id")
void updateLastLogin(@Param("id") Long id, @Param("now") LocalDateTime now);
}
@Service
public class LoginService {
@Autowired
private UserRepository userRepository;
public void onLogin(Long userId) {
// ↓ ここで TransactionRequiredException
userRepository.updateLastLogin(userId, LocalDateTime.now());
}
}
対処1: リポジトリで @Modifying + @Transactional
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying // ★ UPDATE/DELETE クエリには必須
@Transactional // ★ 更新トランザクションを開く
@Query("UPDATE User u SET u.lastLoginAt = :now WHERE u.id = :id")
void updateLastLogin(@Param("id") Long id, @Param("now") LocalDateTime now);
}
@Modifying はこのクエリが SELECT ではないことを Spring に伝えます。これが無いと「結果セットが返ってこない」エラーになります。
@Transactional はメソッド呼び出し時にトランザクションを開始します。
対処2: Service 層に @Transactional(推奨)
リポジトリではなく、Service 層でトランザクション境界を引く方が設計上きれい。複数の更新を 1 トランザクションでまとめられます:
@Service
public class LoginService {
@Autowired
private UserRepository userRepository;
@Autowired
private LoginLogRepository loginLogRepository;
@Transactional // ★ Service のメソッド単位でトランザクション
public void onLogin(Long userId) {
userRepository.updateLastLogin(userId, LocalDateTime.now());
loginLogRepository.save(new LoginLog(userId, LocalDateTime.now()));
// 両方コミット or 両方ロールバック
}
}
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying // ★ これは依然として必要
@Query("UPDATE User u SET u.lastLoginAt = :now WHERE u.id = :id")
void updateLastLogin(@Param("id") Long id, @Param("now") LocalDateTime now);
}
@Modifying のオプション
| オプション | 動作 | 用途 |
|---|---|---|
| flushAutomatically = true | クエリ実行前に永続化コンテキストを flush | 同じトランザクション内で entity 変更と直接 UPDATE 両方使う場合 |
| clearAutomatically = true | クエリ実行後に永続化コンテキストをクリア | UPDATE 後にエンティティをロードし直したい場合 |
@Modifying(flushAutomatically = true, clearAutomatically = true)
@Transactional
@Query("UPDATE User u SET u.status = :status WHERE u.id = :id")
int updateStatus(@Param("id") Long id, @Param("status") String status);
戻り値の意味
@Modifying 付きメソッドは戻り値で更新行数を取得できます:
@Modifying
@Transactional
@Query("UPDATE User u SET u.deleted = true WHERE u.expiredAt < :now")
int softDeleteExpired(@Param("now") LocalDateTime now);
// 呼び出し
int count = userRepository.softDeleteExpired(LocalDateTime.now());
log.info("論理削除した行数: {}", count);
JpaRepository.save() は何故問題ないか
save() や delete() など Spring Data JPA の標準メソッドは内部で自動的にトランザクションを開く設計です。手動の @Query UPDATE/DELETE だけが明示的な指定を必要とします。
| メソッド | トランザクション | @Modifying |
|---|---|---|
save() / saveAll() | 自動 | 不要 |
delete() / deleteAll() | 自動 | 不要 |
@Query SELECT ... | 不要(read-only) | 不要 |
@Query UPDATE/DELETE ... | ★ 必要 | ★ 必要 |
@Query INSERT ... (ネイティブ) | ★ 必要 | ★ 必要 |
@Transactional の付け場所のベストプラクティス
- Service クラスのメソッドに付けるのが基本(トランザクション境界 = ビジネスロジック単位)
- リポジトリの個別メソッドに付けるのは「単独で UPDATE するメソッド」だけ
- クラスレベル
@Transactionalでクラス全メソッドを対象にできる - readOnly = true を SELECT 系メソッドに付けるとパフォーマンス向上
- 例外時のロールバック: デフォルトは
RuntimeExceptionのみ。checked例外でロールバックさせるにはrollbackFor = Exception.class
関連エラー
- "Not supported for DML operations" — JPQL でなくネイティブクエリで UPDATE/DELETE する場合 →
@Query(value = "...", nativeQuery = true) - "No EntityManager with actual transaction available" — Service 層に
@Transactional付け忘れの典型 - "Cannot acquire transaction" — DataSource 設定ミス / 接続プール枯渇
- "Could not commit JPA transaction" — コミット時の制約違反 / 楽観ロック失敗など
子ページ
子ページはありません
同階層のページ
- java.lang.IllegalStateException: CGLIB is required to process @Configuration classes
- Error creating bean with name 'org.springframework.aop.config.internalAutoProxyCreator': Instantiation of bean failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
- No mapping found for HTTP request with URI ... in DispatcherServlet with name ...
- An internal error occurred during: "Building UI model". com/google/common/base/Function
- No identifier specified for entity : ...
- org.hibernate.hql.internal.ast.QuerySyntaxException: table_name is not mapped
- No compiler is provided in this environment
- java.sql.SQLException: The server time zone value ' ... ' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone
- Caused by: java.lang.RuntimeException: Executing an update/delete query
- Not supported for DML operations
- Field ... required a bean of type ... that could not be found.
- Annotation-specified bean name 'xxxController' for bean class [com.xxx.controller.xxxController] conflicts with existing, non-compatible bean definition of same name and class [com.xxx.xxxController]
- Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.
- Exception in thread "main" java.lang.UnsupportedClassVersionError
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 7 Spring Frameworkのアノテーション一覧
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- プロジェクトをTomcatプロジェクトとして認識させる方法 2026-10-07 22:32:50
- MySQLの1366 Incorrect string value|Laravelの文字コード・絵文字エラー 2026-10-07 21:54:03
- curlの証明書ホスト名不一致|旧エラー51・現行60の確認と対処 2026-10-07 21:54:03
- LaravelのMassAssignmentException|fillableの原因と安全な対処 2026-10-07 21:54:03
- Eclipse で Tomcat の起動ログがコンソールに出ない時の確認手順 2026-10-07 21:54:02
- MySQLにおける中央値(Median)の導き方(バージョン8未満) 2026-10-07 13:49:45
- getInputForward 2026-10-07 13:41:15
- JSONから配列に変換 2026-10-07 13:41:15
- ビューから値をモデルに格納しコントローラーで受け取る方法 2026-10-07 13:23:41
- Laravelのテーブル作成と定義変更|マイグレーション・up/down・注意点 2026-10-07 13:23:41
- NumPy 配列に要素を追加する方法 (append / concatenate) 2026-10-07 13:23:41
- MariaDB・MySQLで現在日時を取得する方法|NOW・タイムゾーン・保存型 2026-10-07 13:13:36
- 【django】テンプレートで定数を使用する方法 2026-10-07 13:10:15
- Spring BootにおけるApplication.propertiesの環境依存設定の分割方法 2026-10-07 12:09:35
- Not supported for DML operations【Springエラー】 2026-10-07 11:09:38