# jpa-query **Repository Path**: baima2020/jpa-query ## Basic Information - **Project Name**: jpa-query - **Description**: 基于Spring Data JPA复杂查询编写的工具,可大幅简化复杂查询的代码编写,适用条件为后台处理各类综合条件查询,比如根据姓名/手机号/身份证等查询以及多表关联查询. - **Primary Language**: Java - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-12-01 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JPA-Query #### 介绍 基于Spring Data JPA复杂查询编写的查询工具,可大幅简化复杂查询的代码编写,比如根据姓名/手机号/身份证等进行组合查询,该工具自动判断参数查询是否为空,如果为空则不拼装查询条件。 #### 软件架构 软件架构说明 #### 依赖说明 1. lombok >= 1.18.10 # 一个通过注解自动生成字段的get、set方法。 2. mysql >= 5.1.46 # MySql驱动 3. p6spy >= 3.0.0 # 输出JPA最终执行的原生SQL语句 #### JPA使用注意 1. 建议禁用强制外键关联 强制外键关联虽然有很多优点,比如保证数据完整性,但是缺点也非常明显,当我们改动表,或者删除数据的时候,就会出现很多的问题,因此可以选择禁用掉外键生成,具体为: ``` /** * 禁止创建外键. * * @author xianbin.su * @Date 2019/10/25 */ public class MySQLDialectWithoutFK extends MySQL5Dialect { @Override public String getTableTypeString() { return " ENGINE=InnoDB DEFAULT CHARSET=utf8"; } @Override public String getAddForeignKeyConstraintString( String constraintName, String[] foreignKey, String referencedTable, String[] primaryKey, boolean referencesPrimaryKey) { return " alter ".concat(foreignKey[0]).concat(" set default NULL "); } } ``` 然后在application.yml文件中配置com.suxianbin.jpa.mysql.MySQLDialectWithoutFK ``` ### spring配置 spring: ## 数据库配置 datasource: #type: com.alibaba.druid.pool.DruidDataSource #driver-class-name: com.mysql.jdbc.Driver driver-class-name: com.p6spy.engine.spy.P6SpyDriver url: jdbc:p6spy:mysql://127.0.0.1:3306/jpaquery?useSSL=false&characterEncoding=utf-8 #url: jdbc:mysql://127.0.0.1:3306/jpaquery?useSSL=false&characterEncoding=utf-8 username: root password: 123456 ## jpa配置 jpa: show-sql: false hibernate: ddl-auto: create properties: hibernate.dialect: com.suxianbin.jpa.mysql.MySQLDialectWithoutFK hibernate.format_sql: false open-in-view: true ``` 2. 配置p6spy 这个工具方便我们跟踪JPA执行的SQL,建议开发阶段可以引入,具体使用方法可参考网上教程。 #### 使用说明 ``` package com.suxianbin.jpa.domain; import com.suxianbin.jpa.enums.StatusEnum; import com.suxianbin.jpa.utils.StatusUtil; import lombok.Data; import lombok.experimental.FieldNameConstants; import net.minidev.json.annotate.JsonIgnore; import org.hibernate.annotations.Where; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import org.springframework.format.annotation.DateTimeFormat; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 学生基础信息 * * @author xianbin.su * @date 2019/06/12 */ @Data @FieldNameConstants @Entity @Table(name = "edu_student_base_info") @EntityListeners(AuditingEntityListener.class) @Where(clause = StatusUtil.notDelete) public class StudentBaseInfo implements Serializable { private static final long serialVersionUID = 1L; // 主键ID @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 姓名 private String studentName; // 昵称 private String nickName; // 性别 private String gender; // 生日 @DateTimeFormat(pattern = "yyyy-MM-dd") @Temporal(TemporalType.DATE) private Date birthday; // 年龄 private Integer age; // 联系人姓名 private String contactName; // 联系人称呼 private Byte contactTitle; // 手机号 private String phone; // 身份证号 private String idNumber; // 家庭住址 private String homeAddress; @OneToMany(mappedBy = "studentBaseInfo") @JsonIgnore private List students = new ArrayList<>(); // 创建时间 @CreatedDate private Date createDate; // 更新时间 @LastModifiedDate private Date updateDate; // 数据状态 private Byte status = StatusEnum.OK.getCode(); } ``` ``` package com.suxianbin.jpa.domain; import com.suxianbin.jpa.enums.StatusEnum; import com.suxianbin.jpa.utils.StatusUtil; import lombok.Data; import lombok.experimental.FieldNameConstants; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; import org.hibernate.annotations.Where; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; import javax.persistence.*; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * 学生扩展信息 * * @author xianbin.su * @date 2019/06/12 */ @Data @FieldNameConstants @Entity @Table(name = "edu_student") @EntityListeners(AuditingEntityListener.class) @Where(clause = StatusUtil.notDelete) public class Student implements Serializable { private static final long serialVersionUID = 1L; // 主键ID @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 所属学生 @NotFound(action = NotFoundAction.IGNORE) @ManyToOne(fetch = FetchType.LAZY) private StudentBaseInfo studentBaseInfo; // 所属班级数量.该字段不需要持久化存储. @Transient private Integer classAmount = 0; // 所属班级 @NotFound(action = NotFoundAction.IGNORE) @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable(name = "edu_student_class_info", joinColumns = @JoinColumn(name = "edu_student_id"), inverseJoinColumns = @JoinColumn(name = "edu_class_info_id")) private List classInfos = new ArrayList<>(); // 创建时间 @CreatedDate private Date createDate; // 更新时间 @LastModifiedDate private Date updateDate; // 数据状态 private Byte status = StatusEnum.OK.getCode(); } ``` 定义Repository,需要JpaSpecificationExecutor支持。 ``` public interface StudentBaseInfoRepository extends BaseRepository, JpaSpecificationExecutor { } ``` ``` public interface StudentRepository extends BaseRepository, JpaSpecificationExecutor { } ``` 复杂的综合查询示例。查询条件将自动进行字段是否为空判断,如果为空null,则不作为查询条件。 ``` /** * 获取列表数据 * * @param student 查询实例 * @param page 分页信息 * @return 返回数据 */ @Override public Page getPageList(Student student, PageRequest page) { // 使用Specification复杂查询 return studentRepository.findAll(new Specification() { private static final long serialVersionUID = 1L; @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return getPredicates(root, query, cb, student); } }, page); } /** * 获取列表数据 * * @param student 查询实例 * @return 返回数据 */ @Override public List getList(Student student) { // 使用Specification复杂查询 return studentRepository.findAll(new Specification() { private static final long serialVersionUID = 1L; @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) { return getPredicates(root, query, cb, student); } }); } /** * 获取综合查询条件。 * * @param root * @param query * @param cb * @param student * @return */ private Predicate getPredicates(Root root, CriteriaQuery query, CriteriaBuilder cb, Student student) { List preList = new ArrayList<>(); QueryExpression expression = new QueryExpression(root, cb); // 匹配多个字段值是否相等 String[] fields = new String[]{Student.Fields.id, Student.Fields.status}; preList.addAll(expression.equal(fields, student)); // 关联表查询 String[] equalJoinFields = new String[]{Student.Fields.studentBaseInfo}; fields = new String[]{StudentBaseInfo.Fields.id, StudentBaseInfo.Fields.status}; preList.addAll(expression.joinEqual(equalJoinFields, fields, student)); // 关联表进行模糊查询 String[] joinLikeFields = new String[]{Student.Fields.studentBaseInfo}; String[] likeFields = new String[]{StudentBaseInfo.Fields.studentName, StudentBaseInfo.Fields.phone}; preList.addAll(expression.joinLike(joinLikeFields, likeFields, student)); // 组合成查询条件 Predicate[] pres = new Predicate[preList.size()]; return query.where(preList.toArray(pres)).getRestriction(); } ``` #### 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request #### 码云特技 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md 2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目 4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目 5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)