类比前端知识来学习Java的Spring Boot实现MySql的全栈CRUD功能——搭配Svelte+Vite

作者:水冗水孚日期:2025/11/9

前言

  • 本文梳理了后端的相关操作流程环节
  • 使用Svelte+Vite(前端)搭配Spring Boot(后端)
  • 实现了一个增删改查全栈项目
  • 有助于前端更好理解后端java的分层思想,数据流转控制
  • Svelte尝鲜学习了解
  • 完整前后端代码在github:github.com/shuirongshu…

大道至简,一些知识点是相通的比如——python里面也有闭包这个概念

所谓编程即:学习规则语法、理解规则语法、合理运用规则语法、从而自定义规则...

Java、Spring、Spring Boot ≈ Node.js、Express/Koa、Egg.js/NestJS

效果图

1.gif

1.5.png

仓库代码图

2.png

对比理解后端宏观架构流程、数据流转

当我们知道后端具体做了什么事情以后,就更好理解了,即宏观架构流程、数据流转要清晰

Java之于Spring之于Spring Boot 相当于 Node.js之于Express/Koa之于Egg.js/NestJS

  • Java底层是JDK+JRE+JVM(JDK安装以后自带JRE和JVM,类似于Node安装后自带NPM)
  • 基于Java原生开发了Spring框架,基于Spring框架有了Spring Boot(开箱即用)
    • Spring MVC是Spring框架中的一部分,所谓的MVC指的是ModelViewController
    • 简约而言,后端主要做这几件事:
      1. 定义请求路由接口 (C路由)
      2. 请求参数验证 (C参数验证)
      3. 业务逻辑处理 (M业务逻辑)
      4. 操作数据库 (M业务逻辑)
      5. 返回响应数据JSON、下载返回流文件 (C路由返回 V视图概念消失弱化)
    • 前后端不分离JSP时代,MVC基本后端做。即:
      1. 过去: 后端 = M + C + V (渲染HTML)
      2. 现在: 后端 = M + C; 前端 = V (前端框架渲染) + 交互
  • 类比,Node.js --> Express.js / Koa.js --> Egg.js/NestJS (开箱即用)
  • 至于Java微服务Spring Cloud实际上就是一堆Spring Boot的集合

技术栈类比

Spring Boot 生态Node.js 对应技术说明
JDK 8Node.js⚙️ 运行环境,学Java装JDK,就像学JS装Node
Spring && Spring MVCExpress/Koa/Fastify🚀 后端基础框架,快速搭建应用服务
Spring Boot 2.7.18Egg.js/Nest.js 或 Express/Koa/Fastify + 一堆插件🚀 后端进阶完善的框架,可开箱即用
MyBatis-Plus 3.5.3.1Sequelize/Prisma🗄️ ORM框架,简化数据库操作,不用手搓sql了
Swagger 3.0.0 + Knife4j 3.0.3swagger-ui-express📖 API文档自动生成
Hutool 5.8.22lodash/day.js🛠️ 工具库,提供各种实用函数
Apache POI 4.1.2node-xlsx / xlsx📊 Excel文件处理,导入导出解析excel的数据
数据库驱动(JDBC Driver)mysql或者mysql2🔌 数据库连接
HikariCPmysql或者mysql2内置的连接池🔌 数据库连接池,管理数据库连接
  • Maven 就像 npm,pom.xml 就是 package.json,依赖管理方式几乎一样!
  • Java 的包管理比 npm 更严格,但概念相同
  • Spring Boot 的注解就像 Vue的自定义指令

后端五件事(简约版)

    1. 定义请求路由接口
    1. 请求参数验证
    1. 业务逻辑处理
    1. 操作数据库
    1. 返回响应数据(JSON / 流)

整体流程

流程节点核心操作
前端 → Nginx → Controller前端发请求(如 GET /user/1),Controller 用 UserQueryDTO 接收参数(如 id=1),校验参数合法性
Controller → ServiceController 调用 Service 方法,传入 UserQueryDTO 或提取后的参数(如 id=1)
Service → MapperService 处理业务逻辑(如权限判断),调用 Mapper 方法(如 userMapper.selectById(1))
Mapper → 数据库Mapper 执行 SQL,将查询条件(id=1)转为数据库语句,同时通过 Entity 映射表结构(如 User 类对应 user 表)
数据库 → Mapper数据库返回结果集,Mapper 自动将结果集转为 User 实体对象
Mapper → ServiceMapper 将 User 实体返回给 Service
Service → ControllerService 将 User 实体通过转换器(或手动)转为 UserRespDTO(屏蔽敏感字段,如密码)
Controller → Nginx → 前端Controller 将 UserRespDTO 转为 JSON 响应,返回给前端

下面以新增请求为例

当然还有别的 这里不赘述

1. 定义请求路由接口 (Controller层)

定义新增接口 /people

比如定义一个新增接口

  • 定义一个请求Url是 /people
  • Post 请求 Body传参
  • 传参示例:{ age: 20,home: "string",name: "string",remark: "string" }
  • curl命令调用如下
1curl -X POST http://localhost:8080/people \
2  -H "Content-Type: application/json" \
3  -d '{"age": 20, "home": "string", "name": "string", "remark": "string"}'
4

Controller控制层

PeopleController.java

1import com.people_sys.people.service.PeopleService; // 导入业务服务层 PeopleService
2// @RestController = @Controller + @ResponseBody = 返回JSON格式的数据
3@RestController 
4@RestController // REST风格的接口
5@RequestMapping("/people") // 接口请求url 统一请求前缀/people
6public class PeopleController { // PeopleController类
7    // 定义变量存储peopleService
8    private final PeopleService peopleService;
9    // 构造器注入(初始化执行)
10    // 也可以使用注解 @Resource 或 @Autowired  一步到位
11    public PeopleController(PeopleService peopleService) {
12        this.peopleService = peopleService; // 存起来
13    }
14    
15    // 新增人员 - POST /people
16    @PostMapping
17    public ApiResponse<Boolean> create(@Valid @RequestBody PeopleDTO people) throws Exception {
18        // 调用peopleService层的create方法新增用户人员
19        boolean result = peopleService.create(people);
20        return ApiResponse.success(result);
21    }
22    // 根据id查询 - GET /people/{id}
23    @GetMapping("/{id}")
24    public ApiResponse<People> getById(@PathVariable Integer id) {
25        // 调用peopleService层的getById方法,根据id查询对应人员的
26        People people = peopleService.getById(id);
27        return ApiResponse.success(people);
28    }
29    
30    ......
31}
32

何为注解&常见的注解举例

简而言之:

  • 注解有点像前端Vue中的指令,比如只要写了v-if以后,Vue框架会自动根据相应逻辑处理显示隐藏
  • 也像React中的Props,比如 @NotNull(message = "不能为空") 类比于 <input required ... />

注解(实际上是封装了一层),就是用特定的语法,告诉框架(组件),如何正确处理对应逻辑

常见的注解举例:

  • @RestController 控制器注解,标明定义的接口——适合前后端分析的项目,返回JSON
  • @Controller 适合前后端不分离的,比如返回html,用的少了
  • @Service 服务层注解,撰写具体业务逻辑
  • @Data Lombok注解,自动生成getter/setter/toString等
  • @RequestMapping系列注解,请求映射注解
    • @PostMapping // POST请求
    • @GetMapping("/{id}") // GET请求带路径参数
    • @PutMapping // PUT请求
    • @DeleteMapping // DELETE请求
    • @RequestMapping("/api") // 通用映射
  • 验证注解系列
    • @NotNull // 不为 null
    • @NotBlank // 去空格非空
    • @NotEmpty // 集合或数组至少一个元素
    • @Size(min=1, max=10) // 长度限制
    • @Email // 邮箱格式
    • @Min(0) @Max(150) // 数值范围
  • 跨域注解
1import org.springframework.web.bind.annotation.CrossOrigin;
2import org.springframework.web.bind.annotation.GetMapping;
3import org.springframework.web.bind.annotation.RestController;
4@RestController
5public class TestController {
6    // 仅当前接口支持跨域,允许所有来源(*)
7    @CrossOrigin
8    @GetMapping("/api/test")
9    public String testCrossOrigin() {
10        return "跨域请求成功!";
11    }
12}
13
  • 拿到前端传参的注解
    • @PathVariable注解
      * 能拿到/findById/100 这个100
      * 类似express中的req.params + 路由 :变量名
    • @RequestParam 注解
      * 能拿到query传参 ?name=xxx&age=88
      * 类似express中的req.query
    • @RequestBody
      * 能拿到body传参
      * 类似express中的req.body + 解析中间件app.use(express.json())
  • 等很多注解...

注解还可以自定义,有点像函数

5. 返回响应 JSON / 流

统一返回JSON

首先,前端请求接口时,后端统一返回格式,使用ApiResponse这个类统一控制

1package com.people_sys.people.config;
2import lombok.Data;
3@Data // 自动生成getter/setter
4public class ApiResponse<T> {
5    private int code;       // 状态码(如200成功,400参数错误,500系统错误)
6    private String message; // 响应消息(成功时为"success",失败时为错误信息)
7    private T data;         // 业务数据(成功时返回)
8    public ApiResponse(int code, String message, T data) {
9        this.code = code;
10        this.message = message;
11        this.data = data;
12    }
13    // 成功响应
14    public static <T> ApiResponse<T> success(T data) {
15        return new ApiResponse<>(200, "success", data);
16    }
17    // 错误响应
18    public static <T> ApiResponse<T> error(int code, String message) {
19        return new ApiResponse<>(code, message, null);
20    }
21}
22

前端查询id为300的这条数据,http://localhost:8080/people/300返回

1{
2    "code": 200,
3    "message": "success",
4    "data": {
5        "id": 300,
6        "name": "张三",
7        "age": 3,
8        "home": "山东",
9        "remark": "zhangsan",
10        "delFlag": 0,
11        "createTime": "2025-11-04T14:48:05",
12        "updateTime": "2025-11-04T17:23:32"
13    }
14}
15

ApiResponse实际上就是一个公共函数,统一加工处理数据的

返回流文件给前端下载

动态生成Excel并下载(伪代码示例)

1@GetMapping("/downloadExcel")
2public void downloadExcel(HttpServletResponse response) throws IOException {
3    // 1. 动态生成Excel(使用POI等工具)
4    XSSFWorkbook workbook = new XSSFWorkbook(); // POI的Excel对象
5    XSSFSheet sheet = workbook.createSheet("Sheet1");
6    // ... 向sheet中写入数据 ...
78    // 2. 设置响应头(同上)
9    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
10    String fileName = URLEncoder.encode("动态生成的Excel.xlsx", "UTF-8");
11    response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
1213    // 3. 直接将工作簿写入响应流
14    workbook.write(response.getOutputStream());
15    workbook.close(); // 关闭资源
16}
17

workbook.write(response.getOutputStream()) 意思就是:将内存中动态生成的文件(如 Excel)以二进制流的形式写入 HTTP 响应输出流,最终返回给前端,以便于前端使用a标签实现文件下载

2. 请求参数验证 (Controller层)

数据新增接口细节拆解

接下来,看对应新增接口注释,新增接口前端Post的Body传参为

people: { name: 'tom', age: 2, home: 'New York', 'remark': 'xyz' }

1// 1. 接口请求类型注解,等价于:@RequestMapping(method = RequestMethod.POST)
2@PostMapping
3// 2. public公开的create方法,允许其他类调用(若写成private,Spring无法扫描到这个接口,前端会访问失败)
4public ApiResponse<Boolean> create( // 方法名叫做create
5    // 3. 数据校验触发注解(想要使用PeopleDTO里面的校验,必须要使用@Valid标明开启校验)
6    @Valid  
7    // 4. 请求体接收注解,通过这个可以拿到前端请求体里面的参数,并将其赋值给people参数
8    @RequestBody  
9    // 5. 方法参数(DTO 实体 + 参数名)
10    PeopleDTO people  // people为函数的形参存储的前端参数
11) throws Exception {  // 6. 异常则抛出声明
12    // 7. 业务逻辑:把前端传递进来的people对象参数,调用 Service 层新增方法,得到布尔类型结果
13    boolean result = peopleService.create(people);
14    // 8. 返回统一响应结果,新增成功返回 {"code":200,"message":"success","data":true}
15    return ApiResponse.success(result); 
16}
17

通过@RequestBody 可以拿到前端参数 存到people变量里面,然后交由peopleService.create方法去做业务逻辑处理进而写入数据库

但是,前端可能乱传参,所以,需要搭配PeopleDTO和 @Valid 进行校验一下

什么是DTO,能做什么

简而言之:DTO就是规范接收前端传参、规范返回接口数据

新增的DTO

1package com.people_sys.people.dto;
2import lombok.Data;
3import javax.validation.constraints.NotBlank;
4import javax.validation.constraints.NotNull;
5@Data
6public class PeopleDTO {
7    @NotBlank(message = "姓名不能为空") // 必传,字符串类型
8    private String name;
9    @NotNull(message = "年龄不能为空") // 必传,数字类型
10    private Integer age;
11    private String home; // 非必传
12    private String remark; // 非必传
13}
14

编辑的DTO多了一个id,其他和新增一样(毕竟编辑要找到对应id再去编辑)

1@Data
2public class PeopleUpdateDTO {
3
4    @NotNull(message = "ID不能为空")
5    private Integer id;
6
7    @NotBlank(message = "姓名不能为空")
8    private String name;
9
10    ...
11}
12

我们可以把DTO看成一个工具函数,在接到前端传参的时候,做规范限制——过滤掉不需要的参数,校验是否符合传参要求

用JavaScript模拟DTO功能

假设,我们要求前端传参规则是这样的:

name字段必传且为字符串类型、age字段非必传(若传了必须要是数字类型),若多传则忽略之

1const Dto = {
2    name: {
3        type: "string",
4        required: true,
5    },
6    age: {
7        type: "number",
8        required: false,
9    },
10}
11
  • 假设用户传递{ name: '孙悟空', age: 500, home: '花果山' }那么经过DTO处理以后,能得到{ name: '孙悟空', age: 500}多传的home字段和其值,被忽略
  • 假设用户传递{ age: 500 }那么经过DTO处理以后,要校验提示name是必传的
  • 假设用户传递{ name: true }那么经过DTO处理以后,要校验提示name的数据类型不对

于是,就可以有以下模拟代码

1/**
2 * 模拟定义DTO
3 *  姓名为字符串,必传
4 *  年龄为数字类型,非必传
5 * */
6const dtoDef = (params) => {
7    // 定义Dto字段规则
8    const Dto = {
9        name: {
10            type: "string",
11            required: true,
12        },
13        age: {
14            type: "number",
15            required: false,
16        },
17    }
18    /**
19     * 1. 必传字段校验
20     * */
21    const mustHaveKeys = []
22    // 1.1 收集那些字段是必传的
23    for (const key in Dto) {
24        if (Dto[key].required) {
25            mustHaveKeys.push(key)
26        }
27    }
28    // 1.2 收集传递进来的key组成的数组
29    const paramsKeys = Object.keys(params)
30    // 1.3 看看是否每一个必传字段,都在参数key数组里面
31    const flag = mustHaveKeys.every((mk) => paramsKeys.includes(mk))
32    // 1.4 必传参数校验
33    if (!flag) {
34        console.warn([`必传字段缺失,必传字段有这些:${mustHaveKeys.join(",")}`](https://xplanc.org/primers/document/zh/10.Bash/90.%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C/EX.join.md))
35        return false
36    }
37    /**
38    * 2. 字段类型校验
39    * */
40    const resDto = {}
41    for (const key in params) {
42        // 在Dto里的做对应校验
43        if (key in Dto) {
44            // 类型校验
45            if (typeof params[key] === Dto[key].type) {
46                // 校验通过则转存一份
47                resDto[key] = params[key]
48            } else {
49                console.warn([`字段${key}类型错误,类型应为${Dto[key].type}`](https://xplanc.org/primers/document/zh/02.Python/EX.%E5%86%85%E5%BB%BA%E5%87%BD%E6%95%B0/EX.type.md))
50                return false
51            }
52        }
53        // 不在Dto里面的忽略,这样resDto里存的就是定义好的
54        else { }
55    }
56    return resDto
57}
58
59const par = { name: '孙悟空', age: 500, home: '花果山' }
60
61// 经过dtoDef的校验、过滤就能得到符合要求的dto了
62const result = dtoDef(par)
63console.log('result', result) // {name: '孙悟空', age: 500}
64
  • 在java中,dto定义好以后,可在接受前端参数的时候使用
    • 只拿自己需要的字段值
    • 使用注解快速校验前端传参
  • 也可在从数据库捞出数据返回给前端的时候使用
    • 比如返回的时候,password和gender字段作为保密,不返回给前端
    • 只返回能返回的数据

所以,再总结一下:DTO就是规范接收前端传参、规范返回接口数据的一个工具

问:如果有一些额外的字段,要返回给前端,又不是前端要传递的字段,怎么定义呢?比如返数据时,需加一个字段isAdults来确认是否成年(规则是大于18岁成年为true,小于则为false)

答:这个时候,VO就闪亮登场了

VO是最终返回给前端数据结构格式

总结:VO是最终返回给前端数据结构格式——如果小项目,直接用dto也行(可以看成把dto当成vo用)

1@Data
2public class PeopleVO {
3    private Long id; // 额外字段:数据库主键(前端不用传,要展示)
4    // 复用 DTO 中的字段
5    private String name; 
6    private Integer age;
7    private String home;
8    private String remark;
9    private Boolean isAdults; // 额外字段:计算后信息,是否成年
10}
11

3. 业务逻辑处理 & 4.操作数据库

先假设没有复杂业务逻辑处理,直接把前端传递的数据,存到数据库里,就需要编写sql语句

古法手搓sql

现在接口定义好了,且用DTO规范了前端传参,接下来应该把前端传递来的参数转成sql语句

比如,新增一条数据:{ "name": "tom", "age": 18 }

1public void addPerson(String name, int age) { // 拿到前端传进来的参数name和age
2     // 拼接手搓sql
3    String sql = "INSERT INTO people (name, age) VALUES (?, ?)";
4    // 调用Spring的jdbcTemplate类的update方法新增数据
5    int rowsAffected = jdbcTemplate.update(sql, name, age);
6    // 成功插入 1 条记录 → 返回 1
7    if (rowsAffected > 0) {
8        System.out.println("成功插入 " + rowsAffected + " 条记录");
9    }
10}
11

但是这个手搓sql的方式,不优雅(可维护性、类型安全、重复劳动等),所以诞生了orm框架,通过orm框架去操作数据库

什么是ORM

  • ORM(Object-Relational Mapping,对象关系映射)是一种编程技术
  • 核心是把数据库中的 “表、行、列” 映射成程序中的 “类、对象、属性”
  • 让开发者能用面向对象(OOP)的方式操作数据库,而不用直接写复杂的 SQL 语句。

换句话说,ORM是翻译官

  • 数据库世界:用表(Table)、行(Row)、字段(Column)存储数据(比如 MySQL 的 user 表,有 id/name/age 字段);
  • 程序世界:用类(Class)、对象(Object)、属性(Attribute)处理数据(比如 Java 的 User 类,有 id/name/age 属性);
  • ORM 的作用:在两者之间做 “翻译”—— 假使我们操作程序里的对象(比如 user.name = "张三"),ORM 自动转换成对应的 SQL(UPDATE user SET name = "张三"),执行后再把数据库结果转回

ORM的核心价值就是不用写 或者少写 SQL,专注业务逻辑

上述案例,如果使用mybatis-plus这个orm框架(半自动化orm框架),则这样写即可

1public void addPerson(String name, int age) {
2    // 创建实体对象并设置参数
3    Person person = new Person();
4    person.setName(name);
5    person.setAge(age);
6
7    // 调用 MyBatis-Plus 的 insert 方法插入数据
8    int rowsAffected = personMapper.insert(person);
9
10    if (rowsAffected > 0) {
11        System.out.println("成功插入 " + rowsAffected + " 条记录");
12    }
13}
14

这里的personMapper继承自BaseMapper(自带一套通用的crud的方法)如

所以可以直接insert数据

1import com.baomidou.mybatisplus.core.mapper.BaseMapper;
2// 定义一个接口PersonMapper继承了BaseMapper上所用功能,比如crud的api
3public interface PersonMapper extends BaseMapper<Person> {
4    // 无需编写任何方法,BaseMapper 已提供 CRUD 基础功能
5}
6

注意,BaseMapper<Person> 中的泛型 <Person> 就是告诉 MyBatis-Plus:这个 Mapper 要操作的是与 Person 类绑定的那张表(即 people 表)

所以,一定要告诉 MyBatis-Plus要操作那张表,怎么告诉呢 就通过entity告诉

所以,这里的<Person>就是一个entity,如下

1@Data  // 无需手动编写 getter、setter 等方法,@Data 会自动生成
2@TableName("people") // 映射数据库表名——告诉mybatis,是那张表
3public class Person {
4    @TableId(type = IdType.AUTO) // 主键自增
5    private Long id; 
6    
7    private String name; 
8    private Integer age; 
9}
10

所以ORM 一定是要搭配着entity,才能一块干活!!!

通俗而言,ORM是翻译官,他要把火星文翻译成中文,所以,它需要一个火星文对应中文词典,而entity就是这本词典

常见的 ORM 框架

  • Python:SQLAlchemy(通用)、Django ORM(Django 内置);
  • Java:Hibernate(重量级)、MyBatis(半 ORM,更灵活);
  • JavaScript/TypeScript:Sequelize(Node.js)、Prisma(现代主流);
  • PHP:Eloquent ORM(Laravel 内置)。

无论哪种 ORM 框架,都必须通过某种形式的 “实体” 来定义 “代码对象” 与 “数据库表” 的映射关系(表名、字段名、类型、主键等)。这些 “实体” 可能叫 Model(PHP Prisma Python)Entity(Java、cSharp) 或直接是一个类 / 配置,但本质都是 ORM 框架的 “翻译词典”—— 没有它们,ORM 就无法完成 “对象操作→SQL 语句” 的转换。

ORM之Mybatis操作sql

回顾一下,使用ORM操作sql,首先,orm要知道操作那张表的哪些字段,当然,dto是老早就定义好了,如下提前定义好了的dto

1// DTO(接收前端)
2public class PersonDTO {
3    private String name;
4    private Integer age;
5    // getter/setter
6}
7

我们会发现,dto中的东西不够用,毕竟DTO只是用来定义传输的部分数据,完整数据还是在表里。但是orm必须要知道完整数据,才方便操作数据库

而我们又不能把所有信息都丢到dto里面

所以,需要一个新的东西,来告知orm,完整的表、字段、数据类型是啥。用对象(类)的形式告知,映射数据库,于是就有了Entity这个文件

1// Entity(映射数据库)
2@TableName("people") // 告知表名字
3public class People {
4    @TableId(type = IdType.AUTO) // 主键自增
5    private Long id;
6    private String name;
7    private Integer age;
8    // getter/setter
9}
10

现在orm知道了操作的表名是people,和表里的对应字段信息,那么orm就方便操作数据库中的表了

1// 3. Service 层
2public void addPerson(PersonDTO dto) {
3    People people = new People();
4    people.setName(dto.getName());
5    people.setAge(dto.getAge());
6    
7    // 无需写 SQL!ORM 自动 INSERT
8    boolean saved = peopleService.save(people);
9    if (!saved) {
10        throw new BusinessException("保存失败");
11    }
12}
13

ORM 框架的核心优势

  1. 简化开发:不用写 SQL,减少重复工作(比如拼接 SQL、解析结果),开发效率大幅提升;
  2. 屏蔽数据库差异:同一套代码,通过 ORM 适配 MySQL、PostgreSQL、SQLite 等不同数据库(ORM 负责翻译不同数据库的 SQL 语法);
  3. 降低学习成本:不用精通各种数据库的 SQL 细节,专注于面向对象编程;
  4. 安全性更高:自动防止 SQL 注入(比如拼接用户输入时,ORM 会自动转义)。

ORM总结

ORM 是连接 “面向对象编程” 和 “关系型数据库” 的桥梁,核心目标是让开发者用更熟悉的 OOP 方式操作数据库,减少 SQL 编写,提升开发效率和代码可维护性

Entity与DTO和VO对比

Entity 是“存数据的”,DTO 是“传数据的”,VO 是“给用户看的”。

  • Entity(实体类)——一般不直接返回 Entity 给前端
  • DTO(Data Transfer Object,数据传输对象)
  • VO(View Object / Value Object,视图对象)

如果项目简单、无敏感数据,可 DTO/VO 合并,但Entity 仍应隔离

维度EntityDTOVO(View Object)
用途数据库映射层间/系统间数据传输前端展示专用
是否持久化是(对应 DB 表)
是否含敏感字段可能有(如密码)通常过滤掉通常无
字段结构与 DB 一致灵活,可裁剪/组合为 UI 定制,可能格式化/计算
使用位置Repository / JPA 层Service ↔ Controller / API 间Controller → 前端
是否含逻辑一般无(或简单 getter)可能有简单格式化逻辑

3. 业务逻辑之新增的人员不能重名

1@Override
2// 新增人员的校验:新增的人名字不能和数据库中已经有的人名字重复
3public boolean create(PeopleDTO dto) throws Exception {
4    // 校验名字是否重复
5    if (lambdaQuery()
6            .eq(People::getName, dto.getName())
7            .eq(People::getDelFlag, 0)
8            .count() > 0) {
9        throw new BusinessException(400, "人员姓名已存在,请使用其他姓名");
10    }
11
12    People entity = BeanUtil.copyProperties(dto, People.class);
13    return save(entity);
14}
15

这里的save也是mybatis-plus提供的,比直接insert更加智能

由于我们的业务逻辑是——新增的人名字不能和数据库中已经有的人名字重复,所以,在写入数据库之前,还需要编写sql查询一下,数据库中有多少条数据,和当前人名一样。

Mybatis也提前准备好了lambdaQuery()以供我们进行链式调用,进行条件构造链式查询,语法简洁

若使用手写sql,则是如下写法

1@Autowired
2private JdbcTemplate jdbcTemplate; // Spring 的 JDBC 工具类
3
4public void checkDuplicateName(String name) {
5    // 手写 SQL 字符串
6    String sql = "SELECT COUNT(*) FROM people WHERE name = ? AND del_flag = 0";
7    // 执行查询,获取记录数
8    Integer count = jdbcTemplate.queryForObject(
9        sql, 
10        new Object[]{name},  // 绑定参数(姓名)
11        Integer.class        // 返回类型
12    );
13    // 判断并抛异常
14    if (count != null && count > 0) {
15        throw new BusinessException(400, "人员姓名已存在,请使用其他姓名");
16    }
17}
18

由此可见,当真是使用ORM框架——Mybatis更优雅提效,便于我们处理业务路基,操作数据库

常用技术栈:

  • 数据库:MySQL + HikariCP(连接池)
  • 数据访问:MyBatis + MyBatis-Plus(ORM + 代码生成)
  • API 开发:Spring Web + SpringDoc-OpenAPI(接口 + 文档)
  • 缓存:Redis + Spring Cache(提升性能)
  • 消息队列:Kafka/RabbitMQ(削峰填谷)
  • 日志:SLF4J + Logback(日志记录)
  • 测试:JUnit 5 + Mockito(单元测试)
  • 部署:Maven + Docker(打包部署)

Maven打包构建

  • Maven之于Java如同Npm之于Node.js
  • pom.xml文件作依赖版本管理——package.json做依赖版本管理
  • mvn install安装项目依赖——相当于npm install
  • 项目内安装单个依赖,需手动在 pom.xml 文件中添加依赖后执行 mvn install——相当于npm install xxx
  • 卸载某个依赖,需手动删除 pom.xml 中依赖后执行 mvn clean install——相当于npm uninstall xxx
  • mvn package相当于npm run build
  • Maven构建打包功能,把一堆Java开发代码打包成一个.jar文件(压缩包)——Npm把一堆前端代码打包成一个dist文件夹

Java基础,面向对象、类、接口、抽象、封装、继承、多态、线程、IO流 本文暂不赘述...

Svelte的增删改查尝鲜

  • Svelte也有生命周期,如import { onMount } from "svelte";
  • 也可以数据双向绑定,如bind:value={variable}
  • 也有计算属性,如$: computed = expression
  • 可直接响应式变量,如let variable = value
  • 事件绑定语法是on:click={handler}
  • 阻止冒泡on:click|stopPropagation
  • 也有条件渲染,如
1{#if condition}
2  <!-- 内容 -->
3{:else}
4  <!-- 其他内容 -->
5{/if}
6
  • 也有循环v-for、map渲染
1{#each items as item (item.id)}
2  <!-- 循环内容 -->
3{/each}
4
  • 也可组件化开发项目(单文件直接和vue类似,直接 <script><style>HTML模板
  • 也可以import和export
  • 分页组件父组件传对象和做事件处理<Page {pageInfo} on:pageChange={handlePageChange} />
  • 对应子组件接收是
1// Props - 接收整个分页信息对象
2export let pageInfo = {
3    currentPage: 1,
4    pageSize: 10,
5    total: 0,
6};
7
  • 子组件触发父组件使用createEventDispatcher,如
1import { createEventDispatcher } from "svelte";
2
3// 创建事件分发器
4const dispatch = createEventDispatcher();
5
6// 按钮点击的回调
7dispatch("pageChange", newPage);
8

比如和React语法对比:

  1. 更简洁的语法: 无需 useStateuseEffect 等Hook
  2. 真正的响应式: 直接赋值触发更新,不需要 setState()
  3. 更少的样板代码: 无需频繁的解构和回调
  4. 更小的包体积: 编译时优化,运行时更轻量
  5. 内置动画支持: transitionanimate 指令
  6. CSS作用域自动化: 无需CSS Modules或CSS-in-JS

更多完整代码和注释,参见github仓库的前后端代码,创作不易,感谢支持点赞鼓励😉😉😉


类比前端知识来学习Java的Spring Boot实现MySql的全栈CRUD功能——搭配Svelte+Vite》 是转载文章,点击查看原文


相关推荐


Bash 的 if 条件语句
hubenchang05152025/11/7

#Bash 的 if 条件语句 Bash 的 if 条件语句的语法为: if 条件命令 then 命令 ... elif 条件命令 then 命令 ... else 命令 ... fi 其中,条件命令返回成功(0)时为真(true),返回失败(非 0)时为假(false)。 如果省略(部分)换行,则需要使用分号(;)区分: if 条件命令; then 命令; 命令; elif 条件命令; then 命令; 命令; else 命令; 命令; fi


hive的SQL练习3
普通网友2025/11/3

根据如上表格信息,实现如下需求: 查询五一期间(2023-05-01 ~ 2023-05-07),每个餐厅的订单总数量及排名with t as ( select *,        count(1) over(partition by restaurant_id) countNum        from orders where substr(order_date,1,10)     between '2023-05-01' and '2023-05-07' ) select d


游戏盾是如何保障游戏安全稳定的
上海云盾第一敬业销售2025/10/31

上海云盾SDK游戏盾是如何保障游戏安全稳定的 防攻击保流畅:游戏服务器易遭受 UDP Flood、CC 攻击等针对性威胁,这类攻击会导致服务器负载过高,引发玩家掉线、操作卡顿。游戏盾能深度解析游戏协议,快速识别攻击流量并进行清洗,同时依托多节点部署,将攻击流量引流至防护节点,避免源服务器受冲击,确保游戏全程流畅不中断。 阻外挂护公平:外挂、作弊脚本会破坏游戏内平衡,导致合规玩家失去兴趣,损害游戏生态。游戏盾内置外挂检测机制,可实时监控异常行为,如数据篡改、加速作弊等,通过特征匹配与行为分


前端别再乱存数据了!这3种存储方案让你的应用快如闪电
良山有风来2025/10/28

你是不是也遇到过这样的场景? 用户刚填完一个超长的表单,不小心刷新了页面,所有数据都没了... 从接口请求的数据,用户每次操作都要重新加载,体验卡成PPT... 应用离线状态下完全无法使用,用户直接流失... 别担心!今天我就带你彻底解决这些问题。看完这篇文章,你将掌握一套完整的数据交互方案,让你的应用在任何网络状态下都能流畅运行。 为什么数据存储这么重要? 想象一下,你去超市购物,每次想买什么东西,都要跑回家查一下购物清单,然后再跑回超市... 这得多累啊! 网页应用也是同样的道理。合理的数据


Bash 的变量
hubenchang05152025/10/25

#Bash 的变量 Bash 中的变量定义语法如下: 变量名=值 注意,等号(=)两边不能有空格。 变量名的命名应当遵循如下规则: 只能包含字母,数字和下划线(_),并且不能以数字开头。 不能使用 Bash 保留的关键字,如:if then else fi for while do done 等 环境变量和常量使用全大写字母,单词间使用下划线分隔 普通变量使用全小写字母,单词间使用下划线分隔 函数内的局部变量使用 local 关键字声明 例如: PI=3.1415925 URL="htt


搜索百科(5):Easysearch — 自主可控的国产分布式搜索引擎
极限实验室2025/10/23

大家好,我是 INFINI Labs 的石阳。 欢迎关注 《搜索百科》 专栏!每天 5 分钟,带你速览一款搜索相关的技术或产品,同时还会带你探索它们背后的技术原理、发展故事及上手体验等。 在上一篇我们介绍了 OpenSearch —— 那个因协议争议而诞生的开源搜索分支。今天,我们把目光转向国内,聊聊极限科技研发的一款轻量级搜索引擎:Easysearch。 引言 在搜索技术的世界里,从 Lucene 的出现到 Solr、Elasticsearch 的崛起,搜索引擎技术已经发展了二十余年。然而,随


Swift 字符串与字符完全导读(一):从字面量到 Unicode 的实战之旅
unravel20252025/10/22

前言 Swift 的 String 看起来“像 NSString 的弟弟”,但骨子里是一套全新的 Unicode 抽象模型。 String 与 Character 的本质 String:由“扩展字形簇”(extended grapheme cluster)构成的有序集合。 Character:一个扩展字形簇,人类眼中的“一个字符”,占用的字节数可变。 // 1 个 Character,由 2 个 Unicode 标量合成 let eAcute: Character = "é"


JAVA面试复习笔记(待完善)
paishishaba2025/10/20

目录 布隆过滤器 一、核心思想 二、执行逻辑详解 1. 添加元素 2. 查询元素 三、为什么会有误判? 四、关键参数与性能权衡 五、执行逻辑总结与特点 六、典型应用场景 Redis 的 SETNX 命令 一、基本语法和语义 二、简单示例 三、SETNX 的核心特性 1. 原子性 2. 简单性 3. 无过期时间 四、经典应用场景 1. 分布式锁(最经典的应用) 五、SETNX 的局限性及改进方案 问题1:非原子性的设置过期时间 解决方案:使用 SET 命令


Windows Server,如何使用WSFC+nginx实现集群故障转移
IT橘子皮2025/10/19

在 Windows Server 环境中结合 WSFC(Windows Server Failover Clustering)和 Nginx 实现集群故障转移,核心目标是构建一个既具备应用层高可用性(由 Nginx 负责),又具备基础设施层高可用性(由 WSFC 保障 Nginx 服务本身)的稳固架构。下面这张图清晰地展示了这套架构的完整工作流程: 上图展示了WSFC如何通过心跳检测监控Nginx主节点的状态,并在故障发生时自动将服务(包括虚拟IP和Nginx进程)转移到备节点。下面我们详细拆


AI修图革命:IOPaint+cpolar让废片拯救触手可及
倔强的石头_2025/10/18

文章目录 前言【视频教程】1.什么是IOPaint?2.本地部署IOPaint3.IOPaint简单实用4.公网远程访问本地IOPaint5.内网穿透工具安装6.配置公网地址7.使用固定公网地址远程访问总结 前言 旅行拍照时意外拍到路人闯入?证件照背景不合规?传统修图软件学习成本高,在线工具又担心隐私泄露?IOPaint的出现给出了完美解方——这款开源AI修图工具支持一键擦除多余物体、修复老照片瑕疵,所有操作在本地完成,无需上传原始图片。特别适合摄影爱好者和自媒体创作者,其

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0