SpringBoot整合Spring-Data-Jpa + QueryDsl以及使用案例
这些年我接触/学习过得ORM框架或库也有一箩筐了。
- dbutils
- mybatis
- sql2o
- beetlsql
- hibernate
- cayenne
- spring-data-jpa
- querydsl
我觉得springboot应用中最得心应手的利器,还是 spring-data-jpa + queryds。但是它好像在国内不怎么流行,看国内的开源项目,工作遇到的项目基本都是mybatis/mybatis-plus。写不完的xml和mapper,用不完的代码生成。
这种单表CRUD的ORM框架,不能灵活的JOIN,投影查询。新增一个JOIN表,就要新写一个mapper方法和xml,新增一个查询列,也要新写一个mapper方法和xml(当然,我看到很多人很多人永远都是SELECT *
干到底,一个 findOne 方法,哪里都可以用)。还要配置各种结果集映射。实在是太累了。
前阵子看到JEECMS
居然用的就是QueryDsl,我就想着写一个教程。也不能算是教程,只能算是一堆案例,QueyDsl的各种使用案例。如果你对QueryDsl一无所知,也可以直接看看。它并不难,你只要会写SQL语句,那就会用了90%
QueyDsl
快速的解释一下这玩意儿咋用,QueryDsl需要配置JPA使用,它根据你定义的JPA Entity
实体类,逆向的生成查询类。通过操作查询类完成SQL的操作。
逆向生成的过程,完全自动,只需要配置好maven插件,定义好实体类就行。
怎么去操作这些查询类?你会写SQL就会操作。
它能完成项目中的大部分SQL查询,太复杂了也没辙。但是可以用spring-data-jpa的原生查询。
快速看一眼
实体类 User 定义
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.With;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@With
@Entity
@Table(name = "user", uniqueConstraints = {
@UniqueConstraint(columnNames = "name", name = "name")
}, indexes = {
@Index(columnList = "department_id", name = "department_id")
})
@org.hibernate.annotations.Table(appliesTo = "user", comment = "用户")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1691873956126863400L;
@Id
@Column(columnDefinition = "INT UNSIGNED COMMENT 'ID'")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(columnDefinition = "VARCHAR(50) COMMENT '名字'", nullable = false)
private String name;
@Column(columnDefinition = "VARCHAR(10) COMMENT '性别'", nullable = false)
@Enumerated(EnumType.STRING)
private Gender gender;
@Column(columnDefinition = "DECIMAL(10,2)COMMENT '账户余额'")
private BigDecimal balance;
@Column(name = "department_id", columnDefinition = "INT UNSIGNED COMMENT '部门ID'", nullable = false)
private Integer departmentId;
@Column(columnDefinition = "TINYINT UNSIGNED COMMENT '是否启用。0:禁用,1:启用'", nullable = false)
private Boolean enabled;
@Column(columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'", nullable = false)
private LocalDateTime createAt;
@Column(columnDefinition = "TIMESTAMP DEFAULT NULL COMMENT '修改时间'")
private LocalDateTime updateAt;
public static enum Gender {
MALE, // 男
FEMALE // 女
}
}
QueryDsl自动生成的查询类
import static com.querydsl.core.types.PathMetadataFactory.*;
import com.querydsl.core.types.dsl.*;
import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;
/**
* QUser is a Querydsl query type for User
*/
@Generated("com.querydsl.codegen.DefaultEntitySerializer")
public class QUser extends EntityPathBase<User> {
private static final long serialVersionUID = 373632107L;
public static final QUser user = new QUser("user");
public final NumberPath<java.math.BigDecimal> balance = createNumber("balance", java.math.BigDecimal.class);
public final DateTimePath<java.time.LocalDateTime> createAt = createDateTime("createAt", java.time.LocalDateTime.class);
public final NumberPath<Integer> departmentId = createNumber("departmentId", Integer.class);
public final BooleanPath enabled = createBoolean("enabled");
public final EnumPath<User.Gender> gender = createEnum("gender", User.Gender.class);
public final NumberPath<Integer> id = createNumber("id", Integer.class);
public final StringPath name = createString("name");
public final DateTimePath<java.time.LocalDateTime> updateAt = createDateTime("updateAt", java.time.LocalDateTime.class);
public QUser(String variable) {
super(User.class, forVariable(variable));
}
public QUser(Path<? extends User> path) {
super(path.getType(), path.getMetadata());
}
public QUser(PathMetadata metadata) {
super(User.class, metadata);
}
}
查询Demo
JPAQueryFactory query = new JPAQueryFactory(this.entityManager);
QUser qUser = QUser.user; // 生成的查询对象,可以理解为数据表
User user = query.select(qUser).from(qUser).where(qUser.id.eq(1)).fetchOne(); // 查询唯一记录,如果结果不止一个则异常
有感觉了没?用Java代码的方式写SQL。用代码的方式进行JOIN检索,投影查询,结果集封装。实在是太灵活。通过合理的抽象设计,直接在Controller就能把SQL执行了。
由于是根据实体类生成的查询对象,那么在修改了实体类的字段名称后,查询对象会重新生成。在代码中涉及到修改/删除字段的相关操作都会在编译时异常。而不是xml一样,得去挨个找,甚至没法找。不知道哪些人在哪些xml中用了这个被修改的字段。
官方地址
官网
Github
https://github.com/querydsl/querydsl
QueryDsl Example
案例有10来个,不方便在一篇帖子里面展开,所以新建了一个工程在Github。这个工程整合了spring-data-jpa 和 querydsl以及一些常用的案例。
https://github.com/KevinBlandy/springboot-querydsl-example
软件版本
- SpringBoot 2.6.1
- Java 17
- MYSQL 8.x
需要手动创建数据库(看yaml配置),系统启动会后自动创建数据表(包括索引)。
Example代码
都在 src/main/resources
目录下,可以每一个都执行一下看看,希望你会喜欢这玩意儿。
DataInit
初始化演示数据(最先执行)Example1
单表的查询/编辑/删除Example2
join查询Example3
分页/排序Example4
条件列子查询/查询列子查询/exists子查询/count子查询Example5
聚合查询Example6
条件分组Example7
加锁Exapmle8
结果集封装Exapmle9
结果列的一些操作。case/转换/null判断...Exapmle10
spring-data-jpa 的支持
最后,非常欢迎大家指出代码中的问题,提出相关建议。