# spring-boot-learn
**Repository Path**: tianwyam/spring-boot-learn
## Basic Information
- **Project Name**: spring-boot-learn
- **Description**: 基于maven的多模块方式 学习spring boot项目
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-08-30
- **Last Updated**: 2022-03-24
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# spring-boot-learn
是基于maven多模块工程来记录学习springboot的知识的一个过程
目录:
- [spring-boot-learn](#spring-boot-learn)
- [spring-boot-learn-jwt](#spring-boot-learn-jwt)
- [JWT简单使用](#JWT简单使用)
- [使用Assembly打包](#使用Assembly打包)
- [spring-boot-learn-freemarker](#spring-boot-learn-freemarker)
- [freemarker实现转word](#freemarker实现转word)
- [spring-boot-learn-jsp](#spring-boot-learn-jsp)
- [集成JSP](#集成JSP)
- [spring-boot-learn-word](#spring-boot-learn-word)
- [给PDF文件添加文本水印](#给PDF文件添加文本水印)
- [spring-boot-learn-feign](#spring-boot-learn-feign)
- [在springboot环境下](#在springboot环境下)
- [不在spring环境下](#不在spring环境下)
- [spring-boot-learn-capture-screen](#spring-boot-learn-capture-screen)
- [获取屏幕截屏](#获取屏幕截屏)
- [服务端推送](#服务端推送)
- [spring-boot-learn-websocket 实现聊天-即时通讯](#spring-boot-learn-websocket)
- [spring-boot-learn-mybatis 简单使用](#spring-boot-learn-mybatis)
- [spring-boot-learn-javafx 使用JavaFX制作一个GUI客户端](#spring-boot-learn-javafx)
- [上传本地JAR到私服仓库](#上传本地JAR到私服仓库)
- [spring-boot-learn-mockito 单元测试](#spring-boot-learn-mockito)
## spring-boot-learn-jwt
spring boot集成JWT,实现token认证
### JWT简单使用
第一步:导入依赖
~~~xml
com.auth0
java-jwt
3.8.1
io.jsonwebtoken
jjwt
0.9.1
~~~
第二步:使用JWT工具类生成token(用于生成token)
~~~java
/**
* @description
* 生成TOKEN
* @author TianwYam
* @date 2021年5月19日下午5:43:36
* @param tokenReqBean
* @return
*/
public static String token(TokenReqBean tokenReqBean) {
long nowTime = System.currentTimeMillis();
// 私钥 不允许给其他人
Algorithm algorithm = Algorithm.HMAC256(secret);
return JWT.create()
// 内容
.withSubject(encode(JSON.toJSONString(tokenReqBean)))
// token生成时刻
.withIssuedAt(new Date(nowTime))
// 过期时刻
.withExpiresAt(new Date(nowTime + EXPIRES_TIME))
.sign(algorithm);
}
~~~
第三步:验证token
~~~java
/**
* @description
* 验证TOKEN是否过期等等
* @author TianwYam
* @date 2021年5月19日下午5:51:22
* @param token
* @return 过期 则抛出异常
*/
public static TokenReqBean checkToken(String token) {
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
String subject = verifier.verify(token).getSubject();
return JSON.parseObject(decode(subject), TokenReqBean.class);
}
~~~
### 使用Assembly打包
pom.xml
~~~xml
src/main/java
**/*.*
src/main/resources
*.*
true
org.apache.maven.plugins
maven-jar-plugin
*.properties
*.yml
release-note
true
lib/
false
com.tianya.springboot.jwt.LearnJwtApplication
./
${project.build.directory}
org.apache.maven.plugins
maven-dependency-plugin
copy-dependencies
package
copy-dependencies
${project.build.directory}/lib
false
false
runtime
maven-assembly-plugin
assembly.xml
make-assembly
package
single
false
${project.artifactId}-install-${project.version}-${maven.build.timestamp}
~~~
assembly.xml
~~~xml
allsystem
tar.gz
zip
true
${basedir}/target/classes
./
0777
*.properties
*.yml
*.sh
${basedir}/target/lib
lib
0777
${basedir}/target
./
0777
${project.build.finalName}.jar
~~~
## spring-boot-learn-freemarker
spring boot集成freemarker
### freemarker实现转word
第一步:制作word模板
第二步:word模板转xml文件
第三步:修改xml文件内容(添加freemarker语法)
第四步:修改xml文件后缀为.ftl
第五步:使用freemarker的API语法实现输出word文件
## spring-boot-learn-jsp
spring boot集成JSP
### 集成JSP
第一步:添加依赖
~~~xml
org.springframework.boot
spring-boot-starter-web
javax.servlet
javax.servlet-api
javax.servlet
jstl
org.apache.tomcat.embed
tomcat-embed-jasper
~~~
第二步:主配置application.yml文件
spring boot 版本不一样配置也不一样
~~~yaml
server:
port: 8080
servlet:
context-path: /
spring:
mvc:
view:
# 前缀
prefix: /WEB-INF/
# 后缀
suffix: .jsp
~~~
第三步:编写控制器
~~~java
@Controller
public class IndexController {
@GetMapping("/index")
public String index() {
return "index" ;
}
}
~~~
启动类:
~~~java
@SpringBootApplication
public class JspLearnApplication {
public static void main(String[] args) {
SpringApplication.run(JspLearnApplication.class, args);
}
}
~~~
第四步:新建JSP文件
目前只有两种位置放JSP,才能不报404资源找不到错误
1、/resources/META-INF/resourcesWEB-INF/
2、/webapp/WEB-INF/
若是两个位置都存在相同资源
则优先级:/webapp/WEB-INF/ 大于 /resources/META-INF/resourcesWEB-INF/
其他位置,都报找不到资源404
位置:webapp/WEB-INF/
~~~jsp
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
首页
INDEX JSP | /webapp/WEB-INF/index.jsp
~~~
JSP位置放不对,很容易导致资源访问不到问题
## spring-boot-learn-word
针对word的操作:
- spire.doc.free 简单示例
- itextpdf实现对生成后的PDF添加页码示例
### 给PDF文件添加文本水印
第一步:导入依赖
~~~xml
com.itextpdf
itextpdf
5.5.13.1
com.itextpdf
itext-asian
5.2.0
~~~
第二步:编写代码
~~~java
/**
* @description
* 给PDF文档添加水印
* @author TianwYam
* @date 2021年4月28日上午10:00:05
*/
public static void addWaterMark(String pdfFilePath, String outputFilePath) {
try {
// 原PDF文件
PdfReader reader = new PdfReader(pdfFilePath);
// 输出的PDF文件内容
PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(outputFilePath));
// 字体
BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", true);
PdfGState gs = new PdfGState();
// 设置透明度
gs.setFillOpacity(0.3f);
gs.setStrokeOpacity(0.4f);
int totalPage = reader.getNumberOfPages() + 1;
for (int i = 1; i < totalPage; i++) {
// 内容上层
// PdfContentByte content = stamper.getOverContent(i);
// 内容下层
PdfContentByte content = stamper.getUnderContent(i);
content.beginText();
// 字体添加透明度
content.setGState(gs);
// 添加字体大小等
content.setFontAndSize(baseFont, 50);
// 添加范围
content.setTextMatrix(70, 200);
// 具体位置 内容 旋转多少度
content.showTextAligned(Element.ALIGN_CENTER, "机密文件", 300, 350, 300);
content.showTextAligned(Element.ALIGN_TOP, "机密文件", 100, 100, 5);
content.showTextAligned(Element.ALIGN_BOTTOM, "机密文件", 400, 400, 75);
content.endText();
}
// 关闭流
stamper.close();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
~~~
## spring-boot-learn-excel
针对excel的操作:
- hutool工具实现对excel导入导出的示例
## spring-boot-learn-crawler-webmagic
简单学习WebMagic,是一个简单灵活的Java爬虫框架
- 使用webmagic爬取了笔趣阁小说类别
- 爬取了笔趣阁小说《剑来》目录
## spring-boot-learn-mybatis
spring boot整合mybatis持久层框架
第一步:添加spring boot和mybatis的依赖,可以根据实际情况进行修改版本
~~~xml
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.0.0
mysql
mysql-connector-java
~~~
第二步:application.yml文件添加配置
分为数据源配置 + mybatis的配置
~~~yaml
## 数据源的配置
spring:
datasource:
username: root
password: mysql
## 新版本MySQL驱动链接URL必须添加时区配置 serverTimezone=UTC
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver
## mybatis的配置
mybatis:
## 配置mybatis的总配置文件路径
config-location: classpath:mybatis/mybatis.xml
## 配置文件各个mapper xml文件的路径
mapper-locations: classpath:mybatis/mapper/*.xml
## 配置取别名的实体类包全路径
type-aliases-package: com.tianya.springboot.mybatis.entity
~~~
第三步:在启动类上添加mapper接口扫描路径
~~~java
@SpringBootApplication
// 自动扫描mapper接口全路径
@MapperScan("com.tianya.springboot.mybatis.mapper")
public class SpringMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringMybatisApplication.class, args);
}
}
~~~
第四步:在接口上添加mapper注解
~~~java
// mapper是mybatis的注解,定义一个mapper接口
// 也会被添加到spring bean容器动
// 若是只用@Repository注解,不会有mybatis的自动扫描配置
// 若是配置了SqlSessionFactory,也是可以的
@Mapper
public interface IUserMapper {
public List getUserList() ;
}
~~~
第五步:在mapper xml文件具体实现的SQL的方法
~~~xml
~~~
## spring-boot-learn-capture-screen
使用服务端推送技术SSE+屏幕截屏,实现一个简单的屏幕共享功能
- SseEmitter 实现服务端推送功能
- java.awt.Toolkit 获取屏幕截屏
### 获取屏幕截屏
~~~java
/**
* 捕获屏幕,生成图片
*/
public static String capture() {
// 获取屏幕大小
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
String fileName = null ;
try {
Robot robot = new Robot();
Rectangle screenRect = new Rectangle(screenSize);
// 捕获屏幕
BufferedImage screenCapture = robot.createScreenCapture(screenRect);
//存放位置
String path = ClassUtils.getDefaultClassLoader().getResource("static").getPath();
fileName = System.currentTimeMillis() + ".png" ;
LOGGER.info("屏幕截屏路径:{}", path);
// 把捕获到的屏幕 输出为 图片
ImageIO.write(screenCapture, "png", new File(path +File.separator + fileName));
} catch (Exception e) {
LOGGER.error("获取屏幕截屏发生异常", e);
}
return fileName ;
}
~~~
### 服务端推送
方式一:自己拼接返回参数值
~~~java
// 服务端推送 返回结果必须以 data: 开始
public static final String SSE_RETURN_START = "data:" ;
// // 服务端推送 返回结果必须以 \n\n 结束
public static final String SSE_RETURN_END = "\n\n" ;
// 服务端推送SSE技术 内容格式必须是 text/event-stream
@GetMapping(value = {"","/"}, produces = "text/event-stream;charset=UTF-8")
public String index() {
String capture = CaptureScreenUtils.capture();
// 返回的内容 必须以 data: 开头,以 \n\n 结尾,不然前端JS处始终不会进入到 onmessage 方法内
return SSE_RETURN_START + capture + SSE_RETURN_END ;
}
~~~
方式二:使用Spring MVC已经封装的对象SseEmitter 来实现
~~~java
@GetMapping("/index")
public SseEmitter sse() {
String capture = CaptureScreenUtils.capture();
SseEmitter emitter = new SseEmitter(100L) ;
try {
emitter.send(capture);
} catch (IOException e) {
e.printStackTrace();
}
return emitter;
}
~~~
实际是封装的内部拼接的头尾
~~~java
// org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilderImpl
@Override
public SseEventBuilder data(Object object, @Nullable MediaType mediaType) {
append("data:");
saveAppendedText();
this.dataToSend.add(new DataWithMediaType(object, mediaType));
append("\n");
return this;
}
~~~
**前端页面JS**
~~~javascript
var source = new EventSource("/sse/index");
source.onmessage = function(event){
console.log('onmessage');
console.log(event.data);
document.getElementById('captureImgId').src = event.data;
};
~~~
## spring-boot-learn-websocket
采用全注解方式实现 web socket聊天
第一步:首先引入websocket的依赖
~~~xml
org.springframework.boot
spring-boot-starter-websocket
~~~
第二步:采用注解方式实现websocket服务端
~~~java
package com.tianya.springboot.websocket.server;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.tianya.springboot.websocket.handle.AppIdMsgHandle;
import lombok.extern.slf4j.Slf4j;
/**
* @description
* WebSocket 服务端
* 每种应用下,对应的会话连接
* key: appId 不同应用,
* 如:
* msg对应消息推送,
* log对应日志推送等,
* chat对应聊天信息推送
* @author TianwYam
* @date 2020年9月17日下午1:47:22
*/
@Slf4j
@Service
@ServerEndpoint(value = "/ws/{appId}")
public class WebSocketServer {
// 自动注入消息处理的实现
// @Autowired 在这儿自动注入会报空指针异常,取不到
private static Set appIdMsgHandles ;
/*** 每种应用下,对应的会话连接 */
private static final ConcurrentHashMap> APPID_CONNECT_SESSION_MAP = new ConcurrentHashMap<>();
/**
* @description
* session 连接 开始
* @author TianwYam
* @date 2020年9月17日下午2:27:12
* @param session 会话连接
* @param appId 应用ID
*/
@OnOpen
public void open(Session session, @PathParam(value = "appId") String appId) {
// 获取此应用下所有连接
LinkedHashSet sessions = getSessions(appId);
if (sessions == null) {
sessions = new LinkedHashSet<>();
APPID_CONNECT_SESSION_MAP.put(appId, sessions);
}
// 新会话session 连接成功
sessions.add(session);
log.info("新连接进入,目前[{}]总人数:{}", appId, sessions.size());
}
/**
* @description
* session 连接关闭
* @author TianwYam
* @date 2020年9月17日下午3:32:45
* @param session
* @param appId
*/
@OnClose
public void close(Session session, @PathParam(value = "appId") String appId) {
// 获取此应用下所有连接
LinkedHashSet sessions = getSessions(appId);
if (sessions != null) {
// 会话session 连接断开
sessions.remove(session);
log.info("连接断开,目前[{}]总人数:{}", appId, sessions.size());
}
}
/**
* @description
* 接受消息
* @author TianwYam
* @date 2020年9月17日下午3:39:22
* @param message 消息内容
* @param session 会话
* @param appId 应用
*/
@OnMessage
public void onMessage(String message, Session session, @PathParam(value = "appId") String appId) {
// 消息处理
for (AppIdMsgHandle appIdMsgHandle : appIdMsgHandles) {
if (Objects.equals(appIdMsgHandle.getAppId(), appId)) {
appIdMsgHandle.handleMsg(message, session);
}
}
}
/**
* @description
* 连接 报错
* @author TianwYam
* @date 2020年9月17日下午3:50:12
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("websocket发生错误", error);
}
/**
* @description
* 根据应用获取所有在线人员
* @author TianwYam
* @date 2020年9月17日下午3:17:04
* @param appId 应用ID
* @return
*/
public static LinkedHashSet getSessions(String appId){
return APPID_CONNECT_SESSION_MAP.get(appId);
}
/**
* @description
* 发送文本消息
* @author TianwYam
* @date 2020年9月17日下午3:52:52
* @param session
* @param message
*/
public static void sendTxtMsg(Session session , String message) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
log.error("websocket发生消息失败", e);
}
}
/**
* @description
*
* WebSocket是线程安全的,有用户连接时就会创建一个新的端点实例,一个端点只能保证一个线程调用。总结就是,WebSocket是多例的。
* Autowired 是程序启动时就注入进去
* 用静态变量来保存消息处理实现类,避免出现空指针异常
* 另一种方式就是 使用 ApplicationContext.getBean()的方式也可以获取
*
* @author TianwYam
* @date 2020年9月17日下午7:43:13
* @param appIdMsgHandles
*/
@Autowired
public void setAppIdMsgHandles(Set appIdMsgHandles) {
WebSocketServer.appIdMsgHandles = appIdMsgHandles;
}
}
~~~
第三步:界面websocket客户端
~~~javascript
$(function() {
var websocket;
// 首先判断是否 支持 WebSocket
if('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/ws/chat");
} else if('MozWebSocket' in window) {
websocket = new MozWebSocket("ws://localhost:8080/ws/chat");
} else {
websocket = new SockJS("http://localhost:8080/ws/chat");
}
// 打开时
websocket.onopen = function(event) {
console.log(" websocket.onopen ");
console.log(event);
$("#msg").append("(欢迎加入聊天群)
");
};
// 处理消息时
websocket.onmessage = function(event) {
console.log(event);
$("#msg").append("(" + event.data + ")
");
console.log(" websocket.onmessage ");
};
websocket.onerror = function(event) {
console.log(" websocket.onerror ");
};
websocket.onclose = function(event) {
console.log(" websocket.onclose ");
};
// 点击了发送消息按钮的响应事件
$("#TXBTN").click(function(){
// 获取消息内容
var text = $("#tx").val();
// 判断
if(text == null || text == ""){
alert(" content can not empty!!");
return false;
}
// 发送消息
websocket.send(text);
$("#tx").val('');
});
});
~~~
html界面
~~~html
WebSocket聊天
~~~
## spring-boot-learn-validation
针对项目做参数校验
采用:
- spring validation
- hibernate validator
第一步:做全局异常处理:
~~~java
/**
* @description
* 全局异常校验处理
* @author TianwYam
* @date 2020年12月25日下午7:22:47
*/
@RestControllerAdvice
public class GlobalValidExceptionHandler {
/**
* @description
* 类绑定异常
* @author TianwYam
* @date 2020年12月28日下午5:14:27
* @param e
* @return
*/
@ExceptionHandler(ConstraintViolationException.class)
public ResultUtis validBeanException(ConstraintViolationException e) {
String errorMsg = "" ;
Set> constraintViolations = e.getConstraintViolations();
for (ConstraintViolation> constraintViolation : constraintViolations) {
errorMsg = constraintViolation.getMessage();
break;
}
return ResultUtis.error(6101, errorMsg);
}
/**
* @description
* 方法参数校验异常
* @author TianwYam
* @date 2020年12月28日下午5:16:02
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultUtis validMethodException(MethodArgumentNotValidException e) {
String errorMsg = "" ;
List allErrors = e.getBindingResult().getAllErrors();
for (ObjectError objectError : allErrors) {
errorMsg = objectError.getDefaultMessage();
break;
}
return ResultUtis.error(6102, errorMsg);
}
/**
* @description
* 参数绑定异常
* @author TianwYam
* @date 2020年12月28日下午5:17:11
* @param e
* @return
*/
@ExceptionHandler(BindException.class)
public ResultUtis validBindException(BindException e) {
String errorMsg = "" ;
List allErrors = e.getBindingResult().getAllErrors();
for (ObjectError objectError : allErrors) {
errorMsg = objectError.getDefaultMessage();
break;
}
return ResultUtis.error(6103, errorMsg);
}
@ExceptionHandler(Exception.class)
public ResultUtis validException(Exception e) {
return ResultUtis.error(ResultCode.SERVER_ERROR);
}
}
~~~
第二步:在对应参数类、参数值变量等上面加校验注解
~~~java
@Data
@Builder
public class Student {
@NotBlank(message = "学号不能为空", groups = {ValidType.Update.class, ValidType.Delete.class})
private int id ;
@NotBlank(message = "学生姓名不可以为空", groups = {ValidType.Insert.class})
private String name ;
private String addr ;
@Range(max = 300, message = "年龄不能超过300")
private int age ;
private int height ;
private String phone ;
private String idcard ;
@Email(message = "邮箱格式错误")
private String email ;
}
~~~
第三步:在对应调用处添加验证注解 @Validated 或者 @Valid
~~~java
@PostMapping("/")
public ResultUtis add(
@RequestBody
@Validated(ValidType.Insert.class)
Student student) {
return ResultUtis.success(student);
}
~~~
注意:
分组校验时采用 @Validated
嵌套校验时,子校验采用 @Valid
自定义分组:
~~~java
public interface ValidType {
// 若是不继承 Default,则分组校验只会校验 相应加了 update组检验的属性才能校验,没有添加的,是不会校验的
interface Update extends Default{}
interface Selete extends Default{}
interface Delete extends Default{}
interface Insert extends Default{}
}
~~~
注意:若是不继承 Default,则分组校验只会校验 相应加了 update组检验的属性才能校验,没有添加的,是不会校验的
## spring-boot-learn-feign
spring boot中使用feign,实现请求第三方接口,作为一个 http请求工具
分为两种使用方式:第一种,在springboot环境下;第二种,不在spring环境下
### 在springboot环境下
第一步,添加maven配置,引入JAR包,pom.xml文件
~~~xml
org.springframework.cloud
spring-cloud-starter-openfeign
2.0.2.RELEASE
~~~
第二步,编写feign客户端,相当于实际请求接口
~~~java
@FeignClient(name = "studentFeignClient", url = "${student.feign.url}")
public interface StudentFeignClient {
@GetMapping("/list")
public ResultUtis getList() ;
}
~~~
第三步,注册到启动类上
~~~java
// 开启 feign
@EnableFeignClients
@SpringBootApplication
public class FeignLearnApplication {
public static void main(String[] args) {
SpringApplication.run(FeignLearnApplication.class, args);
}
}
~~~
第四步,实际调用
~~~java
@RestController
@RequestMapping("/student")
public class StudentController {
// 实际调用,自动注入
@Autowired
private StudentFeignClient studentFeignClient ;
@GetMapping("/list")
public ResultUtis getList() {
return studentFeignClient.getList();
}
}
~~~
### 不在spring环境下
第一步,配置maven,添加JAR包,pom.xml
~~~xml
io.github.openfeign
feign-core
${feign.version}
io.github.openfeign
feign-gson
${feign.version}
io.github.openfeign
feign-jackson
${feign.version}
io.github.openfeign
feign-httpclient
${feign.version}
io.github.openfeign
feign-okhttp
${feign.version}
io.github.openfeign
feign-ribbon
${feign.version}
~~~
第二步,编写feign客户端,实际访问请求接口
~~~java
/**
* @description
* 不在spring环境下的 feign 接口定义
* @author TianwYam
* @date 2020年12月25日下午6:25:42
*/
public interface StudentFeignClientNoSpring {
// 不使用spring环境下的,只能使用 feign自带的 RequestLine 来声明 方法 请求 类型
@RequestLine("GET /list")
public ResultUtis getList() ;
}
~~~
第三步,调用方式
~~~java
/**
* @description
* 在不是 spring环境下 、 springboot环境下的 使用feign
* @author TianwYam
* @date 2020年12月25日下午6:17:56
*/
public class FeignNoSpringMain {
public static void main(String[] args) {
StudentFeignClientNoSpring studentFeignClient = Feign.builder()
.decoder(new GsonDecoder())
.target(StudentFeignClientNoSpring.class, "http://localhost:8081/api/v1/student");
ResultUtis list = studentFeignClient.getList();
System.out.println(JSON.toJSONString(list, SerializerFeature.PrettyFormat));
}
}
~~~
此处写成main方式,实际项目中,可以写成一个工具类
~~~java
/**
* @description
* Feign工具类
* @author TianwYam
* @date 2021年1月30日上午11:24:21
*/
public class FeignClientUtils {
/**
* @description
* 获取 feign客户端
* @author TianwYam
* @date 2021年1月30日上午11:23:34
* @param 客户端类
* @param url 调用客户端请求的URL
* @param clazz 客户端 class
* @return
*/
public static T getClient(String url, Class clazz) {
return Feign.builder()
.decoder(new GsonDecoder())
.encoder(new GsonEncoder())
.target(clazz, url);
}
}
~~~
## spring-boot-learn-javafx
使用JavaFX集成spring boot简单编写了一个GUI客户端,实现上传本地JAR到私服仓库
maven pom.xml
~~~xml
de.roskenet
springboot-javafx-support
2.1.6
com.zenjava
javafx-maven-plugin
com.tianya.springboot.javafx.LearnJavaFXApplication
tianwyam
~~~
### 上传本地JAR到私服仓库
主要命令:
~~~powershell
cmd /c mvn -s maven本地的/config/setting.xml deploy:deploy-file -Durl=私服仓库地址 -DrepositoryId=私服仓库ID -DgeneratePom=false -Dpackaging=jar -Dfile=jar包文件地址 -Dsources=源jar包地址 -Djavadoc=doc包地址
~~~
使用 Java调用本地命令
~~~java
Runtime CMD = Runtime.getRuntime();
Process proc = CMD.exec(命令);
~~~
## spring-boot-learn-mockito
单元测试
### springboot 集成的 test 进行单元测试
第一步:maven配置引入依赖
~~~xml
org.springframework.boot
spring-boot-starter-test
~~~
第二步:编写测试用例
~~~java
package com.tianya.springboot.learntest.test.service;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.alibaba.fastjson.JSON;
import com.tianya.springboot.learntest.entity.UserBean;
import com.tianya.springboot.learntest.service.UserService;
/**
* @description
* 第一种方式:spring boot 测试方式
* 它可以模拟springboot启动整个容器
* @author TianwYam
* @date 2021年8月22日下午6:19:44
*/
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testGetAll() {
List users = userService.getUsers();
System.out.println(JSON.toJSONString(users, true));
}
}
~~~
这种方式需要启动整个项目spring容器运行
## spring-boot-learn-elasticsearch
对 elasticsearch操作
### 一、使用spring-data方式进行操作elasticsearch
1.添加依赖
~~~xml
org.springframework.boot
spring-boot-starter-data-elasticsearch
~~~
2.添加ES配置
~~~yml
#使用模板方式
spring:
elasticsearch:
rest:
uris:
- http://localhost:9200
~~~
3.添加实体类
~~~java
package com.tianya.springboot.es.entity;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "people_index", type = "people")
public class PeopleBean {
/**
* 主键
*/
@Id
private Long uid ;
/**
* 姓名
*/
@Field
private String name ;
/**
* 年龄
*/
@Field
private int age ;
/**
* 地址
*/
@Field
private String addr ;
/**
* 生日
*/
@Field
private String birthDay ;
/**
* 职业
*/
@Field
private String professional ;
/**
* 兴趣
*/
@Field
private String interest ;
}
~~~
4.添加repository
~~~java
package com.tianya.springboot.es.dao;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import com.tianya.springboot.es.entity.PeopleBean;
@Repository
public interface PeopleEsDao extends ElasticsearchRepository{
}
~~~
准备操作都做完了,开始进行对elasticsearch操作了
#### 1.1 新增
~~~java
@SpringBootTest
public class PeopleTest {
@Autowired
private PeopleEsDao peopleEsDao ;
@Test
public void insert() {
List peopleBeanList = new ArrayList<>();
peopleBeanList.add(PeopleBean.builder().uid(1L).name("吴彦祖").age(45).birthDay("1977-01-01").addr("香港").professional("演员").interest("赛车").build());
peopleBeanList.add(PeopleBean.builder().uid(2L).name("吴奇隆").age(55).birthDay("1967-01-01").addr("大陆").professional("歌手").interest("演戏").build());
peopleBeanList.add(PeopleBean.builder().uid(3L).name("吴京").age(45).birthDay("1977-01-01").addr("大陆").professional("武打演员").interest("武术").build());
peopleBeanList.add(PeopleBean.builder().uid(4L).name("古天乐").age(55).birthDay("1967-01-01").addr("香港").professional("演员").interest("唱歌").build());
peopleBeanList.add(PeopleBean.builder().uid(5L).name("苏炳添").age(35).birthDay("1987-01-01").addr("大陆").professional("运动员").interest("跑步").build());
peopleBeanList.add(PeopleBean.builder().uid(6L).name("刘亦菲").age(30).birthDay("1992-01-01").addr("大陆").professional("歌手").interest("演戏").build());
peopleBeanList.add(PeopleBean.builder().uid(7L).name("刘翔").age(30).birthDay("1992-01-01").addr("大陆").professional("运动员").interest("健身").build());
peopleBeanList.add(PeopleBean.builder().uid(8L).name("张家辉").age(45).birthDay("1977-01-01").addr("香港").professional("演员").interest("唱歌").build());
peopleBeanList.add(PeopleBean.builder().uid(9L).name("毛不易").age(45).birthDay("1977-01-01").addr("大陆").professional("歌手").interest("唱歌").build());
// 新增
Iterable saveResult = peopleEsDao.saveAll(peopleBeanList);
System.out.println(JSON.toJSONString(saveResult, true));
}
}
~~~
结果:
~~~json
[
{
"addr":"香港",
"age":45,
"birthDay":"1977-01-01",
"interest":"赛车",
"name":"吴彦祖",
"professional":"演员",
"uid":1
},
{
"addr":"大陆",
"age":55,
"birthDay":"1967-01-01",
"interest":"演戏",
"name":"吴奇隆",
"professional":"歌手",
"uid":2
},
{
"addr":"大陆",
"age":45,
"birthDay":"1977-01-01",
"interest":"武术",
"name":"吴京",
"professional":"武打演员",
"uid":3
},
{
"addr":"香港",
"age":55,
"birthDay":"1967-01-01",
"interest":"唱歌",
"name":"古天乐",
"professional":"演员",
"uid":4
},
{
"addr":"大陆",
"age":35,
"birthDay":"1987-01-01",
"interest":"跑步",
"name":"苏炳添",
"professional":"运动员",
"uid":5
},
{
"addr":"大陆",
"age":30,
"birthDay":"1992-01-01",
"interest":"演戏",
"name":"刘亦菲",
"professional":"歌手",
"uid":6
},
{
"addr":"大陆",
"age":30,
"birthDay":"1992-01-01",
"interest":"健身",
"name":"刘翔",
"professional":"运动员",
"uid":7
},
{
"addr":"香港",
"age":45,
"birthDay":"1977-01-01",
"interest":"唱歌",
"name":"张家辉",
"professional":"演员",
"uid":8
},
{
"addr":"大陆",
"age":45,
"birthDay":"1977-01-01",
"interest":"唱歌",
"name":"毛不易",
"professional":"歌手",
"uid":9
}
]
~~~
#### 1.2 查询-所有
~~~java
@Test
public void get() {
// 查询所有
Iterable peopleList = peopleEsDao.findAll();
System.out.println(JSON.toJSONString(peopleList, true));
}
~~~
类似SQL:
~~~sql
SELECT * FROM people
~~~
#### 1.3 查询-排序
~~~java
@Test
public void order4Get() {
// 按照uid进行排序
Sort orderSort = Sort.by(Order.asc("uid"));
Iterable sortPeopleList = peopleEsDao.findAll(orderSort);
System.out.println(JSON.toJSONString(sortPeopleList, true));
}
~~~
可以按照多个字段进行排序
public static Sort by(Order... orders) ;
类似SQL:
~~~sql
SELECT * FROM people order by uid
~~~
#### 1.4 分页查询
~~~java
@Test
public void page4Get() {
PageRequest page = PageRequest.of(0, 5, Sort.by(Order.asc("uid")));
// 分页查询
Page pagePeopleList = peopleEsDao.findAll(page);
System.out.println(JSON.toJSONString(pagePeopleList, true));
}
~~~
类似SQL:
~~~sql
SELECT * FROM people order by uid limit 0,5
~~~
结果:
~~~json
{
"content":[
{
"addr":"香港",
"age":45,
"birthDay":"1977-01-01",
"interest":"赛车",
"name":"吴彦祖",
"professional":"演员",
"uid":1
},
{
"addr":"大陆",
"age":55,
"birthDay":"1967-01-01",
"interest":"唱歌",
"name":"吴奇隆",
"professional":"演员",
"uid":2
},
{
"addr":"大陆",
"age":45,
"birthDay":"1977-01-01",
"interest":"武术",
"name":"吴京",
"professional":"演员",
"uid":3
},
{
"addr":"香港",
"age":55,
"birthDay":"1967-01-01",
"interest":"唱歌",
"name":"古天乐",
"professional":"演员",
"uid":4
},
{
"addr":"大陆",
"age":35,
"birthDay":"1987-01-01",
"interest":"跑步",
"name":"苏炳添",
"professional":"运动员",
"uid":5
}
],
"empty":false,
"facets":[],
"first":true,
"last":false,
"maxScore":null,
"number":0,
"numberOfElements":5,
"pageable":{
"offset":0,
"pageNumber":0,
"pageSize":5,
"paged":true,
"sort":{
"empty":false,
"sorted":true,
"unsorted":false
},
"unpaged":false
},
"size":5,
"sort":{"$ref":"$.pageable.sort"},
"totalElements":8,
"totalPages":2
}
~~~
#### 1.5 搜索-等值查询-Term
~~~java
@Test
public void search() {
TermQueryBuilder ageQuery = QueryBuilders.termQuery("age", 45);
// age = 45
Iterable search = peopleEsDao.search(ageQuery);
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
类似SQL:
~~~sql
SELECT * FROM people WHERE age = 45
~~~
结果:
~~~json
[
{
"addr":"香港",
"age":45,
"birthDay":"1977-01-01",
"interest":"唱歌",
"name":"张家辉",
"professional":"演员",
"uid":8
},
{
"addr":"香港",
"age":45,
"birthDay":"1977-01-01",
"interest":"赛车",
"name":"吴彦祖",
"professional":"演员",
"uid":1
},
{
"addr":"大陆",
"age":45,
"birthDay":"1977-01-01",
"interest":"武术",
"name":"吴京",
"professional":"演员",
"uid":3
}
]
~~~
若是查询中文,需要添加 keyword ,term 是指分词后的 = 查询
~~~java
@Test
public void searchChinese() {
// 添加 keyword 的目的是 以后面查询的值整体查询,不要分词,不然查询不到
TermQueryBuilder termQuery = QueryBuilders.termQuery("addr.keyword", "大陆");
System.out.println("参数:");
System.out.println(termQuery.toString(true));
Iterable search = peopleEsDao.search(termQuery);
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
#### 1.6 搜索-多条件
~~~java
@Test
public void searchMore() {
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("addr.keyword", "大陆"))
.filter(QueryBuilders.termQuery("age", 45));
System.out.println("参数:");
System.out.println(boolQuery.toString(true));
Iterable search = peopleEsDao.search(boolQuery);
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
类似SQL:
~~~sql
SELECT * FROM people WHERE addr = '大陆' AND age = 45
~~~
结果:
~~~json
{
"addr":"大陆",
"age":45,
"birthDay":"1977-01-01",
"interest":"武术",
"name":"吴京",
"professional":"演员",
"uid":3
}
~~~
#### 1.7 搜索-范围查询
~~~java
@Test
public void searchBetweenAnd() {
// >=
// RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age").gte(55);
// between and
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("age").from(55).to(66);
System.out.println("参数:");
System.out.println(rangeQuery.toString(true));
Iterable search = peopleEsDao.search(rangeQuery);
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
类似SQL:
~~~sql
SELECT * FROM people WHERE age BETWEEN 55 AND 66
~~~
gte >=
gt >
lt <
lte <=
from ... to ... 类似 between and
#### 1.8 搜索-分页
~~~java
@Test
public void searchSort() {
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("professional.keyword", "演员"))
.filter(QueryBuilders.termQuery("age", 55));
System.out.println("参数:");
System.out.println(queryBuilder.toString(true));
// 分页排序
PageRequest page = PageRequest.of(0, 5, Sort.by(Order.desc("age"), Order.asc("uid")));
// 搜索分页查询
Iterable search = peopleEsDao.search(queryBuilder, page);
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
类似SQL:
~~~sql
SELECT
*
FROM people
WHERE professional = '演员'
AND age = 55
ORDER BY age desc, uid asc
limit 0,5
~~~
结果:
~~~json
[
{
"addr":"大陆",
"age":55,
"birthDay":"1967-01-01",
"interest":"唱歌",
"name":"吴奇隆",
"professional":"演员",
"uid":2
},
{
"addr":"香港",
"age":55,
"birthDay":"1967-01-01",
"interest":"唱歌",
"name":"古天乐",
"professional":"演员",
"uid":4
}
]
~~~
#### 1.9 搜索-组合查询-特定输出字段
~~~java
@Test
public void searchQuery() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.withFields("uid","name","age","professional")
.withFilter(QueryBuilders.termQuery("professional.keyword", "演员"))
.withQuery(QueryBuilders.rangeQuery("age").gte(45))
.withSort(SortBuilders.fieldSort("uid"))
.withPageable(PageRequest.of(0, 3));
Iterable search = peopleEsDao.search(queryBuilder.build());
System.out.println("结果:");
System.out.println(JSON.toJSONString(search, true));
}
~~~
类似SQL:
~~~sql
select
uid,name,age,professional
from people
where professional = '演员'
and age >= 45
order by uid
limit 0,3
~~~
结果:
~~~json
[
{
"age":45,
"name":"吴彦祖",
"professional":"演员",
"uid":1
},
{
"age":55,
"name":"吴奇隆",
"professional":"演员",
"uid":2
},
{
"age":45,
"name":"吴京",
"professional":"演员",
"uid":3
}
]
~~~
#### 1.10 搜索-分组
~~~java
@Test
public void searchAggr() {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
.addAggregation(AggregationBuilders.terms("ageCount")
.field("age")
.size(100)
.subAggregation(AggregationBuilders.terms("professionalCount").field("professional.keyword")
.subAggregation(AggregationBuilders.topHits("fieldNms")
.fetchSource(new String[] {"uid","age","professional","name"}, null))));
// 分组聚合分页结果
AggregatedPage peopleAggrPage = (AggregatedPage)peopleEsDao.search(queryBuilder.build());
System.out.println("结果:");
ParsedLongTerms ageAggr = (ParsedLongTerms)peopleAggrPage.getAggregation("ageCount");
List extends Bucket> buckets = ageAggr.getBuckets();
for (Bucket bucket : buckets) {
Number age = bucket.getKeyAsNumber();
long docCount = bucket.getDocCount();
System.out.println("年龄:"+age + " 总数:" + docCount);
Aggregations aggregations = bucket.getAggregations();
for (Aggregation aggregation : aggregations) {
ParsedStringTerms profAggr = (ParsedStringTerms) aggregation;
List extends Bucket> profBuckets = profAggr.getBuckets();
for (Bucket bucket2 : profBuckets) {
long profCount = bucket2.getDocCount();
String prof = bucket2.getKeyAsString();
System.out.println("\t职业:"+prof+" 总数:"+profCount);
Aggregations filedNmAggrs = bucket2.getAggregations();
for (Aggregation filedNmAggr : filedNmAggrs) {
ParsedTopHits topHits = (ParsedTopHits) filedNmAggr ;
SearchHits hits = topHits.getHits();
for (SearchHit hit : hits) {
String source = hit.getSourceAsString();
System.out.println("\t\t" + JSON.parseObject(source, PeopleBean.class));
}
}
}
}
}
}
~~~
类似SQL:
~~~sql
select * from people
group by age, professional
~~~
### 二、使用client方式操作ES
依赖还是跟上面一样,配置也是
查询所有:
~~~java
/**
* @Description:
* 使用 client方式
* @author: TianwYam
* @date 2022年3月23日 下午9:47:23
*/
@SpringBootTest
public class PeopleTestUseClient {
@Autowired
private RestHighLevelClient esClient ;
@Test
public void getAll() throws Exception {
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
System.out.println("参数:");
System.out.println(sourceBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
}
~~~
#### 2.1 等值查询 =
~~~java
@Test
public void where() throws Exception {
// 类似:select * from people where age = 45
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termQuery("age", 45));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select * from people where age = 45
~~~
URL查询:
~~~json
{
"query":{
"term":{
"age":{
"boost":1.0,
"value":45
}
}
}
}
~~~
#### 2.2 多值查询 in
~~~java
@Test
public void in() throws Exception {
// 类似:select * from people where professional in ('演员', '歌手')
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.termsQuery("professional.keyword", Arrays.asList("演员","歌手")));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select * from people where professional in ('演员', '歌手')
~~~
SDL:
~~~json
{
"query":{
"terms":{
"professional.keyword":[
"演员",
"歌手"
],
"boost":1.0
}
}
}
~~~
#### 2.3 范围查询
~~~java
@Test
public void betweenAnd() throws Exception {
// 类似:select * from people where age between 50 and 55
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// sourceBuilder.query(QueryBuilders.rangeQuery("age").from(50).to(55));
// 50 < age <= 55
sourceBuilder.query(QueryBuilders.rangeQuery("age").gt(50).lte(55));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select * from people where age between 50 and 55
~~~
SDL:
~~~json
{
"query":{
"range":{
"age":{
"include_lower":false,
"include_upper":true,
"from":50,
"boost":1.0,
"to":55
}
}
}
}
~~~
#### 2.4 模糊查询
~~~java
@Test
public void likeLeft() throws Exception {
// 类似:select * from people where professional like '武打%'
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(QueryBuilders.prefixQuery("professional.keyword", "武打"));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select * from people where professional like '武打%'
~~~
SDL:
~~~json
{
"query":{
"prefix":{
"professional.keyword":{
"boost":1.0,
"value":"武打"
}
}
}
}
~~~
#### 2.5 通配符模糊查询
~~~java
@Test
public void likeAny() throws Exception {
// 类似:select * from people where professional like '%打%员'
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
// 通配符模糊查询
sourceBuilder.query(QueryBuilders.wildcardQuery("professional.keyword", "*打*员"));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select * from people where professional like '%打%员'
~~~
SDL:
~~~json
{
"query":{
"wildcard":{
"professional.keyword":{
"boost":1.0,
"wildcard":"*打*员"
}
}
}
}
~~~
#### 2.6 统计查询(最大、最小、平均、求和)
~~~java
@Test
public void max() throws Exception {
// 类似:select max(age) as ageMax from people
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
sourceBuilder.aggregation(AggregationBuilders.max("ageMax").field("age"));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select max(age) as ageMax from people
~~~
SDL:
~~~json
{
"size":0,
"aggregations":{
"ageMax":{
"max":{
"field":"age"
}
}
}
}
~~~
#### 2.7 去重
~~~java
@Test
public void distinct() throws Exception {
// 类似:select count(distinct uid) as uidCount from people
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
sourceBuilder.aggregation(AggregationBuilders.cardinality("uidCount").field("uid"));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select count(distinct uid) as uidCount from people
~~~
SDL:
~~~json
{
"size":0,
"aggregations":{
"uidCount":{
"cardinality":{
"field":"uid"
}
}
}
}
~~~
#### 2.8 分组聚合
~~~java
@Test
public void groupBy() throws Exception {
// 类似:select age, count(*) from people group by age
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
sourceBuilder.aggregation(AggregationBuilders.terms("ageGroup").field("age"));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
l类似SQL:
~~~sql
select age, count(*) from people group by age
~~~
SDL:
~~~json
{
"size":0,
"aggregations":{
"ageGroup":{
"terms":{
"shard_min_doc_count":0,
"field":"age",
"size":10,
"show_term_doc_count_error":false,
"min_doc_count":1,
"order":[
{
"_count":"desc"
},
{
"_key":"asc"
}
]
}
}
}
}
~~~
#### 2.9 分组排序
~~~java
@Test
public void groupByOrder() throws Exception {
// 类似:select age as key , count(*) as count from people group by age order by count asc, key desc
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
sourceBuilder.aggregation(AggregationBuilders.terms("ageGroup")
.field("age")
.order(BucketOrder.compound(BucketOrder.count(true), BucketOrder.key(false))));
// sourceBuilder.aggregation(AggregationBuilders.terms("ageGroup").field("age").order(BucketOrder.key(false)));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(JSON.toJSONString(response, true));
}
~~~
类似SQL:
~~~sql
select age as key , count(*) as count
from people group by age
order by count asc, key desc
~~~
SDL:
~~~json
{
"size":0,
"aggregations":{
"ageGroup":{
"terms":{
"shard_min_doc_count":0,
"field":"age",
"size":10,
"show_term_doc_count_error":false,
"min_doc_count":1,
"order":[
{
"_count":"asc"
},
{
"_key":"desc"
}
]
}
}
}
}
~~~
#### 2.10 多值聚合
~~~java
@Test
public void groupByMore() throws Exception {
// 类似:select age , professional, count(*) as count from people group by age, professional
SearchRequest searchRequest = new SearchRequest("people_index");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.size(0);
sourceBuilder.aggregation(AggregationBuilders.terms("ageGroup")
.field("age")
.subAggregation(AggregationBuilders.terms("profGroup")
.field("professional.keyword")));
// sourceBuilder.aggregation(AggregationBuilders.terms("ageGroup").field("age").order(BucketOrder.key(false)));
System.out.println("参数:");
System.out.println(JSON.toJSONString(JSON.parse(sourceBuilder.toString()), true));
searchRequest.source(sourceBuilder);
SearchResponse response = esClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println("结果:");
System.out.println(response);
// System.out.println(JSON.toJSONString(response, true));
}
~~~
l类似SQL:
~~~sql
select age , professional, count(*) as count
from people
group by age, professional
~~~
SDL:
~~~json
{
"size":0,
"aggregations":{
"ageGroup":{
"terms":{
"shard_min_doc_count":0,
"field":"age",
"size":10,
"show_term_doc_count_error":false,
"min_doc_count":1,
"order":[
{
"_count":"desc"
},
{
"_key":"asc"
}
]
},
"aggregations":{
"profGroup":{
"terms":{
"shard_min_doc_count":0,
"field":"professional.keyword",
"size":10,
"show_term_doc_count_error":false,
"min_doc_count":1,
"order":[
{
"_count":"desc"
},
{
"_key":"asc"
}
]
}
}
}
}
}
}
~~~