最終更新日:171120 原本2017-10-08 

Spring Data JPA にてメタモデルを使用すると NullPointerException が発生する

概要

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 になる現象が発生しました。

解決策

非常に単純です。
こちらのページ での示唆のように、メタモデルとエンティティを同じパッケージに配置したら問題は解消します。