相信大家都用过jpa,jpa继承CrudRepository和PagingAndSortingRepository接口之后,在简单的单表查询中,不管是使用自带的findAll(),saveAll等方法还是利用jpa的方法命名规范进行jpql查询,jpa使用起来快的一撇。然而在进行复杂查询时,需要继承JpaSpecificationExecutor接口 利用Specification 进行复杂查询,还需要定义好表之间的映射关系。今天就介绍下jpa的多表查询与条件构造。
pom
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
配置
spring:
jpa:
show-sql: true
hibernate:
# 自动创建、更新、验证数据库表结构
ddl-auto: update
这里有3个表,用户(JpaUser),角色(JpaRole),权限(JpaPermission)。
一个用户有一个角色,1个角色可以分配给多个用户,用户对角色就是多对一。假设一个角色绑定1个权限。角色对权限就是一对一。
JpaUser
@Data
@Entity
@Table(name = "jpa_user")
@org.hibernate.annotations.Table(appliesTo = "jpa_user", comment = "用户表")
public class JpaUser {
@Id
@GeneratedValue
@ApiParam("主键ID")
@Column(name = "id", length = 20)
private Long id;
@ApiParam("用户名")
@Column(name = "user_name", columnDefinition = "VARCHAR(255) NOT NULL COMMENT '用户名'")
private String userName;
@ApiParam("昵称")
@Column(name = "nick_name", columnDefinition = "VARCHAR(255) NOT NULL COMMENT '昵称'")
private String nickName;
@ApiParam("岗位")
@Column(name = "position")
private String position;
@ApiParam("年龄")
@Column(name = "age")
private Integer age;
@Column(name = "longitude", precision = 10, scale = 7)
private BigDecimal longitude;
@Column(name = "latitude", precision = 10, scale = 7)
private BigDecimal latitude;
/**
* @ManyToOne 用户:角色 多个用户对应一个角色,当我们创建表结构时,应在多的一方去维护表关系,也就是说,应将@ManyToOne注解加在用户表中,并且设置为懒加载。
* @JsonBackReference 生成json时该属性排除
*/
@ManyToOne
@JoinColumn(name = "role_id")
private JpaRole role;
}
JpaRole
@Data
@Entity
@Table(name = "jpa_role")
@org.hibernate.annotations.Table(appliesTo = "jpa_role", comment = "角色表")
public class JpaRole {
@Id
@GeneratedValue
@ApiParam("主键ID")
@Column(name = "id")
private Long id;
@ApiParam("角色名")
@Column(name = "role_name", columnDefinition = "VARCHAR(64) NOT NULL COMMENT '角色名'")
private String roleName;
@JsonBackReference
@OneToMany(targetEntity = JpaUser.class, mappedBy = "role", cascade = {
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, fetch = FetchType.LAZY)
private List<JpaUser> jpaUsers;
/**
* 一对一的关系 在其中一个表维护就好了
*/
@OneToOne(cascade = {
CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
@JoinColumn(name = "permission_id")
private JpaPermission permission;
}
JpaPermission
@Data
@Entity
@Table(name = "jpa_permission")
@org.hibernate.annotations.Table(appliesTo = "jpa_permission", comment = "权限表")
public class JpaPermission {
@Id
@GeneratedValue
@ApiParam("主键ID")
@Column(name = "id", length = 20)
protected Long id;
@ApiParam("权限名称")
@Column(name = "permission_name", columnDefinition = "VARCHAR(64) NOT NULL COMMENT '权限名称'")
private String permissionName;
}
定义一个条件查询动态构造器。
JpaUserSpecification
public class JpaUserSpecification implements Specification<JpaUser> {
private JpaUserQueryVo param;
public JpaUserSpecification(JpaUserQueryVo jpaUserQueryVo) {
this.param = jpaUserQueryVo;
}
@Override
public Specification<JpaUser> and(Specification<JpaUser> other) {
return null;
}
@Override
public Specification<JpaUser> or(Specification<JpaUser> other) {
return null;
}
@Override
public Predicate toPredicate(Root<JpaUser> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<Predicate>();
/**
* 2张表
*/
if (StringUtils.isNotEmpty(param.getRoleName())) {
Join<JpaRole, JpaUser> join = root.join("role", JoinType.LEFT);
predicates.add(criteriaBuilder.equal(join.get("roleName"), param.getRoleName()));
}
/**
* 跨表查询,3个表
* 需要通过权限名称查出对应的用户,JpaUser.role.permission = param.getPermissionName()
* jointype.LEFT主要是说"role"这个属性是在哪个表中,这里是在JpaUser中。
* 此处debug就可以发现,执行第一行之后,join中的Model就变成了JpaRole。(图贴在后面)
* 此处是要通过权限名称查出对应的用户。所以先root.join("role", JoinType.LEFT)拿到JpaRole,然后再join.get("permission")拿到JpaPermission ,然后再匹配它的属性permissionName
* 这里就是get出相应的属性,一直到你得到想要的属性为止。
*/
if (StringUtils.isNotEmpty(param.getPermissionName())) {
Join<JpaPermission, JpaUser> join = root.join("role", JoinType.LEFT);
predicates.add(criteriaBuilder.equal(join.get("permission").get("permissionName"), param.getPermissionName()));
}
/**
* in查询
*/
if (StringUtils.isNotEmpty(param.getIds())) {
CriteriaBuilder.In in = criteriaBuilder.in(root.get("id").as(Long.class));
List<String> ids = Arrays.asList(param.getIds().split(","));
ids.forEach(s -> in.value(Long.parseLong(s)));
predicates.add(criteriaBuilder.and(in));
}
/**
* like查询
*/
if (StringUtils.isNotEmpty(param.getUserName())) {
predicates.add(criteriaBuilder.like(root.get("userName"), "%" + param.getUserName() + "%"));
}
/**
* 全等 = 查询
*/
if (StringUtils.isNotEmpty(param.getNickName())) {
predicates.add(criteriaBuilder.equal(root.get("nickName"), param.getNickName()));
}
/**
* 大于小于等等
*/
if (param.getAge() != null) {
predicates.add(criteriaBuilder.le(root.get("age"), param.getAge()));
predicates.add(criteriaBuilder.gt(root.get("age"), param.getAge() - 10));
}
return predicates.isEmpty() ? criteriaBuilder.conjunction() : criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
JpaUserQueryVo 是接收各种查询参数的Entity。此处就不贴它的代码了。
此处就省略controller层代码,只看service层代码。
@Override
public Page<JpaUser> tables(JpaUserQueryVo userQueryVo) {
PageRequest pageRequest = PageRequest.of(0, 10);
Specification<JpaUser> specification = new JpaUserSpecification(userQueryVo);
return jpaJpaUserRepository.findAll(specification, pageRequest);
}
就这样就ok了。JpaUserSpecification会根据JpaUserQueryVo 里的参数自动构建查询。
来看一下效果。