1. 程式人生 > >第六章:使用QueryDSL的聚合函式

第六章:使用QueryDSL的聚合函式

在企業級專案開發過程中,往往會經常用到資料庫內的聚合函式,一般ORM框架應對這種邏輯問題時都會採用編寫原生的SQL來處理,而QueryDSL完美的解決了這個問題,它內建了SQL所有的聚合函式下面我們簡單介紹我們常用的幾個聚合函式。

本章目標

基於SpringBoot平臺整合QueryDSL完成常用聚合函式使用。

構建專案

我們使用idea來建立一個SpringBoot專案,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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.yuqiyu.querydsl.sample</groupId> <artifactId>chapter6</artifactId>
<version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <name>chapter6</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>

建立資料表

下面我們來建立一個張資料表來講解本章的內容,表結構如下所示:

/*
Navicat MariaDB Data Transfer

Source Server         : local
Source Server Version : 100108
Source Host           : localhost:3306
Source Database       : test

Target Server Type    : MariaDB
Target Server Version : 100108
File Encoding         : 65001

Date: 2017-07-13 15:57:37
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `u_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵編號',
  `u_username` varchar(50) CHARACTER SET utf8 DEFAULT NULL COMMENT '使用者名稱',
  `u_age` int(10) DEFAULT NULL COMMENT '年齡',
  `u_score` double(8,2) DEFAULT NULL COMMENT '積分',
  PRIMARY KEY (`u_id`)
) ENGINE=MyISAM AUTO_INCREMENT=5 DEFAULT CHARSET=latin1;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('1', 'admin', '12', '45.70');
INSERT INTO `users` VALUES ('2', 'hengyu', '23', '56.40');
INSERT INTO `users` VALUES ('3', 'test', '22', '67.80');
INSERT INTO `users` VALUES ('4', 'jocker', '25', '99.00');

我們簡單建立了一張使用者資訊表,表內的年齡、積分是我們本章主要使用到的欄位,下面我們就開始來講解本章的內容。

建立實體

我們對應資料庫內的表結構建立我們需要的實體並新增JPA的對映,實體程式碼如下所示:

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

import lombok.Data;

import javax.persistence.*;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/7/12
 * Time:10:58
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@Entity
@Table(name = "users")
@Data
public class UserBean
{
    @Id
    @GeneratedValue
    @Column(name = "u_id")
    private Long id;
    @Column(name = "u_username")
    private String name;
    @Column(name = "u_age")
    private int age;
    @Column(name = "u_score")
    private double socre;
}

如果對@Data註解有疑問,大家可以去GitHub查一下lombok開源專案。
我們的實體已經建立完成,下面我們開始使用maven compile命令完成QueryDSL查詢實體的建立,我們找到Maven Projects視窗,展開Lifecyle組,雙擊compile命令即可,如下圖1所示:

圖1
檢視控制檯輸出Build Success表示專案構建完成,我們就可以在target/generated-sources/java目錄下看到自動生成的查詢實體原始碼。

建立控制器

本章建立控制器的方法與前幾章一致,採用@PostConstruct來初始化JPAQueryFactory實體物件,控制器程式碼如下所示:

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

import com.querydsl.jpa.impl.JPAQueryFactory;
import com.yuqiyu.querydsl.sample.chapter6.bean.UserBean;
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;

/**
 * ========================
 * Created with IntelliJ IDEA.
 * User:恆宇少年
 * Date:2017/7/12
 * Time:10:59
 * 碼雲:http://git.oschina.net/jnyqy
 * ========================
 */
@RestController
public class UserController
{
    //實體管理物件
    @Autowired
    private EntityManager entityManager;
    //queryDSL,JPA查詢工廠
    private JPAQueryFactory queryFactory;

    //例項化查詢工廠
    @PostConstruct
    public void init()
    {
        queryFactory = new JPAQueryFactory(entityManager);
    }
}

下面我們開始編寫聚合函式程式碼。

Count函式

我們現在的需求是查詢使用者表內的總條數,控制器方法程式碼如下所示:

/**
     * count聚合函式
     * @return
     */
    @RequestMapping(value = "/countExample")
    public long countExample()
    {
        //使用者查詢實體
        QUserBean _Q_user = QUserBean.userBean;
        return queryFactory
                .select(_Q_user.id.count())//根據主鍵查詢總條數
                .from(_Q_user)
                .fetchOne();//返回總條數
    }

可以看到我們根據id這個欄位進行了count聚合,當然我們也可以根據實體內任意欄位進行count聚合,我們一般會根據主鍵來進行聚合,因為主鍵預設有索引,效率會更高。

這裡要注意一點,我們使用的fetchOne方法返回的型別完全是根據select方法內單個引數的型別對應的。

下面我們來啟動專案測試下我們這個count聚合是否有效,專案啟動完成後我們訪問地址http://127.0.0.1:8080/countExample,介面輸入內容如下圖2所示:

圖2
我們再來看下控制檯輸出的生成SQL是否為我們預期的效果,SQL如下所示:

Hibernate: 
    select
        count(userbean0_.u_id) as col_0_0_ 
    from
        users userbean0_

可以看到QueryDSL自動生成的SQL跟我們預期的是一樣的,我又被QueryDSL的方便深深的折服了。

Sum函式

接下來我們需要查詢所有使用者分數總和,程式碼如下所示:

    /**
     * sum聚合函式
     * @return
     */
    @RequestMapping(value = "/sumExample")
    public double sumExample()
    {
        //使用者查詢實體
        QUserBean _Q_user = QUserBean.userBean;
        return queryFactory
                .select(_Q_user.socre.sum())//查詢積分總數
                .from(_Q_user)
                .fetchOne();//返回積分總數
    }

我們重啟專案測試我們的sum聚合函式是否能夠查詢出總分數,訪問地址http://127.0.0.1:8080/sumExample介面輸出內容如下圖3所示:
圖3
我們再來檢視下控制檯輸出的生成SQL,如下所示:

Hibernate: 
    select
        sum(userbean0_.u_score) as col_0_0_ 
    from
        users userbean0_

也是沒問題的,很智慧,可謂是指哪打哪。

Avg函式

下面我們又有新的需求了,需要查詢下積分的平均值,程式碼如下所示:


    /**
     * avg聚合函式
     * @return
     */
    @RequestMapping(value = "/avgExample")
    public double avgExample()
    {
        //使用者查詢實體
        QUserBean _Q_user = QUserBean.userBean;
        return queryFactory
                .select(_Q_user.socre.avg())//查詢積分平均值
                .from(_Q_user)
                .fetchOne();//返回平均值
    }

訪問對映地址介面輸出內容如下圖4所示:
圖4
我們再來看下控制檯輸出的SQL,如下所示:

Hibernate: 
    select
        avg(userbean0_.u_score) as col_0_0_ 
    from
        users userbean0_

可以看到QueryDSL自動根據積分欄位進行了avg聚合實現。

Max函式

接下來我們來查詢使用者最大積分值,程式碼如下所示:

    /**
     * max聚合函式
     * @return
     */
    @RequestMapping(value = "/maxExample")
    public double maxExample()
    {
        //使用者查詢實體
        QUserBean _Q_user = QUserBean.userBean;
        return queryFactory
                .select(_Q_user.socre.max())//查詢最大積分
                .from(_Q_user)
                .fetchOne();//返回最大積分
    }

我們根據積分欄位呼叫max方法即可獲取最大積分,然後呼叫fetchOne方法就能夠返回double型別的最大積分值。我們重啟下專案訪問路徑http://127.0.0.1:8080/maxExample介面輸出內容如下圖5所示:
圖5
下面再來看下控制檯輸出的SQL,如下所示:

Hibernate: 
    select
        max(userbean0_.u_score) as col_0_0_ 
    from
        users userbean0_

到現在為止我們得出來了一個結論,如果原生SQL內聚合函式是作用在欄位上,在QueryDSL內使用方法則是查詢屬性.xxx函式,那麼接下來的聚合函式作用域就不是欄位了而變成了表。

Group By函式

我們的分組函式該如何使用呢?下面我們根據積分進行分組並且僅查詢年齡大於22歲的資料,控制器程式碼如下所示:

    /**
     * group by & having聚合函式
     * @return
     */
    @RequestMapping(value = "/groupByExample")
    public List<UserBean> groupByExample()
    {
        //使用者查詢實體
        QUserBean _Q_user = QUserBean.userBean;
        return queryFactory
                .select(_Q_user)
                .from(_Q_user)
                .groupBy(_Q_user.socre)//根據積分分組
                .having(_Q_user.age.gt(22))//並且年齡大於22歲
                .fetch();//返回使用者列表
    }

因為Group By函式作用域不是欄位而是表,所以會與select、from方法同級,跟原生SQL一樣使用Group By進行查詢時查詢條件不能使用where,而是having!在QueryDSL內也是一樣,因為QueryDSL完全遵循了SQL標準。
下面我們重啟下專案訪問地址http://127.0.0.1:8080/groupByExample看下效果,如下圖6所示:
圖6
可以看到我們讀取到資料是正確的,僅僅查詢出了大於22歲的資料。下面我們再來看下控制檯輸出的SQL如下所示:

Hibernate: 
    select
        userbean0_.u_id as u_id1_0_,
        userbean0_.u_age as u_age2_0_,
        userbean0_.u_username as u_userna3_0_,
        userbean0_.u_score as u_score4_0_ 
    from
        users userbean0_ 
    group by
        userbean0_.u_score 
    having
        userbean0_.u_age>?

可以看到SQL是根據積分欄位進行分組並且查詢年齡大於22歲的列表。

總結

以上內容就是本章的全部講解,我們不管是從上面的程式碼還是之前章節的程式碼可以得到一個QueryDSL的設計主導方向,QueryDSL完全遵循SQL標準進行設計,SQL內的作用域的關鍵字在QueryDSL內也是通過,不過展現形式不同罷了。
上面函式不是全部的聚合函式,專案中如果需要其他函式可按照本章的思路去寫。

更多幹貨文章掃碼關注微信公眾號

掃碼關注 - 專注分享

加入知識星球,恆宇少年帶你走以後的技術道路!!!

微信掃碼加入