spring data jpa 实现多条件复杂查询及多表联查 您所在的位置:网站首页 javaweb条件查询怎么做 spring data jpa 实现多条件复杂查询及多表联查

spring data jpa 实现多条件复杂查询及多表联查

2024-07-15 18:21| 来源: 网络整理| 查看: 265

写了好多年了,求波点赞,收藏,关注,一键三连!!

 

最近发现JPA在处理单表时,很方便,但多表就比较复杂了。今天写一下端午这两天琢磨的多条件查询,还有多表联查。

文章比较长,大部分都是代码,不愿意看的代码copy下去,直接可以实现;想交流的可以看完,然后留言交流。

maven依赖啊,配置,继承写法等知识点不展开说了,之前写过一篇文章:

spring boot 配置及使用 spring data jpa

这里说一下更新的地方:

JPA的配置 ###################################################### ###spring data JPA配置 ###################################################### #指定JPA的DB spring.jpa.database=MYSQL #是否显示SQL spring.jpa.show-sql=true #执行DDL语句时,是创建create,创建删除create-drop,更新update spring.jpa.hibernate.ddl-auto=update #命名策略:当创建了entity,会在DB中创建一个表结构 #这个是驼峰命名法,遇到大写加下划线 #spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy #这个是默认写法,以属性名命名 spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #hibernate配置DB方言 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect

说一点,命名策略的配置更新了,以前是:

org.hibernate.cfg.DefaultNamingStrategy

org.hibernate.cfg.ImprovedNamingStrategy

但我发现配置了之后无论使用哪种都是带下划线的,所以查找了一下资料,发现现在使用上面的两个配置:

PhysicalNamingStrategyStandardImpl  默认以属性名作为字段名;

SpringPhysicalNamingStrategy 以驼峰法拆分加下划线为字段名。

其他注解几乎都写明白了,不是重点不展开赘述了。

 

然后上实例

系统分4层:entity,repository,service,controller

模拟:一个用户可以有多个地址,但是一条地址记录,只能对应一个用户。

entity:

两个实体webuser和address

因为两个实体有共性,都需要主键,创建时间,销毁时间.....所以抽出来单独写一个类。

entity父类: package com.wm.springboot.base; import java.util.Date; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import javax.persistence.Transient; import javax.persistence.Version; import com.alibaba.fastjson.annotation.JSONField; import lombok.Getter; import lombok.Setter; @MappedSuperclass //表明这是父类,可以将属性映射到子类中使用JPA生成表 public abstract class BaseEntity extends BaseClass { @JSONField(ordinal=1) @Getter @Setter @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="id",columnDefinition="int(30) comment '无意义自增主键'") protected Integer id; //无意义自增主键 @JSONField(ordinal=2,format="yyyy-MM-dd HH:mm:ss") @Getter @Setter @Column(name="createTime",columnDefinition="DATETIME comment '创建时间'") protected Date createTime; //创建时间 @JSONField(ordinal=3,format="yyyy-MM-dd HH:mm:ss") @Getter @Setter @Column(name="destroyTime",columnDefinition="DATETIME comment '销毁时间'") protected Date destroyTime; //销毁时间 @JSONField(ordinal=4) @Getter @Setter @Version @Column(name="version",nullable=false,columnDefinition="int(20) comment '版本号'") protected Integer version; @JSONField(ordinal=5) @Getter @Setter @Column(length=1,name="isValid",nullable=false,columnDefinition="int(1) comment '是否启用,1:启用 0:不启用'") protected Integer isValid; //是否启用 @Transient @JSONField(ordinal=5) @Getter @Setter protected String createTimeStart; //创建时间的开始点 @Transient @JSONField(ordinal=6) @Getter @Setter protected String createTimeEnd; //创建时间的结束点 }

1,由于是父类,所以不需要单独实例化,所以写成抽象类。

2,BaseClass不展开了,里面是国际化;这里只从baseEntity展开。

3,使用@MappedSuperclass注解,让子类在JPA生成表时可以使用父类继承来的属性。

4,@Getter @Setter 使用了插件lombok,自动生成getset方法,非常好用。多说一句:有人觉得这个东西改变了代码的写法,造成不好影响,我觉得目前使用来看,没给我造成什么不好影响,反而提高了我的效率。

5,@JSONField(ordinal=1) 继承了fastjson带的注解,这里仅做排序使用。其实可以不用写,我写习惯了。

6,@Id, 这个是生成表时的主键,按照数据库设计原则,主键应该是无意义自增主键。所以我觉得可以抽象出来放到父类;使每个表的主键都叫ID也不是什么问题;

7,@GeneratedValue(strategy=GenerationType.AUTO)  主键生成策略,自增;

8,@Column:网上一查一大堆。不过我的用法跟网上不太一样。简单说一下吧。

    a),name,映射到表时的字段名,这个和上面讲的命名策略相关。

如果使用之前的策略或者加下划线的命名策略,这里只要使用驼峰写法的,都会自动加下划线。我不想要这种命名策略,所以使用:PhysicalNamingStrategyStandardImpl,这样其实name可以省略了,但是为了规范我还是写上了。

    b),length  长度,比如String类型的属性 length写20,生成字段为varchar(20),这里注意:length的值要和后面写的columnDefinition中的例如:varchar(32)的值一致,不然启动时会报错。所以如果配置columnDefinition,建议可以不写length。

    c),nullable:能否为空  true:可以为空    false:不能为空。

这里其实还有一个属性:unique,唯一性约束,我这里发现一个问题;如果设置了unique,那么启动时会报错,但是启动能成功!而且,去表中看,唯一性约束设置成功。报错的大概意思好像是还没表无法设置唯一性约束。这里我觉得有可能涉及到底层原因。有时间再深究吧。但是我看着这个报错又难受,我就退而求其次,使用columnDefinition设置字段的唯一性约束,并且好处是还可以设置字段的备注,或者映射到表中的字段类型,以及长度。

    d),columnDefinition: 其实就是添加建表sql。例子代码都有。

9,@version  乐观锁,这个不是重点 不赘述了。

用户子类BaseUserEntity: package com.wm.springboot.base; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import com.alibaba.fastjson.annotation.JSONField; import lombok.Getter; import lombok.Setter; @MappedSuperclass //表明这是父类,可以将属性映射到子类中使用JPA生成表 public abstract class BaseUserEntity extends BaseEntity { @JSONField(ordinal=1) @Getter @Setter @Column(length=32,name="userName",nullable=false,columnDefinition="varchar(32) unique comment '用户名'") protected String userName; //用户名 @JSONField(ordinal=2) @Getter @Setter @Column(length=32,name="password",nullable=false,columnDefinition="varchar(32) default '000000' comment '密码'") protected String password; //密码 @JSONField(ordinal=3) @Getter @Setter @Column(length=64,name="email",nullable=false,columnDefinition="varchar(64) unique comment '邮箱'") protected String email; //邮箱号 @JSONField(ordinal=4) @Getter @Setter @Column(length=11,name="phoneNo",nullable=false,columnDefinition="varchar(11) unique comment '电话号码'") protected String phoneNo; //手机号 @JSONField(ordinal=5) @Getter @Setter @Column(length=32,name="realName",nullable=false,columnDefinition="varchar(32) comment '真实姓名'") protected String realName; //真实姓名 }

注解参考上面解释。这么写的思路是,假设一个系统分管理用用户,和网站用户。这样用户也会有共同特性。所以再抽象一层。

WebUser类: package com.wm.springboot.sc.entity; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.OneToMany; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; import com.wm.springboot.base.BaseUserEntity; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; /** * 用户信息表 * 原则:ID,用户名,邮箱号,手机号,微信ID都不可重复 * @author maybe */ @NoArgsConstructor @AllArgsConstructor /* * @Entity说明这是一个实体bean,使用orm默认规则(类名=表名;属性名=字段名)关联DB; * 如果想改变这种规则:1,可以配置@Entity的name来对应DB中的表名;@Entity(name="USER") * 2,使用@Table来改变class和DB表名的映射规则;@Column来改变属性名和字段名的映射规则 */ @Entity(name="WEBUSER") public class WebUser extends BaseUserEntity{ @JSONField(ordinal=1) @Getter @Setter @Column(length=32,name="nickName",columnDefinition="varchar(32) comment '昵称'") private String nickName; //昵称 @JSONField(ordinal=2) @Getter @Setter @Column(length=32,name="wxId",columnDefinition="varchar(32) unique comment '微信号'") private String wxId; //微信ID @JSONField(ordinal=3) @Getter @Setter @OneToMany(mappedBy="webUser",cascade=CascadeType.ALL,fetch=FetchType.LAZY) private Set addresses; public WebUser(String username) { this.userName = username; } @Override public String toString() { return JSONObject.toJSONString(this,true); } }

@NoArgsConstructor  无参构造器

@AllArgsConstructor 全参构造器   不过只是本类的全部参数,如果需要使用父类参数,还需要自己写构造器。

@Entity(name="WEBUSER")  将被此注解标注的实体,映射到数据库,表名为name名。

@OneToMany(mappedBy="webUser",cascade=CascadeType.ALL,fetch=FetchType.LAZY)

    webuser是用户实体,一个用户对应多个地址,所以webuser是“一对多”中的“一”。在一的实体中,使用此注解标注。

mappedBy:标注该属性对应“多”的实体中的属性名。

cascade 表示级联操作。

fetch  加载方式,默认都是lazy加载。

重写toString方法,fastjson提供,将实体打印时,默认以json格式输出。  true的意思是标准json格式。

 

Address类: package com.wm.springboot.sc.entity; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.annotation.JSONField; import com.wm.springboot.base.BaseEntity; import lombok.Getter; import lombok.Setter; @Entity(name="ADDRESS") public class Address extends BaseEntity { @JSONField(ordinal=1) @Getter @Setter @Column(name="label",nullable=false,columnDefinition="varchar(16) comment '地址标签(家、公司)'") private String label; @JSONField(ordinal=2) @Getter @Setter @Column(name="country",nullable=false,columnDefinition="varchar(16) comment '国家'") private String country; @JSONField(ordinal=3) @Getter @Setter @Column(name="province",nullable=false,columnDefinition="varchar(32) comment '省份'") private String province; @JSONField(ordinal=4) @Getter @Setter @Column(name="city",nullable=false,columnDefinition="varchar(32) comment '城市'") private String city; @JSONField(ordinal=5) @Getter @Setter @Column(name="address",nullable=false,columnDefinition="varchar(255) comment '具体地址'") private String address; @JSONField(ordinal=6) @Getter @Setter @ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY,optional=true) @JoinColumn(name="webUser_id",nullable=true) private WebUser webUser; @Override public String toString() { return JSONObject.toJSONString(this,true); } }

@ManyToOne(cascade=CascadeType.ALL,fetch=FetchType.LAZY,optional=true)

address表为“一对多”中的多,所以使用@ManyToOne注解,并且配合@JoinColumn注解使用。

如果单独使用@ManyToOne,那么会生成一张中间表来维护两张表关系,如果不想使用中间表使用@JoinColumn来生成外键维护两张表关系。

name="webUser_id",表示生成的外键名称,并且字段类型以webUser表的主键为准。

 

多表联查的重点是:@ManyToOne@JoinColumn@OneToMany注解的使用。

==================================================

实体搞完了,下面搞一下repository层

比较简单了,普通的增删改JPA封装的很好了。这里重点说多条件查询及多表联查。先上代码。

WebUserRepository package com.wm.springboot.sc.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.wm.springboot.sc.entity.WebUser; @Repository public interface WebUserRepository extends JpaRepository{ public Page findAll(Specification specification,Pageable pageable); }

使用Specification来进行复杂条件查询,还可以使用Pageable进行分页查询。具体实现我们再service进行实现。

AddressRepository package com.wm.springboot.sc.repository; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.wm.springboot.sc.entity.Address; @Repository public interface AddressRepository extends JpaRepository { public Page findAll(Specification specification,Pageable pageable); }

 

=====================================

 

接下来是service层及repository的方法实现

面向接口变成我们先定义一下webuser的service

WebUserService

package com.wm.springboot.sc.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.wm.springboot.sc.entity.WebUser; public interface WebUserService { /** * 单表条件查询 */ public Page findAll(WebUser webUser,Pageable pageable); /** * 批量添加 * @param list * @return */ public WebUser save(WebUser webUser); /** * 单个删除 * @param user */ public void deleteOne(int id); /** * 单个修改 */ public WebUser update(WebUser webUser); /** * 根据ID查找 */ public WebUser findOne(int id); }

实现类WebUserServiceImpl

package com.wm.springboot.sc.service.impl; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.transaction.Transactional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.wm.springboot.sc.entity.WebUser; import com.wm.springboot.sc.repository.WebUserRepository; import com.wm.springboot.sc.service.WebUserService; @Service("WebUserServiceImpl") public class WebUserServiceImpl implements WebUserService { @Autowired private WebUserRepository webUserRepository; @Override public Page findAll(WebUser webUser, Pageable pageable) { Page page = webUserRepository .findAll((Root root, CriteriaQuery query, CriteriaBuilder cb) -> { List predicates = new ArrayList(); predicates.add(cb.like(root.get("userName").as(String.class), "%"+webUser.getUserName() + "%")); predicates.add(cb.like(root.get("email").as(String.class), "%"+webUser.getEmail() + "%")); predicates.add(cb.like(root.get("phoneNo").as(String.class), "%"+webUser.getPhoneNo() + "%")); predicates.add(cb.equal(root.get("isValid").as(String.class), webUser.getIsValid())); SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd"); try { if (null != webUser.getCreateTimeStart() && !"".equals(webUser.getCreateTimeStart())) predicates.add(cb.greaterThanOrEqualTo(root.get("createTime").as(Date.class), f.parse(webUser.getCreateTimeStart()))); if (null != webUser.getCreateTimeEnd() && !"".equals(webUser.getCreateTimeEnd())) predicates.add(cb.lessThan(root.get("createTime").as(Date.class), new Date(f.parse(webUser.getCreateTimeEnd()).getTime() + 24 * 3600 * 1000))); } catch (ParseException e) { e.printStackTrace(); } return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction(); }, pageable); return page; } @Override @Transactional public WebUser save(WebUser webUser) { return webUserRepository.save(webUser); } @Override public void deleteOne(int id) { webUserRepository.delete(id); } @Override public WebUser update(WebUser webUser) { return webUserRepository.save(webUser); } @Override public WebUser findOne(int id) { return webUserRepository.findOne(id); } }

重点说一下实现的findAll方法。因为使用jdk8,进入接口Specification,发现是函数式接口,直接使用lambda表达时进行书写。关于lambda表达式:

lambda概念及实际使用举例

简述一下这段逻辑,有错误请指正:

进入相应的方法可以看到:

root应该就是来获得字段的。

CriteriaBuilder 是用来拼装查询条件的。 如like  equal greaterThanOrEqualTo ......

将每一个Predicate添加到list,然后使用CriteriaQuery进行查询。

pageable,是用来分页查询的。

 

AddressService

package com.wm.springboot.sc.service; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.wm.springboot.sc.entity.Address; public interface AddressService { public Address save(Address address); public Page findAll(Pageable pageable,Address address); }  

实现类AddressServiceImpl

package com.wm.springboot.sc.service.impl; import java.util.ArrayList; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import com.wm.springboot.sc.entity.Address; import com.wm.springboot.sc.entity.WebUser; import com.wm.springboot.sc.repository.AddressRepository; import com.wm.springboot.sc.service.AddressService; @Service public class AddressServiceImpl implements AddressService { @Autowired private AddressRepository addressRepository; @Override public Address save(Address address) { return addressRepository.save(address); } @Override public Page findAll(Pageable pageable,Address address) { return addressRepository.findAll((Root root, CriteriaQuery query, CriteriaBuilder cb)->{ List predicates = new ArrayList(); if(null!=address.getId()&&!"".equals(address.getId())) predicates.add(cb.equal(root.get("id").as(Integer.class),address.getId())); if(null!=address.getWebUser()&&!"".equals(address.getWebUser())) predicates.add(cb.equal(root.get("webUser").get("id"),address.getWebUser().getId())); return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction(); },pageable); } } 重点:root.get("webUser").get("id")  通过获取address类中的属性webUser,得到一个webUser实体中的id,这个就是address中的外键。也是多表联查时的关键。 =======================================   controller层,访问控制器

 

先看一下webuserController

package com.wm.springboot.sc.controller; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.wm.springboot.modelUtils.Pages; import com.wm.springboot.modelUtils.PagesUtils; import com.wm.springboot.modelUtils.RespResult; import com.wm.springboot.modelUtils.RespResultEnum; import com.wm.springboot.modelUtils.RespResultUtil; import com.wm.springboot.sc.entity.WebUser; import com.wm.springboot.sc.service.WebUserService; /** * 网站用户控制器 * @author maybe */ @RequestMapping("/WebUser") @RestController public class WebUserController { @Autowired private WebUserService webUserService; /** * 分页查询所有用户(动态页数,每页大小,排序方式,排序字段) * 包括动态条件查询(用户名,email,电话,是否启用,创建时间) * 规则:无输入条件,默认查询全部。默认返回第一页 每页5条,默认asc排序,默认id排序。 */ @RequestMapping(value="/findAll.do",method={RequestMethod.POST,RequestMethod.GET}) public RespResult findAll(Pages pages,WebUser webUser){ return RespResultUtil.success(webUserService.findAll(webUser, PagesUtils.createPageRequest(pages))); } /** * 添加 */ @PostMapping(value="/save.do") public RespResult save(WebUser webUser){ webUser.setCreateTime(new Date()); webUser.setIsValid(1); webUser.setVersion(2); System.out.println(webUser.toString()); WebUser webUser2 = webUserService.save(webUser); if(webUser2!=null) return RespResultUtil.success(); else return RespResultUtil.error(RespResultEnum.ERROR); } /** * 单个删除 */ @RequestMapping(value="/deleteOne.do",method= {RequestMethod.POST,RequestMethod.GET}) public RespResult deleteOne(String id){ try { webUserService.deleteOne(Integer.parseInt(id)); } catch (Exception e) { return RespResultUtil.error(RespResultEnum.ERROR); } return RespResultUtil.success(); } /** * 修改 * @param webUser * @return */ @RequestMapping(value="/update.do",method= {RequestMethod.POST,RequestMethod.GET}) public RespResult update(WebUser webUser){ webUser.setVersion(webUserService.findOne(webUser.getId()).getVersion()); System.out.println(webUser.toString()); WebUser user = webUserService.update(webUser); if(user!=null) return RespResultUtil.success(); else return RespResultUtil.error(RespResultEnum.ERROR); } }

RespResult、RespResultUtil为我自己封装的返回实体类。具体可参考:

哦,我还没来得及写。。。有空补上。

在说下Pages、PagesUtils类,这个也是我自己封装的分页相关的类:

pages类:

package com.wm.springboot.modelUtils; import com.alibaba.fastjson.JSON; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @AllArgsConstructor @NoArgsConstructor public class Pages { @Getter @Setter private int page;//第几页 @Getter @Setter private int size;//每页显示几条内容 @Getter @Setter private String sortColumn; //排序字段 @Getter @Setter private String direction; //排序方式 @Override public String toString() { return JSON.toJSONString(this, true); } }

PagesUtils类

 

package com.wm.springboot.modelUtils; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.util.StringUtils; public class PagesUtils { //分页大小 private final static Integer SIZE = 5; //默认页数 0开头 private final static Integer PAGE = 0; //默认排序字段 private final static String ID = "id"; public static Pageable createPageRequest(Pages pages) { return new PageRequest(pages.getPage()


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有