QueryDSL与SpringDataJPA实现单表普通条件查询

恒宇少年2018-11-28 14:32:14

在企业开发中ORM框架有很多种如:Hibernate,Mybatis,JdbcTemplate等。每一种框架的设计理念是不一样的,Hibernate跟我们本章讲解的SpringDataJPA是一致的框架都是全自动理念作为设计核心,让用户更少的去写SQL语句通过简单的配置就可以实现各种查询。而Mybatis框架则是半自动理念作为设计核心,SQL让用户自己定义实现了更好的灵活性。

本章目标

本章我们目标实现QueryDSL通用查询语言整合SpringDataJPA完成单表的查询多样化。

构建项目

下面我们先来创建一个SpringBoot项目,具体如何使用Maven整合QueryDSL请访问QueryDSL学习目录第一章,创建项目时的依赖也与第一章一致,pom.xml配置文件如下代码块所示:

  1. <?xml version="1.0" encoding="UTF-8"?>

  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  3.    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  4.    <modelVersion>4.0.0</modelVersion>

  5.    <groupId>com.yuqiyu.querydsl.sample</groupId>

  6.    <artifactId>chapter2</artifactId>

  7.    <version>0.0.1-SNAPSHOT</version>

  8.    <packaging>war</packaging>

  9.    <name>chapter2</name>

  10.    <description>Demo project for Spring Boot</description>

  11.    <parent>

  12.        <groupId>org.springframework.boot</groupId>

  13.        <artifactId>spring-boot-starter-parent</artifactId>

  14.        <version>1.5.4.RELEASE</version>

  15.        <relativePath/> <!-- lookup parent from repository -->

  16.    </parent>

  17.    <properties>

  18.        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

  19.        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

  20.        <java.version>1.8</java.version>

  21.    </properties>

  22.    <dependencies>

  23.        <dependency>

  24.            <groupId>org.springframework.boot</groupId>

  25.            <artifactId>spring-boot-starter-data-jpa</artifactId>

  26.        </dependency>

  27.        <dependency>

  28.            <groupId>org.springframework.boot</groupId>

  29.            <artifactId>spring-boot-starter-web</artifactId>

  30.        </dependency>

  31.        <dependency>

  32.            <groupId>mysql</groupId>

  33.            <artifactId>mysql-connector-java</artifactId>

  34.            <scope>runtime</scope>

  35.        </dependency>

  36.        <!--阿里巴巴数据库连接池,专为监控而生 -->

  37.        <dependency>

  38.            <groupId>com.alibaba</groupId>

  39.            <artifactId>druid</artifactId>

  40.            <version>1.0.26</version>

  41.        </dependency>

  42.        <!-- 阿里巴巴fastjson,解析json视图 -->

  43.        <dependency>

  44.            <groupId>com.alibaba</groupId>

  45.            <artifactId>fastjson</artifactId>

  46.            <version>1.2.15</version>

  47.        </dependency>

  48.        <dependency>

  49.            <groupId>org.springframework.boot</groupId>

  50.            <artifactId>spring-boot-starter-tomcat</artifactId>

  51.            <!--<scope>provided</scope>-->

  52.        </dependency>

  53.        <dependency>

  54.            <groupId>org.springframework.boot</groupId>

  55.            <artifactId>spring-boot-starter-test</artifactId>

  56.            <scope>test</scope>

  57.        </dependency>

  58.        <!--queryDSL-->

  59.        <dependency>

  60.            <groupId>com.querydsl</groupId>

  61.            <artifactId>querydsl-jpa</artifactId>

  62.            <version>${querydsl.version}</version>

  63.        </dependency>

  64.        <dependency>

  65.            <groupId>com.querydsl</groupId>

  66.            <artifactId>querydsl-apt</artifactId>

  67.            <version>${querydsl.version}</version>

  68.            <scope>provided</scope>

  69.        </dependency>

  70.        <dependency>

  71.            <groupId>org.projectlombok</groupId>

  72.            <artifactId>lombok</artifactId>

  73.            <version>1.16.16</version>

  74.        </dependency>

  75.    </dependencies>

  76.    <build>

  77.        <plugins>

  78.            <plugin>

  79.                <groupId>org.springframework.boot</groupId>

  80.                <artifactId>spring-boot-maven-plugin</artifactId>

  81.            </plugin>

  82.            <!--添加QueryDSL插件支持-->

  83.            <plugin>

  84.                <groupId>com.mysema.maven</groupId>

  85.                <artifactId>apt-maven-plugin</artifactId>

  86.                <version>1.1.3</version>

  87.                <executions>

  88.                    <execution>

  89.                        <goals>

  90.                            <goal>process</goal>

  91.                        </goals>

  92.                        <configuration>

  93.                            <outputDirectory>target/generated-sources/java</outputDirectory>

  94.                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>

  95.                        </configuration>

  96.                    </execution>

  97.                </executions>

  98.            </plugin>

  99.        </plugins>

  100.    </build>

  101. </project>

上图代码块有关配置如有不明白的地方请查看第一章:Maven环境下如何配置QueryDSL环境

构建数据库信息表

我们先来创建一张普通信息表,创建表SQL如下所示:

  1. DROP TABLE IF EXISTS `t_user`;

  2. CREATE TABLE `t_user` (

  3.  `t_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',

  4.  `t_name` varchar(30) DEFAULT NULL COMMENT '名称',

  5.  `t_age` int(10) DEFAULT NULL COMMENT '年龄',

  6.  `t_address` varchar(100) DEFAULT NULL COMMENT '家庭住址',

  7.  `t_pwd` varchar(100) CHARACTER SET latin1 DEFAULT NULL COMMENT '用户登录密码',

  8.  PRIMARY KEY (`t_id`)

  9. ) ENGINE=MyISAM AUTO_INCREMENT=17 DEFAULT CHARSET=utf8;

测试数据这里就不添加了。

添加application.yml配置文件

在resource目录下我们添加application.yml配置文件来代替application.properties文件,添加对应的数据库连接池的配置信息,代码如下所示:

  1. spring:

  2.  datasource:

  3.      type: com.alibaba.druid.pool.DruidDataSource

  4.      url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8

  5.      username: root

  6.      password: root

  7.      driver-class-name: com.mysql.jdbc.Driver

  8.      filters: stat

  9.      maxActive: 20

  10.      initialSize: 1

  11.      maxWait: 60000

  12.      minIdle: 1

  13.      timeBetweenEvictionRunsMillis: 60000

  14.      minEvictableIdleTimeMillis: 300000

  15.      validationQuery: select 'x'

  16.      testWhileIdle: true

  17.      testOnBorrow: false

  18.      testOnReturn: false

  19.      poolPreparedStatements: true

  20.      maxOpenPreparedStatements: 20

  21.  jpa:

  22.    properties:

  23.      hibernate:

  24.        show_sql: true

  25.        format_sql: true

创建实体

我们根据数据库内对应的字段创建一个实体类并添加对应的SpringDataJPA的注解,实体类代码如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter2.bean;

  2. import lombok.Data;

  3. import javax.persistence.*;

  4. import java.io.Serializable;

  5. /**

  6. * ========================

  7. * Created with IntelliJ IDEA.

  8. * User:恒宇少年

  9. * Date:2017/7/2

  10. * Time:18:31

  11. * 码云:http://git.oschina.net/jnyqy

  12. * ========================

  13. */

  14. @Data

  15. @Entity

  16. @Table(name = "t_user")

  17. public class UserBean implements Serializable

  18. {

  19.    @Id

  20.    @Column(name = "t_id")

  21.    @GeneratedValue

  22.    private Long id;

  23.    @Column(name = "t_name")

  24.    private String name;

  25.    @Column(name = "t_age")

  26.    private int age;

  27.    @Column(name = "t_address")

  28.    private String address;

  29.    @Column(name = "t_pwd")

  30.    private String pwd;

  31. }

实体内有个注解@Data比较特殊,之前也许大家没有使用过,当然你们肯定发现了我这个实体类内并没有对应字段的Getter/Setter方法,如果没有添加@Data注解在SpringDataJPA映射数据时会出现找不到对应字段的Setter方法,导致无法完成数据映射到实体的异常! 在上面的实体源码中可以看到@Data注解是在lombok包内,lombok其实是一个优雅的第三方插件,它可以让你的实体变得简洁,可读性也大大的得到了提升。在使用这个插件的时候需要你们Idea开发工具支持,必填安装相应的Plugin才可以,这里我就不多说相关lombok的配置问题了,大家在跟本章联系的时候可以使用Getter/Setter方法的形式代替@Data。

创建基类JPA

这里我们简单的封装下JPA,我们添加一个接口去继承我们需要的JPA接口并让所有子类继承我们的基类接口就可以了,基类JPA代码如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter2.jpa;

  2. import org.springframework.data.jpa.repository.JpaRepository;

  3. import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

  4. import org.springframework.data.querydsl.QueryDslPredicateExecutor;

  5. /**

  6. * 核心JPA

  7. * ========================

  8. * Created with IntelliJ IDEA.

  9. * User:恒宇少年

  10. * Date:2017/7/2

  11. * Time:18:23

  12. * 码云:http://git.oschina.net/jnyqy

  13. * ========================

  14. */

  15. @NoRepositoryBean

  16. public interface BaseJPA<T>

  17.        extends JpaRepository<T,Long>,

  18.        JpaSpecificationExecutor<T>,

  19.        QueryDslPredicateExecutor<T>

  20. {

  21. }

上面的注解@NoRepositoryBean是为了避免SpringDataJPA自动实例化才添加的。

创建逻辑JPA

接下来我们开始创建对应User模块的数据逻辑接口JPA,很简单,我们只需要创建一个接口继承下我们的BaseJPA就可以了,代码如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter2.jpa;

  2. import com.yuqiyu.querydsl.sample.chapter2.bean.UserBean;

  3. /**

  4. * ========================

  5. * Created with IntelliJ IDEA.

  6. * User:恒宇少年

  7. * Date:2017/7/2

  8. * Time:18:39

  9. * 码云:http://git.oschina.net/jnyqy

  10. * ========================

  11. */

  12. public interface UserJPA

  13.        extends BaseJPA<UserBean>

  14. {

  15.  //../可以添加命名方法查询

  16. }

我们在继承BaseJPA的时候用到了泛型,因为我们在BaseJPA内所继承的接口都需要我们传递一个具体的实体类的类型,所以这块我们采用了泛型来处理,只有具体逻辑JPA继承BaseJPA的时候传递具体的实体类型就可以了。

自动生成Q结构查询实体

我们之前说过了QueryDSL很神奇的地方就在于它是一个可通过Maven插件自动生成实体类型的结构查询实体,那么我们接下来使用maven compile命令来让我们配置的JPAAnnotationProcessor去做他自己的使命。 idea工具为我们的maven项目自带了比较全面的命令,我们直接使用就可以了,如下图1所示: 

编写查询

下面我们开始编写查询,在编写之前我们创建一个控制器用于查看我们查询到的数据,代码如下所示:

  1. package com.yuqiyu.querydsl.sample.chapter2.controller;

  2. import com.yuqiyu.querydsl.sample.chapter2.jpa.UserJPA;

  3. import org.springframework.beans.factory.annotation.Autowired;

  4. import org.springframework.web.bind.annotation.RequestMapping;

  5. import org.springframework.web.bind.annotation.RestController;

  6. /**

  7. * ========================

  8. * Created with IntelliJ IDEA.

  9. * User:恒宇少年

  10. * Date:2017/7/2

  11. * Time:18:38

  12. * 码云:http://git.oschina.net/jnyqy

  13. * ========================

  14. */

  15. @RestController

  16. @RequestMapping(value = "/user")

  17. public class UserController

  18. {

  19.    @Autowired

  20.    private UserJPA userJPA;

  21. }

这里我并没有追寻MVC三层的设计理念,因为这只是文章的测试Sample编写,大家在实际开发项目中还是需要按照MVC设计模式来进行设计架构。

查询全部数据并排序

我们先来一个简单的查询表内的所有数据并根据主键进行倒序,代码如下所示:

  1. @RestController

  2. @RequestMapping(value = "/user")

  3. public class UserController

  4. {

  5.    @Autowired

  6.    private UserJPA usrJPA;

  7.    //----以下是新添加内容

  8.    //实体管理者

  9.    @Autowired

  10.    private EntityManager entityManager;

  11.    //JPA查询工厂

  12.    private JPAQueryFactory queryFactory;

  13.    @PostConstruct

  14.    public void initFactory()

  15.    {

  16.        queryFactory = new JPAQueryFactory(entityManager);

  17.        System.out.println("init JPAQueryFactory successfully");

  18.    }

  19.    /**

  20.     * 查询全部数据并根据id倒序

  21.     * @return

  22.     */

  23.    @RequestMapping(value = "/queryAll")

  24.    public List<UserBean> queryAll()

  25.    {

  26.        //使用querydsl查询

  27.        QUserBean _Q_user = QUserBean.userBean;

  28.        //查询并返回结果集

  29.        return queryFactory

  30.                .selectFrom(_Q_user)//查询源

  31.                .orderBy(_Q_user.id.desc())//根据id倒序

  32.                .fetch();//执行查询并获取结果集

  33.    }

在使用QueryDSL进行查询之前我们声明了EntityManager的注入以及JPAQueryFactory工厂对象的创建,通过@PostConstruct注解在类初始化的时候完成对JPAQueryFactory对象的实例化。

我们在queryAll方法内首先获取了对应UserBean的查询实体QUserBean,通过QUserBean内自动生成的字段获取,我们使用JPAQueryFactory工厂对象的selectFrom方法来简化查询,该方法代替了select&from两个方法,注意:也是仅限单表操作时可以使用。

而我们倒序的方式看起来就更简单了,这种实现方式完全就像是在编写原始的SQL一样,如果是根据asc的方式进行排序则可以修改为: orderBy(Quser.id.asc()),看起来是不是特别简单?

在一系列的条件都添加完成后,调用fetch方法执行我们的条件查询并且获取对应selectFrom查询实体的类型集合,要注意一点:这里如果selectFrom参数的实体类型不是UserBean那fetch方法返回集合的类型也不是List

注意:在我们启动项目之前,我们需要修改pom.xml配置文件添加相关inject的依赖,SpringBoot内部并没有为我们提供该依赖。

  1. <dependency>

  2.    <groupId>javax.inject</groupId>

  3.    <artifactId>javax.inject</artifactId>

  4.    <version>1</version>

  5. </dependency>

添加该依赖后才能完成JPAQueryFactory的实例化,下面我们启动下项目访问地址看界面输出内容如下图3所示: 

可以看到上图3的输出内容数据是完全按照我们的查询条件来执行的,我们打开控制台看看SpringDataJPA为我们自动生成的SQL

  1. Hibernate:

  2.    select

  3.        userbean0_.t_id as t_id1_0_,

  4.        userbean0_.t_address as t_addres2_0_,

  5.        userbean0_.t_age as t_age3_0_,

  6.        userbean0_.t_name as t_name4_0_,

  7.        userbean0_.t_pwd as t_pwd5_0_

  8.    from

  9.        t_user userbean0_

  10.    order by

  11.        userbean0_.t_id desc

可以看到在SQL上面输出了Hibernate,StringDataJPA生成SQL这一块是使用的Hibernate,所以我们可以完全使用HQL的查询语言来编写JPA的查询。

根据主键查询单条数据

查询详情方法是我们常用到的查询之一,一般用于删除、更新。下面我们就来编写一个detail方法来看来QueryDSL是如何完成查询单挑数据的。

完全QueryDSL风格

代码如下所示:

  1. /**

  2.     * 查询详情

  3.     * @param id 主键编号

  4.     * @return

  5.     */

  6.    @RequestMapping(value = "/detail/{id}")

  7.    public UserBean detail(@PathVariable("id") Long id)

  8.    {

  9.        //使用querydsl查询

  10.        QUserBean _Q_user = QUserBean.userBean;

  11.        //查询并返回结果集

  12.        return queryFactory

  13.                .selectFrom(_Q_user)//查询源

  14.                .where(_Q_user.id.eq(id))//指定查询具体id的数据

  15.                .fetchOne();//执行查询并返回单个结果

  16.    }

SpringDataJPA整合QueryDSL风格

代码如下所示:

  1.    /**

  2.     * SpringDataJPA & QueryDSL实现单数据查询

  3.     * @param id

  4.     * @return

  5.     */

  6.    @RequestMapping(value = "/detail_2/{id}")

  7.    public UserBean detail_2(@PathVariable("id") Long id)

  8.    {

  9.        //使用querydsl查询

  10.        QUserBean _Q_user = QUserBean.userBean;

  11.        //查询并返回指定id的单条数据

  12.        return userJPA.findOne(_Q_user.id.eq(id));

  13.    }

上面的两种风格都是可以根据id字段返回指定的单条数据,当然在上面的编写看来还是使用SpringDataJPA & QueryDSL要简单些,也只是简单的查询整合风格要比纯QueryDSL要简便,但是如果添加排序、模糊查询时还是纯QueryDSL编写更简单一些。

查询指定主键时,我们使用了where方法并且指定了id字段需要eq参数id,这个eq是QueryDSL内置的一个方法,用于查询指定值数据,当然其他字段也同样可以使用eq方法来完成条件查询,都是可以变通使用的。

重启项目后我们来看下查询详情的运行效果,如下图4所示: 

我们再来看下控制台输出的生成SQL,如下代码所示:

  1. Hibernate:

  2.    select

  3.        userbean0_.t_id as t_id1_0_,

  4.        userbean0_.t_address as t_addres2_0_,

  5.        userbean0_.t_age as t_age3_0_,

  6.        userbean0_.t_name as t_name4_0_,

  7.        userbean0_.t_pwd as t_pwd5_0_

  8.    from

  9.        t_user userbean0_

  10.    where

  11.        userbean0_.t_id=?

可以看到是根据我们指定的字段来作为查询条件来检索的数据,我们通过fetchOne方法来返回一个结果。

根据名称模糊查询

下面我们来根据字段name完成模块查询,先来看下我们的查询条件代码如下:

  1. /**

  2.     * 根据名称模糊查询

  3.     * @param name

  4.     * @return

  5.     */

  6.    @RequestMapping(value = "/likeQueryWithName")

  7.    public List<UserBean> likeQueryWithName(String name)

  8.    {

  9.        //使用querydsl查询

  10.        QUserBean _Q_user = QUserBean.userBean;

  11.        return queryFactory

  12.                .selectFrom(_Q_user)//查询源

  13.                .where(_Q_user.name.like(name))//根据name模糊查询

  14.                .fetch();//执行查询并返回结果集

  15.    }

可以看到我们where条件是根据name字段的like方法来完成的模糊查询,like方法也是QueryDSL内置的方法,我们只需要传入查询的内容就可以实现模糊查询,下面我们来运行访问看下界面输出内容如下图5所示: 

  1. Hibernate:

  2.    select

  3.        userbean0_.t_id as t_id1_0_,

  4.        userbean0_.t_address as t_addres2_0_,

  5.        userbean0_.t_age as t_age3_0_,

  6.        userbean0_.t_name as t_name4_0_,

  7.        userbean0_.t_pwd as t_pwd5_0_

  8.    from

  9.        t_user userbean0_

  10.    where

  11.        userbean0_.t_name like ? escape '!'

也是完全按照我们指定的模糊查询字段生成的,到目前可以看到QueryDSL为我们减少了太多了查询繁琐的事情,让我们能够更好的投入到业务逻辑处理中。

总结

以上内容就是本章的全部讲解,本章主要讲述了SpringDataJPA整合QueryDSL后完全使用QueryDSL来进行单表的查询,使用QueryDSL可以完全按照编写原始SQL的思想来编写查询条件,容易上手,方便快捷。由于QueryDSL的功能强大,有很多东西没有讲解到,希望大家可以在后期的项目中得到总结。

本章源码已经上传到码云: 

SpringBoot配套源码地址:https://gitee.com/hengboy/spring-boot-chapter

SpringCloud配套源码地址:https://gitee.com/hengboy/spring-cloud-chapter 

推荐阅读

SpringBoot平台使用Lombok来优雅的编码


在SpringBoot内如何使用ApplicationEvent&Listener完成业务解耦?


SpringBoot自定义专属业务的Starter


SpringBoot使用MapStruct自动映射DTO


SpringBoot架构使用Profile完成打包环境分离


SpringBoot2.0新特性 - 你get到WebMvcConfigurer两种配置方式了吗?


SpringBoot2.0新特性 - 岂止至今最简单redis缓存集成


SpringBoot2.0新特性 - Quartz自动化配置集成


编码规范 - 养成良好的Java编码习惯


基于SpringBoot 设计业务逻辑异常统一处理


基于SpringBoot & Quartz分布式单节点持久化



加入知识星球,恒宇少年带你走以后的技术道路!!!


Copyright © 温县电话机虚拟社区@2017