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中用了这个被修改的字段。

官方地址

官网

https://querydsl.com/

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 的支持

最后,非常欢迎大家指出代码中的问题,提出相关建议。


首发:https://springboot.io/t/topic/4424

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容