JPA Multi-table Complex Query

In recent work, because only hibernate+jpa is required to interact with database, in simple queries, jpa inherits CrudRepository interface, and then uses jpa method naming specification to query jpql. However, in complex queries, it is necessary to inherit jpa Specification Executor interface and use Specification to perform complex queries. Inquiry, because I have encountered this problem myself, I have checked a lot of information, although there are methods, but there is no detailed explanation, so that I know the method and can not make good use of the convenience of jpa complex query. I will cite a few chestnuts to elaborate on my own scenarios and ideas for using jpa multi-table complex queries.

Chestnut 1:

Filter with several attributes in an entity class User.

1. Name
2. ID
 3. Mobile phone number

This is a multi-condition complex query of a single table. Because it is filtered in several attributes, the number of attributes among them is not known how many, so it is very convenient to realize this requirement only by using Specification query. See the code below:
Scenario: Query the list of users through conditional filtering on the page

There are three conditions on the page that I set the id as searchName, searchId and searchMobile. Because this is the user table, userRepository inherits the JpaSpecification Executor interface, and then I create a class that encapsulates conditions.

public class PageParam<T> {
    private Integer pageSize = 10;
    private Integer pageNumber = 1;
    private String searchName;
    private String searchMobile;
    private String searchId;
}

Since this method is paging directly, pageNumber and pageSize can also be written directly into this class to facilitate the reception of parameters, mainly encapsulating the following three parameters.

Specification<T> specification = new Specification<T>() {

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> list = new ArrayList<Predicate>();

        if (StringUtils.isNotBlank(searchName)) {
            list.add(cb.like(root.get("name").as(String.class), "%" + searchName + "%"));
        }

        if (StringUtils.isNotBlank(searchId)) {
            list.add(cb.equal(root.get("id").as(Long.class), searchId));
        }

        if (StringUtils.isNotBlank(searchMobile)) {
            list.add(cb.like(root.get("mobile").as(String.class), "%" + searchMobile + "%"));
        }

        Predicate[] p = new Predicate[list.size()];
        return cb.and(list.toArray(p));
    };
};

Because it's all a table here, as long as the root.get('N') N corresponds to the name of the attribute to be checked, the important thing of the attribute name is said three times.
Next, let's look at a set of multi-table queries

Chestnut 2:

Here are four tables.

public class Living {
    Long id;
    
    @ManyToOne
    @JsonIgnore
    @JoinColumn(name = "actorId", foreignKey = @ForeignKey(name = "none", value =ConstraintMode.NO_CONSTRAINT))
    public Actor actor;
    
   @ManyToOne
    @JsonIgnore
    @JoinColumn(name = "regionId", foreignKey = @ForeignKey(name = "none", value =ConstraintMode.NO_CONSTRAINT))
    public Region region;
}
    
public class Actor {
    Long id;
    
    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH }, fetch = FetchType.LAZY)
    @JoinColumn(name = "actorId")
    @org.hibernate.annotations.ForeignKey(name = "none")
    List<Living> livings = new ArrayList<>();
    
   @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH }, fetch = FetchType.LAZY)
    @org.hibernate.annotations.ForeignKey(name = "none")
    @JoinColumn(name = "userDetailId", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
    UserDetail userDetail;
    
   @Column(nullable = false)
    @Enumerated(value = EnumType.ORDINAL)
    ActorType actorType = ActorType.A;
    
    public enum ActorType{
        A,B,C
    }
}
    
public class UserDetail {
    Long id; 
    
   @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH }, fetch = FetchType.LAZY)
    @org.hibernate.annotations.ForeignKey(name = "none")
    @JoinColumn(name = "actorId", foreignKey = @ForeignKey(name = "none", value = ConstraintMode.NO_CONSTRAINT))
    Actor actor;
    
    String truename;
}
    
public class Region {
    Long id;
    
    String name;
    
    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH }, fetch = FetchType.LAZY)
    @JoinColumn(name = "regionId")
    @org.hibernate.annotations.ForeignKey(name = "none")
    List<Living> Livings;
}
    

Now we need to query the eligible living according to the actortype of sex actor s of userdetai and the id of region.

public class PageParam<Living> {
    private Integer pageSize = 10;
    private Integer pageNumber = 1;
    private Sex sex;
    private ActorType actortype;
    private Long cityid;

First of all, I encapsulated such a class, but I gave the generic type directly to the desired query results. Next, because there is a multi-table query involved, the above example of single-table query is no longer suitable for this query, but Criteria's join method provides us with a method. Pattern

Specification<Living> specification = new Specification<Living>() {

    @Override
    public Predicate toPredicate(Root<Living> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> list = new ArrayList<Predicate>();

        if (null!=sex) {
            Join<UserDetail, Living> join = root.join("actor", JoinType.LEFT);
            list.add(cb.equal(join.get("userDetail").get("sex"),  sex ));
        }

        if (null!=actortype) {
            Join<Actor, Living> join = root.join("actor", JoinType.LEFT);
            list.add(cb.equal(join.get("actorType"),  actortype));
        }
        if (null!=cityid) {
            Join<Region, Living> join = root.join("region", JoinType.LEFT);
            list.add(cb.equal(join.get("id"), cityid));
        }

        //Join<A, B> join = root.join("bs", JoinType.LEFT);
        //list.add(cb.equal(join.get("c").get("id"), id));
        Predicate[] p = new Predicate[list.size()];
        return cb.and(list.toArray(p));
    };
};

Here is my encapsulation of the conditions. jpa's multi-conditional queries are mainly based on the method provided by Criteria to encapsulate the conditions, and then regenerate sql statements according to the location defined for the conditions, and then complete the query.
What I have to say is that in this multi-table query, the following sentence is taken as an example.

Join<UserDetail, Living> join = root.join("actor", JoinType.LEFT);
list.add(cb.equal(join.get("userDetail").get("sex"),  sex ));

Joint type. LEFT mainly refers to which table the final attribute is in, while the previous "actor" represents the first step of querying from the living table. For example, the example I gave is to query the actor in living and then the user table in actor before the sex attribute in userdetail. join.get("userDetail"). get("sex"), here is get out the corresponding attributes until you get the desired attributes. The next two attributes are the same.
Many people have a great misunderstanding about jpa. They think that jpa's multi-table and multi-condition complex query is not as good as mybatis's query. I also think so before. But since the implementation of this multi-table and multi-condition complex query through jpa, I think hibernate's complex query is no less than mybatis's, especially for sql statements. Proficient code farmers, although the threshold of Hibernate is higher, JPA just reduces the threshold needed by hibernate. I hope you can interact with the database more conveniently through my experience.

Keywords: Java Hibernate Attribute Database Mobile

Added by chenci on Sat, 03 Aug 2019 12:15:26 +0300