第六章:使用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所示:
檢視控制檯輸出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所示:
我們再來看下控制檯輸出的生成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所示:
我們再來檢視下控制檯輸出的生成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所示:
我們再來看下控制檯輸出的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所示:
下面再來看下控制檯輸出的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所示:
可以看到我們讀取到資料是正確的,僅僅查詢出了大於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內也是通過,不過展現形式不同罷了。
上面函式不是全部的聚合函式,專案中如果需要其他函式可按照本章的思路去寫。