◀ 10.

Not supported for DML operations【Springエラー】

▶
この記事の要点
  • Hibernate の QueryExecutionRequestException: Not supported for DML operations
  • 直接条件: UPDATE / DELETEを検索結果取得のAPIで実行。Spring Data JPAの@Queryでは@Modifying不足を確認
  • 対処: 更新として実行し、書込み可能なトランザクション内で呼ぶ。@Modifyingと@Transactionalは別の役割
  • @Modifying のページも参照

 

エラー内容

Caused by: java.lang.RuntimeException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [UPDATE ... ];

nested exception is java.lang.IllegalStateException: org.hibernate.hql.internal.QueryExecutionRequestException: Not supported for DML operations [UPDATE ... ]

 

発生条件/原因/対処法

Modifyingで更新として実行する設定と、Transactionalの書込みトランザクション境界を分けて確認する図

JPAで更新系のSQLを実行した際に発生するエラー。

掲載例外は、更新クエリを検索用APIで実行した場合のエラーです。今回、Hibernate 5.6.15.FinalとSpring Data JPA 2.7.18で、@Transactionalだけの@Queryを呼ぶと同じNot supported for DML operationsを再現しました。トランザクション不足とは区別します。@Modifyingだけでトランザクション外から呼んだ場合は、この検証ではTransactionRequiredExceptionでした。

Spring Data JPAの@QueryにUPDATE / DELETEを定義するときは、以下のように@Modifyingを付けて更新として実行します。さらに実際の呼出しが書込み可能なトランザクション内になる必要があります。アノテーションを付けるだけで、設定・呼出し方に関係なく必ずトランザクションが始まるという意味ではありません。

@Repository
public interface TestRepository extends JpaRepository<TestEntity, String>{
    
    @Transactional

    @Modifying
    @Query("UPDATE TestEntity te SET te.colA = 1 WHERE te.id = :id")
    Integer updateTest(@Param("id")String id);
}

 

二つの設定の役割

設定役割と確認
@Modifying@Queryの更新クエリをexecuteUpdateで実行する。トランザクションを作る設定ではない
@TransactionalSpringが管理する呼出しのトランザクション境界。クエリを検索から更新へ切り替える設定ではない
外側のサービスのトランザクション業務処理全体の境界。既存のreadOnlyや伝播設定を確認する

派生したdeleteByメソッドや独自実装の全メソッドへ無条件に@Modifyingを付けるものではありません。EntityManagerを直接使うなら、更新クエリをgetResultList / getSingleResultではなくexecuteUpdateで実行するか確認します。

ファイル単位の検証例

以下は旧APIのjavax.persistenceを使うHibernate 5.6 / Spring Data JPA 2.7向けです。現行のjakarta.persistenceを使う構成へ古い依存JARを導入する例ではありません。TestEntity.javaとTestRepository.javaは別ファイルで、既存のSpring/JPA設定とRepositoryスキャンが必要です。

example/TestEntity.java

package example;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "test_entity")
public class TestEntity {
    @Id
    private String id;
    private int colA;

    protected TestEntity() {}
    public TestEntity(String id, int colA) { this.id = id; this.colA = colA; }
    public String getId() { return id; }
    public int getColA() { return colA; }
}

example/TestRepository.java

package example;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

public interface TestRepository extends JpaRepository<TestEntity, String> {
    @Transactional
    @Modifying
    @Query("UPDATE TestEntity te SET te.colA = 1 WHERE te.id = :id")
    int updateTest(@Param("id") String id);
}

JPQLのTestEntityとcolAはEntity名・Java属性名です。テーブル名test_entityやDBカラム名をそのまま書くSQLではありません。nativeQuery=trueでネイティブSQLを使う場合とは区別してください。元のInteger戻り値も更新件数を表せます。上の例ではintを使い、存在するidで1、存在しないidで0になることを確認しました。0は例外ではなく、条件に該当する行がないなどの結果として調べます。

更新後の値が古く見える場合

一括更新は既に保持しているEntityの値を自動で書き換えるとは限りません。この検証では更新件数1でも保持中のcolAは0のままで、永続化コンテキストをclearして再取得すると1になりました。DB更新失敗と決めつけず、保持中のEntityと再取得した結果を区別します。

clearAutomatically=trueを使う場合、未flushの変更を失うおそれがあります。flushAutomatically=trueも含め、他の変更・サービスのトランザクション全体への影響を検証し、症状だけを隠すために一律付与しないでください。

確認手順

  1. Spring Data JPA・Hibernate・SpringとJPA APIの版、例外の最も内側の原因を記録する。
  2. @Queryの文がSELECTかUPDATE / DELETEか、Modifyingのimportがorg.springframework.data.jpa.repository.Modifyingか確認する。
  3. 実際に呼ぶBean、トランザクション管理、外側のreadOnly設定を確認する。自己呼出しなど、Springのプロキシを通らない呼出しにも注意する。
  4. 隔離した検証DBで対象あり/なしの更新件数と、コミット後に再取得した値を確認する。

今回はH2のメモリDBと実際のSpring Repositoryプロキシで5テストを実行しました。旧例外の再現、トランザクション不足との区別、更新とコミット、対象なし、保持中Entityの古い値を確認しています。本番DB・実システム全体・別Hibernate版で同じ例外文になることは検証していません。

公式資料: Spring Data JPAの更新クエリ、トランザクション、Hibernate 5.6.15の検索/更新の実装(2026年10月確認)。

子ページ

子ページはありません

同階層のページ
  1. java.lang.IllegalStateException: CGLIB is required to process @Configuration classes
  2. 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
  3. No mapping found for HTTP request with URI ... in DispatcherServlet with name ...
  4. An internal error occurred during: "Building UI model". com/google/common/base/Function
  5. No identifier specified for entity : ...
  6. org.hibernate.hql.internal.ast.QuerySyntaxException: table_name is not mapped
  7. No compiler is provided in this environment
  8. 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
  9. Caused by: java.lang.RuntimeException: Executing an update/delete query
  10. Not supported for DML operations
  11. Field ... required a bean of type ... that could not be found.
  12. 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]
  13. Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.
  14. Exception in thread "main" java.lang.UnsupportedClassVersionError