Spring Boot REST API - 项目实现

news2024/11/24 16:46:25

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 :3306

COMMAND     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 entityManager
    private EntityManager entityManager;

    // setup constructor injection
    @Autowired
    public EmployeeDAOImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List<Employee> findAll() {
        // create a query
        TypedQuery<Employee> query = this.entityManager.createQuery("from Employee", Employee.class);

        // execute query and get result list
        // and return result
        return 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;
        }
    
        @Override
        public 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 entityManager
    private EntityManager entityManager;

    // setup constructor injection
    @Autowired
    public EmployeeDAOImpl(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    @Override
    public List<Employee> findAll() {
        // create a query
        TypedQuery<Employee> query = this.entityManager.createQuery("from Employee", Employee.class);

        // execute query and get result list
        // and return result
        return query.getResultList();
    }

    @Override
    public Employee findById(int employeeId) {
        return this.entityManager.find(Employee.class, employeeId);
    }

    // No @Transactional here, it'll be handled at service layer
    @Override
    public Employee save(Employee employee) {
        // if id == 0, then insert/save
        // else, update
        return this.entityManager.merge(employee);
    }

    @Override
    public 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;
    }

    @Override
    public List<Employee> findAll() {
        return this.employeeDAO.findAll();
    }

    @Override
    public Employee findById(int employeeId) {
        return this.employeeDAO.findById(employeeId);
    }

    @Override
    @Transactional
    public Employee save(Employee employee) {
        return this.employeeDAO.save(employee);
    }

    @Override
    @Transactional
    public 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;
    }

    @Override
    public List<Employee> findAll() {
        return this.employeeRepository.findAll();
    }

    @Override
    public 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
    @Transactional
    public Employee save(Employee employee) {
        return this.employeeRepository.save(employee);
    }

    @Override
    @Transactional
    public 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.coloradmin.cn/o/1589876.html

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

相关文章

CSS3 平面 2D 变换+CSS3 过渡

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍一、CSS3 平面 2D 变换&#x1f48e;1 坐标轴&#x1f48e;2 transform 语法…

单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控

单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控 单细胞RNA测序(scRNA-seq)基础知识可查看以下文章: 单细胞RNA测序(scRNA-seq)工作流程入门 单细胞RNA测序(scRNA-seq)细胞分离与扩增 1. 单细胞RNA-seq样本数据说明 样本数据来源文章:Acquired cancer re…

春招冲刺百题计划|矩阵

Java基础复习 Java数组的声明与初始化Java ArrayListJava HashMap 第一题&#xff1a;螺旋矩阵&#xff08;多复习&#xff0c;谨记这不是子问题&#xff0c;而是走到不能再走才结束这个方向&#xff09; class Solution {public List<Integer> spiralOrder(int[][] ma…

Python 全栈系列239 使用消息队列完成分布式任务

说明 在Python - 深度学习系列32 - glm2接口部署实践提到&#xff0c;通过部署本地化大模型来完成特定的任务。 由于大模型的部署依赖显卡&#xff0c;且常规量级的任务需要大量的worker支持&#xff0c;从成本考虑&#xff0c;租用算力机是比较经济的。由于任务是属于超高计…

Harmony鸿蒙南向驱动开发-UART接口使用

功能简介 UART指异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter&#xff09;&#xff0c;是通用串行数据总线&#xff0c;用于异步通信。该总线双向通信&#xff0c;可以实现全双工传输。 两个UART设备的连接示意图如下&#xff0c;UART与其他模块一…

浏览器工作原理与实践--同源策略:为什么XMLHttpRequest不能跨域请求资源

通过前面6个模块的介绍&#xff0c;我们已经大致知道浏览器是怎么工作的了&#xff0c;也了解这种工作方式对前端产生了什么样的影响。在这个过程中&#xff0c;我们还穿插介绍了一些浏览器安全相关的内容&#xff0c;不过都比较散&#xff0c;所以最后的5篇文章&#xff0c;我…

[Linux][进程控制][进程程序替换]详细解读

目录 1.进程创建1.fork函数初识2.fork函数返回值3.写时拷贝4.fork之后&#xff0c;父子进程代码共享5.fork常规用法6.fork调用失败的原因 2.进程终止0.进程终止时&#xff0c;操作系统做了什么&#xff1f;1.进程退出场景2.进程常见退出方法4 _exit函数(系统接口)4.exit函数(库…

数据结构DAY5--二叉树相关流程

流程有&#xff1a;创建->遍历->得到信息->销毁 创建 根据先序遍历的流程以及对叶子结点的左后驱结点和右后驱结点以#号替代的原则&#xff0c;写出一个数组&#xff0c;并建立一个结构体&#xff0c;包括数据域&#xff0c;结构体类型的左后驱结点和右后驱结点指针…

【C++核心-基础知识】内存分析和new操作符

内存分析和new操作符 一、内存分析1. 程序运行前就存在的区域1.1 代码区1.2 全局区1.3 代码演示 2. 程序运行后才存在的区域2.1 栈区2.2 堆区 二、new操作符1. 基本介绍2. 代码演示 一、内存分析 C程序在执行时&#xff0c;将内存大方向划分为4个区域&#xff1a; 代码区&…

堆排序及调整算法

调整算法 向上调整&#xff1a; 对大堆向上调整&#xff1a; adujust_up void adjust_up(int* a, int child) {int parent (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){swap(a[child], a[parent]);child parent;parent (child - 1) / 2;}else//默…

有关格式输入输出的问题

对于格式输入输出问题&#xff0c;我们最好用c语言编写代码&#xff01;&#xff01;&#xff01; 成绩统计 难点&#xff1a;格式化输出 #include <cstdio> using namespace std; typedef long long ll;ll n,score,a,b;int main() {//及格>60 优秀>85 求及格率…

脱发治疗2.0时代,植发企业如何找到新发展高地?

“在春天这个万物生长的季节&#xff0c;只有头发在掉”。 脱发已成为现代人日常生活中最大的困扰之一。国家卫健委调查数据显示&#xff0c;我国脱发人口数量已超2.5亿&#xff0c;其中26-30岁人群占比达41.9%&#xff0c;占据脱发人群主流。考虑到快节奏生活的常态化&#x…

取消格式化 SSD磁盘:从格式化 SSD磁盘恢复数据

许多用户认为SSD上删除或格式化的数据就永远消失了。事实上&#xff0c;这是完全错误的。数据恢复软件能够取消格式化SSD或恢复SSD上的数据。在本文中&#xff0c;我们将向您展示一个简单的解决方案&#xff0c;可以从格式化的 SSD 中快速、完整地恢复数据。 格式化 SSD 的原因…

【保姆级讲解Element UI】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

青少年体能素质教育平台

一、项目背景与意义 随着社会的快速发展和人们生活水平的提高&#xff0c;青少年体能素质教育逐渐受到社会各界的广泛关注。体能素质作为青少年全面发展的重要组成部分&#xff0c;对于提升他们的健康水平、增强自信心、培养团队协作精神和创新能力具有重要意义。然而&#xf…

cygwin工具学习记录

文章目录 cygwin工具学习记录主要特点使用场景安装命令使用使用命令行安装命令包的基本步骤&#xff1a;安装apt-cyg&#xff1a;错误处理&#xff1a;错误一&#xff1a;错误二错误三 简单使用 cygwin工具学习记录 Cygwin是一个在Windows操作系统上提供类Unix环境的免费软件工…

tcp early retransmit 和 rack 中神奇的 1/4 minrtt

雨中跑步十公里&#xff0c;沿河看柳&#xff0c;发了一则朋友圈&#xff1a; 为什么采用 1/4 minrtt 作为重传和探测的延时&#xff0c;上图解释的已经很清楚了&#xff0c;主要还是怕乱序&#xff0c;关于乱序的度量&#xff0c;上图解释得非常清楚&#xff0c;乱序预期可在…

蓝桥2021A组E题

回路计数 问题描述格式输入格式输出评测用例规模与约定解析参考程序难度等级 问题描述 格式输入 无 格式输出 输出方案数 评测用例规模与约定 无 解析 题目的意思是21个教学楼对于编号互质的两个楼就有一个通道&#xff0c;可以想成一个图有双向边当编号互质时&#xff0c;…

path环境变量的作用

当我把一个运行文件的路径加入到了path环境变量&#xff0c;就可以在cmd命令行随时使用运行。 在path中有两个path上面的是用户的path&#xff0c;下面的是计算机的path

GD32 HID键盘矩阵键盘发送数据时,一直发送数据问题处理

这个问题找了两三天,开始并不认为是示例程序的问题,只是感觉是自己代码问题。 这个解决流程大概是: 先调好矩阵键盘=> 调用发送函数。 就是因为调用时,一直发送数据,我也在按键抬起做了操作,始终不行。 最后,发现时示例代码中有个 空闲中断 引起的。 udev->reg…