LoginSignup
2
2

More than 5 years have passed since last update.

Spring Data JPAでSpecificationを使ったexists

Posted at

こんな構造のテーブルがあるとする。

ER.png

Spring Data JPAを使ってちょっと複雑なクエリを発行したい場合にSpecificationを使うが、existsを使う場合は、次のようなEntity構造だと、問題が発生した。(setter/getterは省略)
ちなみに、Spring Bootのバージョンは1.5.4.RELEASE。

User.java

@Entity
public class User {
    @Id
    private String userId;

    private String name;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Phone> services;
}
Phone.java
@Entity
public class Phone {
    @Id
    private Integer phoneId;

    @ManyToOne
    @JoinColumn(name = "USER_ID")
    private User user;

    private Integer type;

    private String number;
}

これらのEntityを用いて、「Phoneが1つでもあるUserを取得」するためのexists句を

public interface UserRepository extends CrudRepository<User, Integer>, JpaSpecificationExecutor<User> {

    static Specification<User> existsPhone() {
        return (root, query, cb) -> {
            Subquery<Integer> subquery = query.subquery(Integer.class);
            Root<Phone> subRoot = subquery.from(Phone.class);
            return cb.exists(subquery.select(cb.literal(1)).where(
                    cb.equal(root, subRoot.get("user").get("userId")));
        };
    }

}

のように作って

userRepository.findAll(Specifications.where(UserRepository.existsPhone()));

と実行したところ、StackOverflowErrorが発生した。

どうやら、User→Phone→User→Phone→・・・と循環参照してしまっているらしい。。

で、次のようにPhoneからのUser参照を無くせば、エラーは出なくなった。

User.java

@Entity
public class User {
    @Id
    private String userId;

    private String name;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "userId")
    private List<Phone> services;
}
Phone.java
@Entity
public class Phone {
    @Id
    private Integer phoneId;

    private String userId;

    private Integer type;

    private String number;
}
public interface UserRepository extends CrudRepository<User, Integer>, JpaSpecificationExecutor<User> {

    static Specification<User> existsPhone() {
        return (root, query, cb) -> {
            Subquery<Integer> subquery = query.subquery(Integer.class);
            Root<Phone> subRoot = subquery.from(Phone.class);
            return cb.exists(subquery.select(cb.literal(1)).where(
                    cb.equal(root, subRoot.get("userId")));
        };
    }

}

PhoneからUserを参照することもないので、これでいいっか。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2