Chapter 5: Using QueryDSL and SpringDataJPA to implement queries and return custom objects

In our actual project development, we often encounter a multi-table association query and only need to return several fields in the multi-table and finally combine them into a collection or entity. In this situation, in the traditional query, we can't control the fields to be queried, and we can only separate them after they have been queried. This is also the most unwilling way to deal with it. This method will be cumbersome, complicated and inefficient. , poor code readability, etc. QueryDSL provides us with a tool type that returns custom objects, and the stream method in Java8's new feature Collection can also complete the logic of returning custom objects. Let's take a look at how to write these two methods?

Objectives of this chapter

Based on the SpringBoot platform, there are two ways to integrate SpringDataJPA and QueryDSL to query and return custom objects.

Build the project

Let's first use the idea tool to create a SpringBoot project and add the corresponding dependencies in advance. The content of the pom.xml configuration file is as follows:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yuqiyu.querydsl.sample</groupId>
    <artifactId>chapter5</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>chapter5</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--阿里巴巴数据库连接池,专为监控而生 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.26</version>
        </dependency>
        <!-- 阿里巴巴fastjson,解析json视图 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.15</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <!--<scope>provided</scope>-->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--queryDSL-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.16</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--添加QueryDSL插件支持-->
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

The QueryDSL in the above will not be explained here. If you have any questions, please refer to Chapter 1: How to configure the QueryDSL environment in the Maven environment .
Next we need to create two tables to complete this chapter.

Create table structure

As in the previous chapter, we still use the product information table and the product type table to complete the coding.

Product Information Sheet

-- ----------------------------
-- Table structure for good_infos
-- ----------------------------
DROP TABLE IF EXISTS `good_infos`;
CREATE TABLE `good_infos` (
  `tg_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `tg_title` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '商品标题',
  `tg_price` decimal(8,2) DEFAULT NULL COMMENT '商品单价',
  `tg_unit` varchar(20) CHARACTER SET utf8 DEFAULT NULL COMMENT '单位',
  `tg_order` varchar(255) DEFAULT NULL COMMENT '排序',
  `tg_type_id` int(11) DEFAULT NULL COMMENT '类型外键编号',
  PRIMARY KEY (`tg_id`),
  KEY `tg_type_id` (`tg_type_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of good_infos
-- ----------------------------
INSERT INTO `good_infos` VALUES ('1', '金针菇', '5.50', '斤', '1', '3');
INSERT INTO `good_infos` VALUES ('2', '油菜', '12.60', '斤', '2', '1');

Product Type Information Sheet

-- ----------------------------
-- Table structure for good_types
-- ----------------------------
DROP TABLE IF EXISTS `good_types`;
CREATE TABLE `good_types` (
  `tgt_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键自增',
  `tgt_name` varchar(30) CHARACTER SET utf8 DEFAULT NULL COMMENT '类型名称',
  `tgt_is_show` char(1) DEFAULT NULL COMMENT '是否显示',
  `tgt_order` int(2) DEFAULT NULL COMMENT '类型排序',
  PRIMARY KEY (`tgt_id`)
) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of good_types
-- ----------------------------
INSERT INTO `good_types` VALUES ('1', '绿色蔬菜', '1', '1');
INSERT INTO `good_types` VALUES ('2', '根茎类', '1', '2');
INSERT INTO `good_types` VALUES ('3', '菌类', '1', '3');

Create entity

We create entities corresponding to the table structure and add corresponding SpringDataJPA annotations.

commodity entity

package com.yuqiyu.querydsl.sample.chapter5.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 商品基本信息实体
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/10
 * Time:22:39
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_infos")
@Data
public class GoodInfoBean
    implements Serializable
{
    //主键
    @Id
    @Column(name = "tg_id")
    @GeneratedValue
    private Long id;
    //标题
    @Column(name = "tg_title")
    private String title;
    //价格
    @Column(name = "tg_price")
    private double price;
    //单位
    @Column(name = "tg_unit")
    private String unit;
    //排序
    @Column(name = "tg_order")
    private int order;
    //类型编号
    @Column(name = "tg_type_id")
    private Long typeId;
}

commodity type entity

package com.yuqiyu.querydsl.sample.chapter5.bean;

import lombok.Data;

import javax.persistence.*;
import java.io.Serializable;

/**
 * 商品类别实体
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/10
 * Time:22:39
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "good_types")
@Data
public class GoodTypeBean
    implements Serializable
{
    //类型编号
    @Id
    @GeneratedValue
    @Column(name = "tgt_id")
    private Long id;
    //类型名称
    @Column(name = "tgt_name")
    private String name;
    //是否显示
    @Column(name = "tgt_is_show")
    private int isShow;
    //排序
    @Column(name = "tgt_order")
    private int order;
}

The annotation @Entity in the above entity identifies that the entity is managed by SpringDataJPA, @Table identifies the table information in the database corresponding to the entity, and the @Data annotation is a merged annotation in lombok, and getters/setters are automatically added according to the plugin of the idea tool , toString, full parameter constructor, etc.

Create DTO

We create a custom object returned by the query. The fields in the object contain part of the content of the commodity entity and commodity type entity. The DTO code is as follows:

package com.yuqiyu.querydsl.sample.chapter5.dto;

import lombok.Data;

import java.io.Serializable;

/**
 * 商品dto
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/10
 * Time:22:39
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@Data
public class GoodDTO
    implements Serializable
{
    //主键
    private Long id;
    //标题
    private String title;
    //单位
    private String unit;
    //价格
    private double price;
    //类型名称
    private String typeName;
    //类型编号
    private Long typeId;
}

It should be noted that the object returned by our custom is just an entity and does not correspond to a table in the database, so there is no need to configure JPA annotations such as @Entity, @Table, etc., just configure the @Data annotation. Next, we Compile the project to let the QueryDSL plugin automatically generate query entities.

Generate query entity

The idea tool automatically adds the corresponding function to the maven project. We open the Maven Projects on the right, as shown in Figure 1 below:

figure 1
We double-click the compile command to execute. After the execution is completed, the QueryDSL query entity of the corresponding entity will be generated in the configuration generation directory in our pom.xml configuration file. The generated query entity is shown in Figure 2 below:

figure 2

QueryDSL configuration JPA plugin will only generate query entities based on @Entity

Create a controller

Let's create a test controller to read all the products in the product table. Before writing a specific query method, we need to instantiate the EntityManager object and the JPAQueryFactory object, and instantiate the JPAQueryFactory object when the controller is instantiated. The controller code looks like this:

package com.yuqiyu.querydsl.sample.chapter5.controller;

import com.querydsl.core.types.Projections;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yuqiyu.querydsl.sample.chapter5.bean.QGoodInfoBean;
import com.yuqiyu.querydsl.sample.chapter5.bean.QGoodTypeBean;
import com.yuqiyu.querydsl.sample.chapter5.dto.GoodDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 多表查询返回商品dto控制器
 * ========================
 * Created with IntelliJ IDEA.
 * User:恒宇少年
 * Date:2017/7/10
 * Time:23:04
 * 码云:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class GoodController
{

    //实体管理
    @Autowired
    private EntityManager entityManager;

    //查询工厂
    private JPAQueryFactory queryFactory;

    //初始化查询工厂
    @PostConstruct
    public void init()
    {
        queryFactory = new JPAQueryFactory(entityManager);
    }
}

It can be seen that what we configure is a @RestController, the data returned by the controller are all Json strings, which is also the rule followed by RestController.

QueryDSL & Projections

Let's start writing a method that returns a custom object completely based on the QueryDSL form. The code is as follows:

 /**
     * 根据QueryDSL查询
     * @return
     */
    @RequestMapping(value = "/selectWithQueryDSL")
    public List<GoodDTO> selectWithQueryDSL()
    {
        //商品基本信息
        QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
        //商品类型
        QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;

        return queryFactory
                .select(
                        Projections.bean(
                                GoodDTO.class,//返回自定义实体的类型
                                _Q_good.id,
                                _Q_good.price,
                                _Q_good.title,
                                _Q_good.unit,
                                _Q_good_type.name.as("typeName"),//使用别名对应dto内的typeName
                                _Q_good_type.id.as("typeId")//使用别名对应dto内的typeId
                         )
                )
                .from(_Q_good,_Q_good_type)//构建两表笛卡尔集
                .where(_Q_good.typeId.eq(_Q_good_type.id))//关联两表
                .orderBy(_Q_good.order.desc())//倒序
                .fetch();
    }

We can see the selectWithQueryDSL() query method above, and a new type Projections appears in it. This type is the built-in solution of QueryDSL for processing custom returned result sets. It includes constructors, entities, fields and other processing methods. We Today we mainly talk about entities.

The JPAQueryFactory factory select method can take the QBean returned by the Projections method as a parameter. We use the bean method of Projections to build the returned result set and map it to the entity, which is a bit like the ResultMap in Mybatis, but the internal processing mechanism is definitely There is a huge difference ! The first parameter of the bean method needs to pass the generic type of an entity as a single object type in the returned collection. If the field names in the QueryDSL query entity are different from the field names of the DTO entity, we can use the as method to deal with it. Add an alias to the field specified in the result set of the query, so that it will be automatically mapped to the DTO entity.

run the test

Let's run the next project and visit the address: http://127.0.0.1:8080/selectWithQueryDSL to view the output of the interface as shown in the following code block:

[
    {
        "id": 2,
        "title": "油菜",
        "unit": "斤",
        "price": 12.6,
        "typeName": "绿色蔬菜",
        "typeId": 1
    },
    {
        "id": 1,
        "title": "金针菇",
        "unit": "斤",
        "price": 5.5,
        "typeName": "菌类",
        "typeId": 3
    }
]

We can see that the output Json array string is the effect of the deserialization of all fields in our DTO. The corresponding typeName and typeId in the DTO entity have been queried and assigned.
Let's take a look at the SQL automatically generated by the console output, as shown in the following code block:

Hibernate: 
    select
        goodinfobe0_.tg_id as col_0_0_,
        goodinfobe0_.tg_price as col_1_0_,
        goodinfobe0_.tg_title as col_2_0_,
        goodinfobe0_.tg_unit as col_3_0_,
        goodtypebe1_.tgt_name as col_4_0_,
        goodtypebe1_.tgt_id as col_5_0_ 
    from
        good_infos goodinfobe0_ cross 
    join
        good_types goodtypebe1_ 
    where
        goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id 
    order by
        goodinfobe0_.tg_order desc

The generated SQL is an association query in the form of cross join. The association form replaces on goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id with where goodinfobe0_.tg_type_id=goodtypebe1_.tgt_id. The final query result set returns data in these two ways.

QueryDSL & Collection

Next, we use the new features of java8 to return custom result sets. Our query is still in the form of QueryDSL. The method code is as follows:

 /**
     * 使用java8新特性Collection内stream方法转换dto
     * @return
     */
    @RequestMapping(value = "/selectWithStream")
    public List<GoodDTO> selectWithStream()
    {
        //商品基本信息
        QGoodInfoBean _Q_good = QGoodInfoBean.goodInfoBean;
        //商品类型
        QGoodTypeBean _Q_good_type = QGoodTypeBean.goodTypeBean;
        return queryFactory
                .select(
                        _Q_good.id,
                        _Q_good.price,
                        _Q_good.title,
                        _Q_good.unit,
                        _Q_good_type.name,
                        _Q_good_type.id
                )
                .from(_Q_good,_Q_good_type)//构建两表笛卡尔集
                .where(_Q_good.typeId.eq(_Q_good_type.id))//关联两表
                .orderBy(_Q_good.order.desc())//倒序
                .fetch()
                .stream()
                //转换集合内的数据
                .map(tuple -> {
                    //创建商品dto
                    GoodDTO dto = new GoodDTO();
                    //设置商品编号
                    dto.setId(tuple.get(_Q_good.id));
                    //设置商品价格
                    dto.setPrice(tuple.get(_Q_good.price));
                    //设置商品标题
                    dto.setTitle(tuple.get(_Q_good.title));
                    //设置单位
                    dto.setUnit(tuple.get(_Q_good.unit));
                    //设置类型编号
                    dto.setTypeId(tuple.get(_Q_good_type.id));
                    //设置类型名称
                    dto.setTypeName(tuple.get(_Q_good_type.name));
                    //返回本次构建的dto
                    return dto;
                })
                //返回集合并且转换为List<GoodDTO>
                .collect(Collectors.toList());
    }

From the beginning of the method to the end of fetch(), it is completely no different from QueryDSL. It uses the most primitive way to return the result set, but after getting the result set from fetch(), the way we process it has changed. The fetch() method The returned type is a generic List (List). List inherits from Collection and has the right to use non-private methods in Collection. By calling the stream method, the collection can be converted into a Stream generic object, and the map method of the object can operate the collection. For the conversion of a single object, the specific conversion code can be written according to business logic.
There is a lambda expression parameter tuple in the map method. We can get the query field in the corresponding select method through the tuple object get method.

The tuple can only get the fields that exist in the select. If the select is an entity object, the tuple cannot get the value of the specified field.

run the test

Next, we restart the project and access the address: 127.0.0.1:8080/selectWithStream . The output content of the interface is shown in the following code block:

[
    {
        "id": 2,
        "title": "油菜",
        "unit": "斤",
        "price": 12.6,
        "typeName": "绿色蔬菜",
        "typeId": 1
    },
    {
        "id": 1,
        "title": "金针菇",
        "unit": "斤",
        "price": 5.5,
        "typeName": "菌类",
        "typeId": 3
    }
]

You can see that the returned data is consistent with the above method. Of course, you can also guess that the automatically generated SQL is the same, so I will not explain the SQL here.

Summarize

The above content is the whole content of this chapter. The two methods explained in this chapter are based on QueryDSL for querying, but one uses the form provided by QueryDSL to encapsulate custom objects, while the other uses java8 features to complete, Projections There are many other methods with Stream, and interested partners can check it out on GitHub.

QueryDSL official document: http://www.querydsl.com/static/querydsl/latest/reference/html/ch02.html
The code in this chapter has been uploaded to the code cloud:
SpringBoot supporting source code address: https://gitee.com/hengboy/spring -boot-chapter
SpringCloud supporting source code address: https://gitee.com/hengboy/spring-cloud-chapter
SpringBoot related series of articles please visit: Catalog: SpringBoot Learning Catalog
QueryDSL related series of articles please visit: QueryDSL General Query Framework Learning Catalog
SpringDataJPA For related series of articles, please visit: Catalog: Spring Data JPA Learning Catalog
Thank you for reading!
Welcome to join the QQ technical exchange group and make progress together.
QQ technical exchange group

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324736176&siteId=291194637