[spring] Spring Boot REST API - 项目实现
Spring Boot REST API - 项目实现
书接上文 Spring Boot REST API - CRUD 操作,一些和数据库相关联的注解在 [spring] spring jpa - hibernate CRUD
主要的 layer 如下:
项目配置
项目开始前的准备
spring 配置
Spring 依旧是从 https://start.spring.io/ 上下载的,具体配置如下:
properties 文件更新如下:
spring.datasource.url=jdbc:mysql://localhost:3306/employee_directory
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
DB 配置
用的是 mysql,具体跑的脚本如下:
CREATE DATABASE IF NOT EXISTS `employee_directory`;
USE `employee_directory`;--
-- Table structure for table `employee`
--DROP TABLE IF EXISTS `employee`;CREATE TABLE `employee` (`id` int NOT NULL AUTO_INCREMENT,`first_name` varchar(45) DEFAULT NULL,`last_name` varchar(45) DEFAULT NULL,`email` varchar(45) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;--
-- Data for table `employee`
--INSERT INTO `employee` VALUES(1,'Leslie','Andrews','leslie@google.com'),(2,'Emma','Baumgarten','emma@google.com'),(3,'Avani','Gupta','avani@google.com'),(4,'Yuri','Petrov','yuri@google.com'),(5,'Juan','Vega','juan@google.com');
使用 docker 运行 mysql
最近卸载掉了一些服务然后移到了 docker 上跑景象,发现方便了不少,下面贴一下用 docker 运行 mysql 的指令
# 下载最新的 mysql 镜像
❯ docker pull mysql:latest
# 运行镜像启动容器
❯ docker run --name mysql-container -e MYSQL_ROOT_PASSWORD=yourpassword -p 3306:3306 -d mysql:latest# 查看正在运行的 docker container
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec2f40d48498 mysql "docker-entrypoint.s…" 3 days ago Up 3 days 8080/tcp, 0.0.0.0:3306->3306/tcp, 33060/tcp nice_kirch# 可以查看 3306 是否被使用
# 正常来说上面的 PORTS 没问题就行了
# 我这里跑出来其实有一大堆的结果,因为 spring 跑起来了,也在和 3306 进行沟通,所以 spring 的 process 也会在这个列表中
# 如果刚刚启动了 docker,应该只有一条 docker 的 process 在使用 3306
❯ lsof -i :3306COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
com.docke 1642 _____ 144u IPv6 0xf1d60cef5e02672d 0t0 TCP localhost:mysql->localhost:55086 (ESTABLISHED)
com.docke 1642 _____ 642u IPv6 0xf1d60cef66beb72d 0t0 TCP *:mysql (LISTEN)# 复制 sql 文件到 container 里
❯ docker cp <file_name> <container_name>:<path_name>
完成后使用 mysql 运行 sql 文件即可
CRUD 实现
这里就先用比较传统的实现
entity
@Data
@NoArgsConstructor
@RequiredArgsConstructor
@Entity
@Table(name = "employee")
public class Employee {// define fields@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id")private int id;@NonNull// must be the table name@Column(name = "first_name")private String firstName;@NonNull@Column(name = "last_name")private String lastName;@NonNull@Column(name = "email")private String email;
}
使用 lombok 省略一些实现,关于其他的注解,都是来自 Hibernate 的部分
dao & dao impl
这里就是比较传统的 DAO+DAOImpl 的实现,依旧是 Hibernate 的东西:
DAO interface:
public interface EmployeeDAO {List<Employee> findAll();
}
DAOImpl 实现:
@Repository
public class EmployeeDAOImpl implements EmployeeDAO {// define field for entityManagerprivate EntityManager entityManager;// setup constructor injection@Autowiredpublic EmployeeDAOImpl(EntityManager entityManager) {this.entityManager = entityManager;}@Overridepublic List<Employee> findAll() {// create a queryTypedQuery<Employee> query = this.entityManager.createQuery("from Employee", Employee.class);// execute query and get result list// and return resultreturn query.getResultList();}
}
rest controller 实现
这里依旧结合了一下 rest api 部分和 hibernate 的实现,后期会进行重构:
@RestController
@RequestMapping("/api")
public class EmployeeRestController {private final EmployeeDAO employeeDAO;public EmployeeRestController(EmployeeDAO employeeDAO) {this.employeeDAO = employeeDAO;}@GetMapping("/employees")public List<Employee> findAll() {return employeeDAO.findAll();}
}
实现效果如下:
service 层
上面的实现是直接通过 controller 和 DAO 层进行沟通,但是忽略了 service 层,这里把 service 层的实现补上
service 层本身是 façade 设计模式,其主要实现的功能就是让 DAO 层专注于实现数据的获取,controller 层专注于 HTTP 的处理,而 service 层则对 business logic 进行处理。由此可以延展出的优点/特性为:
-
对功能的实现进行抽象,将低耦合性
-
实现交易(transaction)管理
-
集中处理 business logic
-
复用性与灵活性
-
提高可维护性与可拓展性
Service - Retrieve
具体实现如下:
-
首先声明一个 service 的 interface
public interface EmployeeService {List<Employee> findAll(); }
-
实现 interface
@Service public class EmployeeServiceImpl implements EmployeeService{private final EmployeeDAO employeeDAO;public EmployeeServiceImpl(EmployeeDAO employeeDAO) {this.employeeDAO = employeeDAO;}@Overridepublic List<Employee> findAll() {return this.employeeDAO.findAll();} }
可以看到,这个实现和 DAO 很像
-
将 controller 中调用的 DAO 替换为 service
@RestController @RequestMapping("/api") public class EmployeeRestController {private final EmployeeService employeeService;public EmployeeRestController(EmployeeService employeeService) {this.employeeService = employeeService;}@GetMapping("/employees")public List<Employee> findAll() {return employeeService.findAll();} }
Service - 剩余 CRUD
这里实现一个根据 id 获取 employee 的功能,主要也是更新 DAO, DAOImpl,Service,ServiceImpl 和 Controller 这个套路,这里就放在一起了。
DAO 更新
public interface EmployeeDAO {List<Employee> findAll();Employee findById(int employeeId);Employee save(Employee employee);void deleteById(int employeeId);
}
DaoImpl 更新
@Repository
public class EmployeeDAOImpl implements EmployeeDAO {// define field for entityManagerprivate EntityManager entityManager;// setup constructor injection@Autowiredpublic EmployeeDAOImpl(EntityManager entityManager) {this.entityManager = entityManager;}@Overridepublic List<Employee> findAll() {// create a queryTypedQuery<Employee> query = this.entityManager.createQuery("from Employee", Employee.class);// execute query and get result list// and return resultreturn query.getResultList();}@Overridepublic Employee findById(int employeeId) {return this.entityManager.find(Employee.class, employeeId);}// No @Transactional here, it'll be handled at service layer@Overridepublic Employee save(Employee employee) {// if id == 0, then insert/save// else, updatereturn this.entityManager.merge(employee);}@Overridepublic void deleteById(int employeeId) {Employee employee = this.findById(employeeId);this.entityManager.remove(employee);}
}
这里几个点需要注意一下:
merge
是根据 id 进行的操作,如果id == 0
,那么实现添加功能,不然就是修改- service 层会接管 transaction,所以 DAO 层不需要添加
@Transactional
注解
service 更新
这里和 DAO 一样,添加新的方法即可——我是直接从 DAO 那里 cv 过来的
public interface EmployeeService {List<Employee> findAll();Employee findById(int employeeId);Employee save(Employee employee);void deleteById(int employeeId);
}
service impl 实现
@Service
public class EmployeeServiceImpl implements EmployeeService{private final EmployeeDAO employeeDAO;public EmployeeServiceImpl(EmployeeDAO employeeDAO) {this.employeeDAO = employeeDAO;}@Overridepublic List<Employee> findAll() {return this.employeeDAO.findAll();}@Overridepublic Employee findById(int employeeId) {return this.employeeDAO.findById(employeeId);}@Override@Transactionalpublic Employee save(Employee employee) {return this.employeeDAO.save(employee);}@Override@Transactionalpublic void deleteById(int employeeId) {this.employeeDAO.deleteById(employeeId);}
}
这里需要注意的就是:service 层中添加了 @Transactional
注解
相当于 business logic 在 service 中进行处理——不过这里的业务比较简单就是了
controller 实现
@RestController
@RequestMapping("/api")
public class EmployeeRestController {private final EmployeeService employeeService;public EmployeeRestController(EmployeeService employeeService) {this.employeeService = employeeService;}@GetMapping("/employees")public List<Employee> findAll() {return employeeService.findAll();}@GetMapping("/employees/{employeeId}")public Employee findById(@PathVariable int employeeId) {Employee employeeFound = this.employeeService.findById(employeeId);if (employeeFound == null) {throw new RuntimeException("Employee id not found - " + employeeId);}return employeeFound;}@PostMapping("/employees")public Employee save(@RequestBody Employee newEmployee) {newEmployee.setId(0);return this.employeeService.save(newEmployee);}@PutMapping("/employees")public Employee updateEmployee(@RequestBody Employee updateEmployee) {return this.employeeService.save(updateEmployee);}@DeleteMapping("/employees/{employeeId}")public String deleteEmployee(@PathVariable int employeeId) {Employee employeeFound = this.employeeService.findById(employeeId);if (employeeFound == null) {throw new RuntimeException("Employee id not found - " + employeeId);}this.employeeService.deleteById(employeeId);return "Deleted employee id - " + employeeId;}
}
效果如下:
实现功能 | 效果展示 |
---|---|
findById | ![]() |
save | ![]() ![]() |
updage | ![]() ![]() |
deleteById | ![]() ![]() ![]() |
目前项目结构如下:
优化
其实可以看到代码实现还是有很多的 boilerplate code,如 DAO/DAOImpl 和 Service/ServiceImpl,这 4 个类里的代码其实高度重合。为了解决这个问题,Spring 也实现了两个库,它们可以提供一些常规的 CRUD 功能的实现,减少 boilerplate code
更新 POM
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-rest</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>
主要是增加这两个依赖
二者都是可拓展,如果想要实现更加个性化的功能,也可以通过实现 @Query
去进行拓展
Spring Data JPA
Sping Data JPA 可以通过实现 JpaRepository
去提供基础 CRUD 操作的支持
实现很简单:
package com.example.demo.dao;import com.example.demo.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
不需要写任何代码,就可以把 DAO 和 DAOImpl 删了,接下来就是修改所有使用 DAO 的地方,将其修改为调用 repository:
@Service
public class EmployeeServiceImpl implements EmployeeService {private final EmployeeRepository employeeRepository;public EmployeeServiceImpl(EmployeeRepository employeeDAO) {this.employeeRepository = employeeDAO;}@Overridepublic List<Employee> findAll() {return this.employeeRepository.findAll();}@Overridepublic Employee findById(int employeeId) {Optional<Employee> result = this.employeeRepository.findById(employeeId);if (result.isEmpty()) {throw new RuntimeException("Did not find employee id - " + employeeId);}return result.get();}@Override@Transactionalpublic Employee save(Employee employee) {return this.employeeRepository.save(employee);}@Override@Transactionalpublic void deleteById(int employeeId) {this.employeeRepository.deleteById(employeeId);}
}
需要注意的是这里对 findById
的具体实现有些不一样,这里是因为 employeeRepository.findById
的返回值是 Optional<Employee>
,因此需要对其进行一个空值的检查。
直接返回也不是不行,不过 Intellij 会报警告:
不影响正常使用,不过看着有点烦
Spring Data REST
DAO 可以用 JPA 代替,Service 也可以被 REST 所取代。这里其实不需要做什么事情,只要确定 pom 里有 spring-boot-starter-data-rest
,直接删除掉 controller 和 service 即可
这时候的调用结果如下:
实现功能 | 效果展示 |
---|---|
patch,之前没有实现 patch,所以这里就尝试调用一下 | ![]() |
retrieve 注意这里结构改了 | ![]() ![]() |
query,这也是 rest 自带的一些支持功能 | ![]() |
配置
我这里修改的 properties 文件如下:
# Spring Data Rest properties
spring.data.rest.base-path=/api
spring.data.rest.default-page-size=50
base-path
加回对 RequestMapping
的支持,然后修改了一下默认的 pagination size
另一个比较常见需要修改的可能是 resource 的名字,data-rest 是会按照 resource+s 这种方式去自动添加路径,但是英语的复数形态不一定遵从这个规则,比如 person 的复数是 people,mouse 的复数是 mice 等,要修改路径则需要在 Repo 上使用 @RepositoryRestResource
的方式去修改,如:
@RepositoryRestResource(path = "members")
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}
这样就无法通过 employees
的路径去访问,而是需要使用 members
:
相关文章:

[spring] Spring Boot REST API - 项目实现
Spring Boot REST API - 项目实现 书接上文 Spring Boot REST API - CRUD 操作,一些和数据库相关联的注解在 [spring] spring jpa - hibernate CRUD 主要的 layer 如下: #mermaid-svg-QE1PR1gyrkz4XIT0 {font-family:"trebuchet ms",verdana…...

ELK之Filebeat实用配置及批量部署(部署200+可用)
跟我之前Zabbix-agent批量部署脚本Linux and Windows(部署300可用)文章的套路一样,在使用该脚本前,请先准备好安装包及配置好安装包的资源下载点,由于我这边是纯内网,所以我就找了一个NAS做了共享目录&…...

用odin实现的资源复制编辑器
用odin实现了一个资源复制编辑器,使用要安装odin,功能是把要复制的资源路径一个个添加设置,点copy能把列表里的资源全部复制,支持目录复制到目录,文件复制到目录,文件复制替换。提升效率,让自己…...

linux监控文件操作行为
linux监控文件操作行为 使用 auditd 系统 auditd 是Linux系统的一个安全和审计系统,它能够跟踪系统上发生的安全相关事件。要使用 auditd 来监控文件,你需要首先确保 auditd 已经安装并且运行在你的系统上。 然后,你可以使用 auditctl 命令…...

单链表接口函数的实现(增删查改)
一、单链表的实现形式以及接口函数的声明 #include<stdio.h> #include<stdlib.h> #include<assert.h> typedef int DataType ;typedef struct SListNode {DataType data;struct SListNode* next; }SLTNODE; void SLTPrint(SLTNODE* phead);//打印链表 SLTNO…...

超低功耗Sub-1G收发芯片DP32RF002 M0内核(G)FSK/OOK 无线收发机的32位SoC芯片
产品概述 DP32RF002是深圳市动能世纪科技有限公司研制的基于ARMCortex-MO内核的超低功耗 高性能的、单片集成(G)FSK/OOK 无线收发机的32位SoC芯片。工作于200 ~960MHz范围内,支持灵活可设的数据包格式,支持自动应答和自动重发功能,支持跳频…...

uniapp_微信小程序_NaN
一、定义 isNaN() 函数用于检查一个值是否为 NaN。它接受一个参数,该参数可以是任何 JavaScript 数据类型,包括数字、字符串、对象等。如果参数是 NaN,或者不能被转换为数字,则 isNaN() 返回 true;否则返回 false。 …...

1043: 利用栈完成后缀表达式的计算
解法: #include<iostream> #include<stack> using namespace std; int main() {char a;stack<int> sk;while (cin >> a && a ! #) {if (a > 0 && a < 9) {sk.push(a - 0);}else {int num2 sk.top();sk.pop();int n…...

初学ELK - elk部署
一、简介 ELK是3个开源软件组合,分别是 Elasticsearch ,Logstash,Kibana Elasticsearch :是个开源分布式搜索引擎,提供搜集、分析、存储数据三大功能。它的特点有:分布式,零配置,自…...

[Java EE] 计算机工作原理与操作系统简明概要
1. 计算机工作原理 1.1 生活中常见的计算机 计算机分为通用计算机和专用计算机,计算机并不单单指的是电脑,还有我们平时使用的手机,ipad,智能手表等终端设备都是计算机.还有我们用户不常见的计算机,比如服务器. 还有许多嵌入式设备(针对特定场景定制的"专用计算机"…...

【尚硅谷】Git与GitLab的企业实战 学习笔记
目录 第1章 Git概述 1. 何为版本控制 2. 为什么需要版本控制 3. 版本控制工具 4. Git简史 5. Git工作机制 6. Git和代码托管中心 第2章 Git安装 第3章 Git常用命令 1. 设置用户签名 1.1 基本语法 1.2 案例实操 2. 初始化本地库 2.1 基本语法 2.2 案例实操 3. 查…...

如何在MobaXterm上使用rz命令
1、首先输入命令和想下载的文件,如下图: 2、按住ctrl鼠标右键,选择如下选项: 上传命令是rz,选择Receive...... 下载命令是sz,选择Send...... 3、我这里是要把Linux上的文件下载到我的本地window磁盘&…...

【计算机考研】408网课汇总+资源分享
408王道的视频就比较通俗易懂 王道的教材非常契合408的大纲,是专门为408大纲而编写的,而教材是方方面面都讲解的透彻。 建议王道为主,网络搜索为辅! 王道中讲解不清楚,看不懂的知识点,可以尝试在网络上进…...

如何在OceanBase v4.2 中快速生成随机数据
在使用传统数据库如 MySQL 和 Oracle 时,由于缺乏多样化的随机数据生成方案,或者实现成本过高,构造随机数据的开发成本受到了影响。OceanBase在老版本中虽然有相应的解决方案,但语法复杂和性能较差等问题仍然存在。 现在…...

nvm node.js的安装
说明:部分但不全面的记录 因为过程中没有截图,仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2,了解后 发现是nvm使用的版本较低,于是涉及nvm卸载 重新下载最新版本的nvm 2…...

【Docker】安装Redis、Nginx
1、安装redis mkdir -p /docker/redis mkdir -p /docker/redis/data touch /docker/redis/redis.conf touch /docker/redis/redis.bash编辑配置文件 vim /docker/redis/redis.conf # Redis配置文件# Redis默认不是以守护进程的方式运行,可以通过该配置项修改&…...

RK3568 UBUNTU修改网卡名称
RK3568 UBUNTU系统有两个网卡,ETH0和ETH1,于设备机壳丝印ETH1、ETH2无法对应,于是百度了一下相关的修改办法,有修改设备树的等等,挑了一个最简单,验证通过 #第1步,将原网卡关闭ip …...

【华为OD机试C++】统计字符
《最新华为OD机试题目带答案解析》:最新华为OD机试题目带答案解析,语言包括C、C++、Python、Java、JavaScript等。订阅专栏,获取专栏内所有文章阅读权限,持续同步更新! 文章目录 描述输入描述输出描述示例代码描述 输入一行字符,分别统计出包含英文字母、空格、数字和其它…...

百货商场用户画像描绘and价值分析(下)
目录 内容概述数据说明技术点主要内容4 会员用户画像和特征字段创造4.1 构建会员用户基本特征标签4.2 会员用户词云分析 5 会员用户细分和营销方案制定5.1 会员用户的聚类分析及可视化5.2 对会员用户进行精细划分并分析不同群体带来的价值差异 内容概述 本项目内容主要是基于P…...

spring-cloud微服务gateway
核心部分:routes(路由), predicates(断言),filters(过滤器) id:可以理解为是这组配置的一个id值,请保证他的唯一的,可以设置为和服务名一致 uri:可以理解为是通过条件匹配之后需要路由到&…...

【python】在pycharm创建一个新的项目
双击打开pycharm,选择create new project 选择create,后进入项目 右键项目根目录,选择new一个新的python file 随意命名一下 输入p 然后后面就会出现智能补全提示,此时轻敲一下tab,代码就写好了,非常的方便 右键执行一下代码,下面两个直接运行和debug运行都是可以的 小结 …...

java小作业(9)----用函数实现斐波那契数列(第二遍)
代码: public class Main {public static void main(String[] args) {int n 20; // 你可以更改这个值来计算和输出前n个斐波那契数for (int i 0; i < n; i) {System.out.print(fibonacci(i) " ");}}public static int fibonacci(int n) {if (n <…...

部署项目的时候的一些错误
项目打jar包,找不到资源,连接不上数据库 项目打包后无法运行 直接在idea运行可以 解决方法:pom文件中增加(配置文件如果是yml,写yml) <resources><resource><directory>src/main/java&…...

1044: 顺序栈基本操作的实现
解法: #include<iostream> #include<stack> using namespace std; int main() {int n, a, k;stack<int> sk;cin >> n;while (n--) {cin >> a;sk.push(a);}cin >> k;while (k--) {sk.pop();}if (!sk.empty()) {cout << s…...

微信小程序(总结)
1、wx.createSelectorQuery 在微信小程序中,wx.createSelectorQuery 是用于创建一个 SelectorQuery 对象的方法,而 this.createSelectorQuery 是在组件中获取元素的方法。 使用 wx.createSelectorQuery 创建的 SelectorQuery 对象可以用于获取页面中的…...

C#医学实验室/检验信息管理系统(LIS系统)源码
目录 检验系统的总体目标 LIS主要包括以下功能: LIS是集:申请、采样、核收、计费、检验、审核、发布、质控、耗材控制等检验科工作为一体的信息管理系统。LIS系统不仅是自动接收检验数据,打印检验报告,系统保存检验信息的工具&a…...

Linux驱动编程-module_platform_driver注册platform_driver
使用platform总线驱动模式编写Linux驱动时,需要注册platform_driver(用于跟.dts文件的platform_device匹配)。下面介绍2种常用注册platform_driver方法: 1、module_init()、module_exit() /* 定义平台drv,通过.name来…...

论文解读 --- 《针对PowerShell脚本的有效轻量级去混淆和语义感知攻击检测》
开篇 今天我们继续来解读安全行业优秀论文,通过学习他人的智慧成果,可以不断丰富我们的安全视野,使用它山之石来破解自身的难题。 这次要解读的论文为《Effective and Light-Weight Deobfuscation and Semantic-Aware Attack Detection for…...

在Spring Boot实战中碰到的拦截器与过滤器是什么?
在Spring Boot实战中,拦截器(Interceptors)和过滤器(Filters)是两个常用的概念,它们用于在应用程序中实现一些通用的逻辑,如日志记录、权限验证、请求参数处理等。虽然它们都可以用于对请求进行…...

数据可视化基础与应用-04-seaborn库人口普查分析--如何做人口年龄层结构金字塔
总结 本系列是数据可视化基础与应用的第04篇seaborn,是seaborn从入门到精通系列第3篇。本系列主要介绍基于seaborn实现数据可视化。 参考 参考:我分享了一个项目给你《seaborn篇人口普查分析–如何做人口年龄层结构金字塔》,快来看看吧 数据集地址 h…...