概要
JPA にてメタモデルを使用して、実装した結果、NullPointerException が発生しました。
詳細
JPA では、メタモデルの機能を提供しています。
メタモデルはエンティティの構造を明確にするものです。
メタモデルを使用せず、Criteria API を書くと下記のような実装になります。
NumberSpecification.java
public class NumberSpecification {
public static Specification<Group> members(Map<Integer, Integer> lowerUpper) {
return (root, query, cb) -> {
final Collection<Predicate> predicates = new ArrayList<>();
lowerUpper.forEach((lower, upper) -> {
predicates.add(
cb.and(cb.between(root.get("number"), lower, upper))
);
});
return cb.or(predicates.toArray(new Predicate[predicates.size()]));
};
}
}
上記の場合、文字列で Group クラスの number フィールドを指定して、下記のような条件を作成しています。
condition.sql
where number between 1 and 2 or number between 3 and 6 ...
上記の文字列指定ではフィールド名が "number" から違う名前に変わったとき正常に動作しません。
メタモデルを使用すると、この問題を解消できます。
Group.java
@Entity
@Table(name ="group_member")
public class Group extends Artist {
@Getter
private int number;
}
上記エンティティのメタモデルを作成する場合、@StaticMetamodel() アノテーションをつけて、該当のエンティティのクラス名の最後に "_" をつけたクラスを作成します。
Group_.java
@StaticMetamodel(Group.class)
public class Group_ {
public static volatile SingularAttribute<Group, Integer> number;
}
これにより下記のように Specification を記述できます。
NumberSpecificationWithMetamodel.java
public class NumberSpecificationWithMetamodel {
public static Specification<Group> members(Map<Integer, Integer> lowerUpper) {
return (root, query, cb) -> {
final Collection<Predicate> predicates = new ArrayList<>();
lowerUpper.forEach((lower, upper) -> {
predicates.add(
cb.and(cb.between(root.get(Group_.number), lower, upper))
);
});
return cb.or(predicates.toArray(new Predicate[predicates.size()]));
};
}
}
しかしながら、上記実装において、Group_.number が Null になる現象が発生しました。
解決策
非常に単純です。
こちらのページ での示唆のように、メタモデルとエンティティを同じパッケージに配置したら問題は解消します。