【エラー解決】org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ResultSet に列名 DTYPE は見つかりませんでした。
SpringとJPAを使って自作のアプリを作っていたらDaoで「ResultSet に列名 DTYPE は見つかりませんでした。」というエラーがでたので、原因と解決法をメモ。
サマリー
エラー内容
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ResultSet に列名 DTYPE は見つかりませんでした。
原因
Entityの継承関係。親クラス、子クラスの両方に@Entityをつけるなんてナシだよというおはなし。
解決策
継承元の親クラスEntityに@MappedSuperclassアノテーションだけつけて、それを継承した子クラスEntityに@Entityをつける。
用語
DTYPE・・・継承関係のあるクラスを区別するための識別子。
エラー内容
ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper - ResultSet に列名 DTYPE は見つかりませんでした。
DTYPEってなんだと思ってググると
JPA永続性プロバイダは、デフォルトで、継承階層でクラスを区別するためのDTYPEという識別子列を作成します。
※引用元:JPA注釈の参照情報
ということで原因はEntityの継承関係にありそう。というか実際そうだった。
英語では
the Persistence provider assumes a default column name of DTYPE and column type of DiscriminatorType.STRING.
とか書いてあるのでDiscriminatorTypeの略なのだろう。Discriminatorは識別という意味だそうだ。
対処法
解決策としては共通部分のEntityに@MappedSuperclassアノテーションをつけて、それを継承した2つのクラスを作る。
勝手な推測だけど、子クラスにも@Entityをつけると、hibernate.loaderがQueryを実行して得られるObjectに親クラスと子クラスの両方のEntityの型を関連づけてしまうので、どっちのEntityに変換すればいいのかわからなくなるみたいな感じ?
エラーが起きたソース
HogeEntity.java
@Entity @Table(name="hoge") public class HogeEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column protected Long id; @Column private String name; // getter, setterは省略 }
HogeHogeEntity.java
@Entity // <--ココが原因! public class HogeHogeEntity extends HogeEntity{ @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column protected Long id; @Column private Long fuga_id; // getter, setterは省略 }
HogeDao.java
/** * Idでデータを取得 * @return */ public Optional<HogeEntity> selectById(long id) { EntityManager manager = factory.createEntityManager(); StringBuilder sqlBld = new StringBuilder(); sqlBld.append(" SELECT "); sqlBld.append(" * "); sqlBld.append(" FROM hoge "); sqlBld.append(" WHERE "); sqlBld.append(" id = :id "); Query query = manager.createNativeQuery(sqlBld.toString(), HogeParentEntity.class); query.setParameter("id", id); @SuppressWarnings("unchecked") List<HogeEntity> list = query.getResultList(); // <--ここでエラー manager.close(); Optional<HogeEntity> optEntity = Optional.ofNullable(list.isEmpty() ? null : list.get(0)); return optEntity; }
※Optionalで書いてあるのは、今回のエラーには関係ない。
ちなみにgetResultList()ではなく、getSingleResult()を使った場合は、
クエリー結果のObjectをEntityに変換する処理で、ClassCastExceptionが発生する。対処法は同じ。
Object result = query.getSingleResult(); if (result != null) { HogeParentEntity entity = (HogeParentEntity ) result; // ここでClassCastException }
修正後のソース
HogeAbstractEntity.java
@MappedSuperclass // <--ココを修正! public class HogeAbstractEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column protected Long id; @Column private String name; // getter, setterは省略 }
HogeEntity.java
@Entity @Table(name="hoge") public class HogeEntity extends HogeAbstractEntity { // 何も書かない。継承するだけ。 }
HogeHogeEntity.java
@Entity public class HogeHogeEntity extends HogeAbstractEntity { @Column private Long fuga_id; // getter, setterは省略 }
共通部分は@MappedSuperclassアノテーションをつけて、切り出してあげる。
実際に使うEntityは@MappedSuperclassをつけたEntityを継承して実装するかたち。
DTYPEなんてわかりにくいエラーメッセージやめてほしいよね。