在SpringDataJPA中使用Querydsl(kotlin版)

发布时间 2023-04-03 23:25:37作者: loveletters

前言

我们在做日常开发中经常会进行数据库的操作,ORM框架可以帮助我们更便捷的进行数据的操作。SpringDataJPA就是我们经常用到的ORM框架,我们只需要定义一些实体类以及实现一些接口,它便为我们生成了一些丰富的SQL操作功能。但是如果涉及到多表动态查询, JPA 的功能就显得有些捉襟见肘了,虽然我们可以使用注解 @Query ,在这个注解中写 SQL ,但是这样我们又需要创建Model对象以及Repository接口。好在Spring可以方便的集成QueryDSL,让我们方便的实现这些功能。

QueryDSL仅仅是一个通用的查询框架,专注于通过Java API构建类型安全的SQL查询。
QueryDSL可以通过一组通用的查询API为用户构建出适合不同类型ORM框架或者是SQL的查询语句,也就是说QueryDSL是基于各种ORM框架以及SQL之上的一个通用的查询框架。
借助QueryDSL可以在任何支持的ORM框架或者SQL平台上以一种通用的API方式来构建查询。目前QueryDSL支持的平台包括JPA,JDO,SQL,Mongodb 等等。

环境准备

我们采用spirngboot作为基础框架,开发语言选择kotlin,数据库选择postgre,构建工具选择maven,引入web、jpa、QueryDSL、openapi等相关依赖。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bliietsdoux</groupId>
    <artifactId>jpa_learn_maven</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpa_learn_maven</name>
    <description>jpa_learn_maven</description>
    <properties>
        <java.version>17</java.version>
        <kotlin.version>1.7.10</kotlin.version>
    </properties>
    <dependencies>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-reflect</artifactId>
        </dependency>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib-jdk8</artifactId>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>5.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>5.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>javax.annotation</groupId>
                        <artifactId>javax.annotation-api</artifactId>
                        <version>1.3.2</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <configuration>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <!--这个spring和jpa的插件必不可少,尤其是使用data class作entity的时候,不然首先是需要你在程序入口启动类上添加open属性,而且data class编译不会产生默认无参构造函数,即使加了no-args依赖也没用 -->
                    <compilerPlugins>
                        <plugin>spring</plugin>
                        <plugin>jpa</plugin>
                    </compilerPlugins>
                </configuration>
                <executions>
                    <execution>
                        <id>compile</id>
                        <!-- 这个phase必不可少,这里的compile阶段是处理源码,-->
                        <phase>process-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>kapt</id>
                        <!-- 这个phase必不可少,这里的阶段是产生Q类,-->
                        <phase>generate-sources</phase>
                        <goals>
                            <goal>kapt</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <sourceDir>src/main/kotlin</sourceDir>
                            </sourceDirs>
                            <annotationProcessorPaths>
                                <annotationProcessorPath>
                                    <groupId>com.querydsl</groupId>
                                    <artifactId>querydsl-apt</artifactId>
                                    <version>${querydsl.version}</version>
                                    <!--这个jpa也比不可少 -->
                                    <classifier>jpa</classifier>
                                </annotationProcessorPath>
                            </annotationProcessorPaths>
                        </configuration>
                    </execution>
                </executions>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>

        </plugins>
    </build>

</project>


接下来我们做一些环境配置

做好数据配置、jpa配置


spring:
  mvc:
    pathmatch:
      matching-strategy: ant_path_matcher
  datasource:
    password: 123456
    url: jdbc:postgresql://localhost:5432/daily
    username: postgres
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.PostgreSQLDialect
        format_sql: true
logging:
  level:
    org.hibernate.type.descriptor.sql.BasicBinder: trace




项目开发

项目整体结构如下

openapi的配置bean

我们先要做一下openapi的配置,创建一个config类

Entity

我们再创建一个实体类 User

Repository

因为我们是需要用到Dsl进行查询所以需要我们的Repository接口实现QuerydslPredicateExecutor接口

Service

这里注入EntityManager对象是因为我们后面要通过JPAQueryFactory直接进行查询,而不通过Repository的接口

Controller

我们这里先定义个UserController

QueryDSL对象

我们想要在代码中使用到QueryDSL对象,首先需要先编译下对象。它跟lombok一样会在编译期给我们生成一些对象,所以我们想要用到这些对象及方法需要先编译下项目。
它会在target目录下为我们生成这么一个类

通过Repository对象查询

我们先构建这么一个场景,前端传过来条件查询,然后我们通过Usr对象接受查询条件,对于非空字段进行条件筛选,其他的username跟nickname为模糊匹配,sex为精准匹配。

我们首先需要通过生成的QUser类来创建一个对象。然后根据查询条件构建查询表达式,查询的是所有字段。params.isNotNull.or(params.isNull) 相当于我们平时查询经常用到的 where 1=1,然后再根据具体的需求构建表达式。最后调用repository.findAll()方法查询结果

再简单的在controller中开一个接口来测试一下

我们先看一下表中数据情况:

打开openapi然后调用接口:

可以看到我们已经成功根据我们的需求查询到了对应的结果

然后我们在后台看一下打印的sql语句,因为我们这里只有sex这个参数有值,所以为我们生成的sql也只有这个字段的筛选条件。

通过JPAQueryFactory查询

我们可以Repository的查询接口在其中传入查询条件这样的方式来查询,但是这种方式查询的是所有字段并且表也是Repository所对应的表,如果我们想更自由的查询可以通过JPAQueryFactory来进行。
我们首先构建一个JPAQueryFactory对象跟QUser对象,这样我们就可以指定查询的字段跟表进行查询。但是他查询的结果是一个Tuple对象我们要进行map一下变成我们需要的User对象

简单的创建接口测试一下

可以看到只返回了部分字段的结果

再查看一下后台的sql语句,确实只用到了部分的字段进行查询

后记

其实相较于JPA来说我更喜欢MyBatisPlus,究其原因是因为它既可以像JPA那样为我们提供了开箱即用的简单查询方法,又可以方便我们自己写SQL。但是SpringData为我们抽象了一层统一的api,不仅是对于关系型数据库mysql、postgre等,还有非关系型数据库如redis、mogodb、elasticsearch等。都提供了很好的支持,所有具体用什么看具体的项目。

最后吐槽一下:对于kotlin来说JPA查询时候返回的Optional太鸡肋了,kotlin可以用?来直接判空,所以我们可为Repository接口添加扩展方法

fun<T,ID : Any> CrudRepository<T, ID>.findUserById(id:ID):T? = findById(id).orElse(null)

这样我们在调用的时候就舒服多了