SpringJpa 与 QueryDsl的使用

最近看了下jpa与querydsl的用法,使用起来狠舒服,几乎不用写sql。

起因

  • 过去一直都使用mybatis,想要尝试一下新花样
  • 都是spring全家桶的东西,据说很爽

Start

1.Jpa

  1. 添加jpa的依赖

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.dby.sail'
version = '0.0.1'
sourceCompatibility = '1.8'

repositories {
mavenCentral()
}

dependencies {
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'mysql:mysql-connector-java:5.1.47'
compile 'com.google.code.gson:gson:2.8.5'
compile 'org.projectlombok:lombok:1.16.20'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
  1. 添加实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.peihuan.demo;

import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;

@Data
@Entity
@Table(name = "course")
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
private long length;
private long teacherId;
}

  1. 添加Repository
1
2
3
public interface CourseRepository extends JpaRepository<Course, Long>{
Course findByTitleAndLength(String title,long lenth);
}

我们的接口继承JpaRepository,泛型中,第一个是我们的实体类,第二个是主键类型。
同时,方法名必须满足一些规则,下图不完整,详情查看文档 spring-data-jpa

jpa中也提供了分页的操作,

1
2
3
4
5
public interface CourseRepository extends JpaRepository<Course, Long> {
Course findByTitleAndLength(String title,Long lenth);
// 构建Pageable参数传入, 用Page进行接收
Page<Course> findAllByTeacherId(Long teacherId,Pageable pageable);
}

到这里,jap就算结束了,在我们的service中注入CourseRepository就可以随意的调用增删改查了。

2.QueryDsl

jpa看起来很好用,为什么还需要QueryDsl呢?
因为jpa对以下场景不是特别友好:我想要查询Course,可以按title、length、teacherId查询。那么很容易写出这个接口 :

1
Page<Course> findByTitleAndLengthAndTeacherId(String title,Long lenth,Long teacherId,Pageable pageable);

但实际上这三个参数,可能都会有,也可能都没有,也就是传null值。但如果你真的传了null值,那么会报错,因为jpa不允许空值。

那么该肿么办呢?一种情况是把所有情况的repository接口都写出来,一共 2 * 2 * 2 种情况,在service根据参数情况,调用相应的repository接口。但如果你有五六种查询条件,很明显是不可取的了……

QueryDsl久可以解决这种情况,并可以和jpa结合使用

  1. 首先修改我们的依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
plugins {
id 'org.springframework.boot' version '2.1.4.RELEASE'
id 'java'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.9"
}

apply plugin: 'io.spring.dependency-management'

group = 'com.dby.sail'
version = '0.0.1'
sourceCompatibility = '1.8'


repositories {
mavenCentral()
}

/***start***/

def generatedSourcesDir = file("build/generated-sources/java")

sourceSets {
main {
java {
srcDir "src/main/java"
srcDir generatedSourcesDir
}
}
}

querydsl {
querydslDefault = true
jpa = true
querydslSourcesDir = generatedSourcesDir
}
/***end***/

dependencies {
compile 'org.springframework.boot:spring-boot-starter-data-jpa'
compile 'org.springframework.boot:spring-boot-starter-web'
compile 'mysql:mysql-connector-java:5.1.47'
compile 'com.google.code.gson:gson:2.8.5'
compile("org.projectlombok:lombok:1.16.20")
compile("com.querydsl:querydsl-core:4.2.1")
compile("com.querydsl:querydsl-jpa:4.2.1")
testCompile 'org.springframework.boot:spring-boot-starter-test'
}

之后会发现多了个querydsl,不用管它,直接双击运行compileJava,会自动生成一些Q开头的文件,并不需要关心其内容。

之后尝试改写Repository

1
2
3
4
5
6
7
8
public interface CourseRepository extends JpaRepository<Course,Long>, QuerydslPredicateExecutor<Channel>{
default Page<Course> findByTitleAndLength(String title,Long length,Pageable pageable) {
QCourse qCourse = QCourse.ourse;
BooleanExpression expression = qCourse.title.like("%" + title + "%")
.and(qCourse.length.eq(length));
return findAll(expression, pageable);
}
}

测试后发现,这样也是错误的,参数也不能有空,那还有什么意义呢?但我们可以自己拼接BooleanExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface CourseRepository extends JpaRepository<Course,Long>, QuerydslPredicateExecutor<Channel>{
default Page<Course> findByTitleAndLength(String title,Long length,Pageable pageable) {
QCourse qCourse = QCourse.course;
BooleanBuilder booleanBuilder = new BooleanBuilder();
if (StringUtils.hasText(title)) {
booleanBuilder.and(qCourse.title.like("%" + title + "%"));
}
if (length != null) {
booleanBuilder.and(qCourse.length.eq(length););
}
return findAll(booleanBuilder, pageable);
}
}

这样就很美丽了,已经达到了我们的目标。

注意一个坑

由于高版本中的gradle使用的都是implementation,如果使用了它,那么会出现包无法找到的情况,所以必须使用compile和testCompile。