JPA的多表复杂查询

转 JPA的多表复杂查询:详细篇 

 原文连接: https://mp.weixin.qq.com/s/7J6ANppuiZJccIVN-h0T3Qsql

2017-11-10  从小爱喝AD钙 

最近工做中因为要求只能用hibernate+jpa 与数据库进行交互,在简单查询中,jpa继承CrudRepository 接口 ,而后利用jpa的方法命名规范进行jpql查询,然而在进行复杂查询时,须要继承JpaSpecificationExecutor接口利用Specification进行复杂查询,因为我本身就遇到了这一问题,查了好多资料,虽然有方法,可是都没有一个详细的讲解,以致于知道方法而不能很好的利用jpa复杂查询的方便之处。我将举几个栗子,来详细的说一下我本身在使用jpa多表复杂查询的场景和想法。 数据库

栗子1:mybatis

以一个实体类User中的几个属性进行筛选。app

  1. 名字ide

  2. IDpost

  3. 手机号fetch

这是一个单表的多条件复杂查询,因为是在几个属性中进行筛选,其中的属性的个数不知道有多少个,因此只须要利用Specification 查询就能够很方便的实现这个需求。 下面请看代码: 场景:页面上经过条件筛选,查询用户列表flex

这里有3个条件 在页面上我设置的id分别为searchName,searchId,searchMobile。 因为这个是user表 因此userRepository 继承JpaSpecificationExecutor接口,随后我建立了一个封装条件的类ui

  
  
  
  
  1. spa

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

因为我这个方法是直接分页的 因此pageNumber 和pageSize 也能够直接写入到这个类中,用于方便接收参数,主要是对下面3个参数的封装

  
  
  
  
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));    };};

这里由于都是一个表,因此只要root.get('N ')这个N对应所要查的 属性的名字就好,属性名 属性名 重要的事情说三遍。

再接下来看一组多表的查询

栗子2:

这里有4张表

  
  
  
  
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;}

如今要根据userdetai 种的 sex actor中的actortype 还有 region的id 为条件查询出知足条件的living。

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

首先我仍是封装了这样一个类,可是这里的泛型 我是直接给到了想要的查询结果的泛型,接下来 由于这里涉及到了一个 多表的查询 因此上面的单表查询的例子 已经不适合这个查询了,可是Criteria 的join方法 给咱们提供了一个模式

  
  
  
  
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));    };};

这里是我对条件进行的封装。jpa 的多条件查询 主要是根据Criteria 为咱们提供的方法封装条件,而后根据 给条件定义的位置,再生成sql语句,以后完成查询。 不得不说的地方,在这个多表的查询中如下面这句为例

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

jointype.LEFT主要是说最终的这个属性 是在哪一个表中, 而前面的 “actor” 则表示 从living表中 查询的 第一步的查询,好比我给出的例子 是要查询出 living 中的 actor 而后是actor 中的userdetail 以后才是 userdetail中的 sex属性 因此下面的join.get("userDetail").get("sex") ,这里就是get出相应的属性,一直到你获得想要的属性为止。 接下来的两个属性 也同理, 许多人多jpa 有很大的误解,认为jpa 的多表,多条件复杂查询,不如mybatis的查询,在以前我也是这么以为,但自从经过jpa 实现了这个多表多条件的复杂查询以后,我以为hibernate的复杂查询 不逊于mybatis ,尤为是对sql 语句不是很精通的码农,虽然hibernate的门槛较高可jpa 偏偏下降了hibernate 所须要的门槛,但愿你们能够经过个人经验,更方便的与数据库进行交互。