Joined Subclass の バルク更新

JPQLのバルク更新はポリモーフィックです。どういうことかというと、たとえば親エンティティへのDELETE操作は子エンティティへのDELETE操作でもあるのです。その結果、マッピングによってはJPQLのDELETE文がSQLでは複数のDELETE文になることがあります。

Java EE勉強会でこのことを実際に動かして示したかったのですがうまく動かなくてお見せできませんでした。でも、家に帰ってもういちど試してみるとちゃんと動きました。警告のログが出るのですが、これを例外が発生して正常に動作していないと思ってしまったようです。

次回でもいいのですが、忘れないうちにちょっと書いておきます。以下簡単なサンプルです。

  • エンティティの定義。Joined Subclassの継承関係があります。
@Entity @Inheritance(strategy = InheritanceType.JOINED) public abstract Project {...}
@Entity public class DesignProject extends Project {...}
@Entity public class QualityProjectProject extends Project {...}
  • バルクDELETEのJPQL。
DELETE FROM Project p WHERE p.name like ('a%')
  • DBにはH2 Databaseを使います
  • 上記JPQLの実行結果(発行されるSQLとそのときの警告のログ出力)
Hibernate: insert into HT_Project select project0_.id as id from Project project0_ where name like 'a%'
Hibernate: delete from QualityProject where (id) IN (select id from HT_Project)
Hibernate: delete from DesignProject where (id) IN (select id from HT_Project)
Hibernate: delete from Project where (id) IN (select id from HT_Project)
2007-01-28 10:17:51,046 [main] WARN  org.hibernate.hql.ast.exec.MultiTableDeleteExecutor - unable to drop temporary id table after use [Timeout trying to lock table HT_PROJECT [HYT00-40]
org.h2.jdbc.JdbcSQLException: Timeout trying to lock table HT_PROJECT [HYT00-40]
	at org.h2.message.Message.getSQLException(Message.java:67)
	at org.h2.message.Message.getSQLException(Message.java:49)
	at org.h2.table.TableData.lock(TableData.java:311)
	at org.h2.command.ddl.DropTable.prepareDrop(DropTable.java:61)
	at org.h2.command.ddl.DropTable.update(DropTable.java:83)
	at org.h2.command.CommandContainer.update(CommandContainer.java:64)
	at org.h2.command.Command.executeUpdate(Command.java:120)
	at org.h2.server.TcpServerThread.process(TcpServerThread.java:209)
	at org.h2.server.TcpServerThread.run(TcpServerThread.java:86)
	at java.lang.Thread.run(Unknown Source)
 [HYT00-40]]

JPQLはひとつのDELETE文ですが、SQLでは3つのDELETE文になっています。警告は出るのですが結果には影響ないです。


バルクDELETEは次のような流れで行われるようです。

  1. 一時テーブル(ここではHT_Project)を作成
  2. 一時テーブルにJPQLで指定されたエンティティに対応するテーブル(ここではProject)の削除対象IDを格納
  3. 継承関係に属するテーブル(ここではDesignProject、QualityProject、Project)に対し一時テーブルに格納した値と同じIDをもつレコードを削除
  4. 一時テーブルを除去

2・3番目と4番目の処理は別トランザクションで実行されるのですが、2番目で一時テーブルのレコードを排他ロックしているため4番目の処理が実行できずタイムアウトとなってこれが警告として表示されるみたいです。結果、一時テーブルは残ってしまうのですが、1番目の処理ではちゃんと存在チェックを行っているのでもう一度同じ処理をしても大丈夫です。

このあたり、HibernateはDBのMetaDataやDialectを見て処理方法を変えているのですが、H2 Databaseについてはうまくいっていないっぽいです。他のDBは試していないのですけど...