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

server/2024/12/21 20:22:35/

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

java">@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:

java">public interface EmployeeDAO {List<Employee> findAll();
}

DAOImpl 实现:

java">@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 的实现,后期会进行重构:

java">@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

    java">public interface EmployeeService {List<Employee> findAll();
    }
    
  2. 实现 interface

    java">@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

    java">@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 更新
java">public interface EmployeeDAO {List<Employee> findAll();Employee findById(int employeeId);Employee save(Employee employee);void deleteById(int employeeId);
}
DaoImpl 更新
java">@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 过来的

java">public interface EmployeeService {List<Employee> findAll();Employee findById(int employeeId);Employee save(Employee employee);void deleteById(int employeeId);
}
service impl 实现
java">@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 实现
java">@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 操作的支持

实现很简单:

java">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:

java">@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 的方式去修改,如:

java">@RepositoryRestResource(path = "members")
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}

这样就无法通过 employees 的路径去访问,而是需要使用 members:

在这里插入图片描述

在这里插入图片描述


http://www.ppmy.cn/server/1711.html

相关文章

Spring Cloud学习笔记:Eureka集群搭建样例

这是本人学习的总结&#xff0c;主要学习资料如下 - 马士兵教育 1、项目架构2、Dependency3、项目启动类4、application.yml5、启动项目 1、项目架构 因为这是单机模拟集群搭建&#xff0c;为了方便管理就都放在了一个项目中。这次准备搭建三个项目server1, server2, server3 …

web自动化系列-selenium的3种等待方式(十一)

在ui自动化测试中&#xff0c;几乎出现问题最多的情况就是定位不到元素 &#xff0c;当你的自动化在运行过程中 &#xff0c;突然发现报错走不下去了 。很大概率就是因为找不到元素 &#xff0c;而找不到元素的一个主要原因就是页面加载慢 &#xff0c;代码运行速度快导致 。 …

【创建型模式】工厂方法模式

一、简单工厂模式 1.1 简单工厂模式概述 简单工厂模式又叫做静态工厂方法模式。 目的&#xff1a;定义一个用于创建对象的接口。实质&#xff1a;由一个工厂类根据传入的参数&#xff0c;动态决定应该创建哪一个产品类(这些产品类继承自一个父类或接口)的实例。 简单工厂模式…

数据结构-并查集

数据结构-并查集 并查集&#xff08;Disjoint-Set&#xff09;是一种可以动态维护若干个不重叠的集合&#xff0c;并支持合并与查询的数据结构。 get操作查询一个元素属于哪一个集合。 merge操作把两个集合合并成一个大集合。 //并查集的存储 int fa[N]; //并查集的初始化&…

OSPF综合大实验

实验要求&#xff1a; 1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&#xff0…

Android rxjava

一.简介 RxJava是ReactiveX在JVM上的一个实现&#xff0c;ReactiveX使用Observable序列组合异步和基于事件的程序的库&#xff1b;是一个基于事件流、实现异步操作的库。RxJava在Java环境下使用&#xff0c;它通过Observable&#xff08;可观测对象&#xff09;和Subscriber&a…

(金融会计领域)普通最小二乘法回归得到的β值构建KV指数

KV指数 参考 Kim 和 Vemrecchia(2001)的方法&#xff0c;采用 KV 指数来衡量中潜股份的信息披露质量。该方法通过股票收益率对交易量的回归系数来衡量信息披露质量&#xff0c;其原理在于&#xff0c;上市公司的信息披露质量越低&#xff0c;股票收益率对交易量的依赖程度越高&…

上线流程及操作

上节回顾 1 搜索功能-前端&#xff1a;搜索框&#xff0c;搜索结果页面-后端&#xff1a;一种类型课程-APIResponse(actual_courseres.data.get(results),free_course[],light_course[])-搜索&#xff0c;如果数据量很大&#xff0c;直接使用mysql&#xff0c;效率非常低--》E…