[spring] Spring Boot REST API - 项目实现

Spring Boot REST API - 项目实现

书接上文 Spring Boot REST API - CRUD 操作,一些和数据库相关联的注解在 [spring] spring jpa - hibernate CRUD

主要的 layer 如下:

rest controller
service
DAO
db

项目配置

项目开始前的准备

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 containerdocker 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 在使用 3306lsof -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

具体实现如下:

  1. 首先声明一个 service 的 interface

    public interface EmployeeService {List<Employee> findAll();
    }
    
  2. 实现 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 很像

  3. 将 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);}
}

这里几个点需要注意一下:

  1. merge 是根据 id 进行的操作,如果 id == 0,那么实现添加功能,不然就是修改
  2. 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:

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/819389.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Linux][基础IO][二][缓冲区][理解文件系统]详细解读

目录 1.缓冲区0.缓冲区的刷新策略1.何为缓冲区&#xff1f;2.总结 2.理解文件系统0.文件元数据1.了解文件系统 --> 理解inode2.软硬链接 1.缓冲区 0.缓冲区的刷新策略 一般情况 立即刷新行刷新(行缓冲)满刷新(全缓冲) 特殊情况 用户强制刷新(fflush)进程退出 所有的设备&a…

conda配置多版本python

安装conda 以下任选下载 Anaconda Miniconda 配置conda环境变量 比如windows&#xff0c;在配置我的电脑中的环境变量&#xff0c;在系统变量的Path中新增下面内容 需要根据实际目录进行更改 D:\soft\miniconda3 D:\soft\miniconda3\Scripts D:\soft\miniconda3\Library\bi…

Java的Future机制详解

Java的Future机制详解 一、为什么出现Future机制二、Future的相关类图2.1 Future 接口2.2 FutureTask 类 三、FutureTask的使用方法四、FutureTask源码分析4.1 state字段4.2 其他变量4.4 构造函数4.5 run方法及其他 一、为什么出现Future机制 常见的两种创建线程的方式。一种是…

Python进阶编程 --- 2.MySQL、pymysql、PySpark

文章目录 第一章&#xff1a;SQL基础入门1.1 数据库数据库如何存储数据 1.2 数据库和SQL的关系1.3 MySQL版本1.4 命令提示符内使用MySQL1.5 SQL概述1.5.1 SQL语言分类1.5.2 SQL语言特性 1.6 DDL库管理表管理 1.7 DML - 数据操作1.8 DQL - 查询和计算数据1.8.1 基础数据查询1.8.…

HDFS Lease详解

本文主要介绍hdfs lease的设计以及实现。 写在前面 https://www.cnblogs.com/jhcelue/p/6783076.html https://blog.csdn.net/yexiguafu/article/details/118890014 https://www.jianshu.com/p/33e1a5a2b876 https://blog.csdn.net/breakout_alex/article/details/1014569…

行业模板|DataEase批发零售大屏模板推荐

DataEase开源数据可视化分析平台于2022年6月发布模板市场&#xff08;https://templates-de.fit2cloud.com&#xff09;&#xff0c;并于2024年1月新增适用于DataEase v2版本的模板分类。模板市场旨在为DataEase用户提供专业、美观、拿来即用的大屏模板&#xff0c;方便用户根据…

【Canvas与艺术】绘制斜置黄色三角biohazard标志

【关键点】 径向渐变色和文字按角度偏转。 【成果图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>使用Html5/Canvas绘制…

spring-cloud微服务gateway

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

2024 CKA 基础操作教程(十二)

题目内容 考点相关内容分析 Pods Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。 Pod 是 Kubernetes 中的原子单元&#xff0c;用于封装应用程序的一个或多个容器、存储资源、唯一的网络 IP&#xff0c;以及有关如何运行容器的选项。Pod 提供了一个共享的…

一些实用的工具网站

200 css渐变底色 https://webgradients.com/ 200动画效果复制 https://css-loaders.com/classic/ 二次贝塞尔曲线 https://blogs.sitepointstatic.com/examples/tech/canvas-curves/bezier-curve.html 三次贝塞尔曲线 https://blogs.sitepointstatic.com/examples/tech/c…

Day92:系统攻防-WindowsLinux远程探针本地自检任意执行权限提升入口点

目录 操作系统-远程漏扫-Nessus&Nexpose&Goby Nessus Nexpose 知识点&#xff1a; 1、远程漏扫-Nessus&Nexpose&Goby 2、本地漏扫-Wesng&Tiquan&Suggester 3、利用场景-远程利用&本地利用&利用条件 操作系统-远程漏扫-Nessus&Nexpose&a…

Python——详细解析目标检测xml格式标注转换为txt格式

本文简述了目标检测xml格式标注的内容&#xff0c;以及yolo系列模型所需的txt格式标注的内容。并提供了一个简单的&#xff0c;可以将xml格式标注文件转换为txt格式标注文件的python脚本。 1. xml格式文件内容 <size>标签下为图片信息&#xff0c;包括 <width> …

​​​​​​​iOS配置隐私清单文件App Privacy Configuration

推送到TestFlight后邮件收到警告信息如下&#xff0c;主要关于新的隐私政策需要补充&#xff1a; Hello, We noticed one or more issues with a recent submission for TestFlight review for the following app: AABBCC Version 10.10.10 Build 10 Although submission for …

servlet的三个重要的类(httpServlet 、httpServletRequst、 httpServletResponse)

一、httpServlet 写一个servlet代码一般都是要继承httpServlet 这个类&#xff0c;然后重写里面的方法 但是它有一个特点&#xff0c;根据之前写的代码&#xff0c;我们发现好像没有写main方法也能正常执行。 原因是&#xff1a;这个代码不是直接运行的&#xff0c;而是放到…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《应用图论建模输电网的电力现货市场出清模型》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

JavaSE图书管理系统实战

代码仓库地址&#xff1a;Java图书管理系统 1.前言 该项目将JavaSE的封装继承多态三大特性&#xff0c;使用了大量面向对象的操作&#xff0c;有利于巩固理解 &#xff08;1&#xff09;实现效果 2.实现步骤 第一步先把框架搭建起来&#xff0c;即创建出人&#xff1a;管理员和…

RocketMQ 02 功能大纲介绍

RocketMQ 02 主流的MQ有很多&#xff0c;比如ActiveMQ、RabbitMQ、RocketMQ、Kafka、ZeroMQ等。 之前阿里巴巴也是使用ActiveMQ&#xff0c;随着业务发展&#xff0c;ActiveMQ IO 模块出现瓶颈&#xff0c;后来阿里巴巴 通过一系列优化但是还是不能很好的解决&#xff0c;之后…

MySQL底层架构

MySQL底层架构 连接器 验证客户端连接的用户名密码、校验权限、维持和管理连接。 客户端如果超过 wailt_timeout 没有动静&#xff0c;连接器会主动将它断开&#xff0c;此时客户端再次发送请求的话&#xff0c;就会收到错误&#xff1a;lost connection to MySQL server dur…

【Modelsim】保持波形格式重编译and波形的保存与查看

文章目录 保持原波形格式重编译波形的保持与查看保存波形打开工程查看波形 保持原波形格式重编译 Modelsim 仿真设置好波形格式后&#xff0c;若需要修改代码并保持原波形格式重新查看波形&#xff0c;只需将文件重新编译后仿真即可。 1.修改代码后Project页面的代码状态变成…

外网如何访问内网数据库?

在当今信息时代&#xff0c;随着互联网的快速发展&#xff0c;很多企业和个人都面临着外网访问内网数据库的需求。外网访问内网数据库可以实现远程操作&#xff0c;方便用户在任何地点使用移动设备进行数据管理和查询。本文将介绍一种名为【天联】的组网产品&#xff0c;它是一…