前言
- 本文梳理了后端的相关操作流程环节
- 使用Svelte+Vite(前端)搭配Spring Boot(后端)
- 实现了一个增删改查全栈项目
- 有助于前端更好理解后端java的分层思想,数据流转控制
- 和Svelte尝鲜学习了解
- 完整前后端代码在github:github.com/shuirongshu…
大道至简,一些知识点是相通的比如——python里面也有闭包这个概念
所谓编程即:学习规则语法、理解规则语法、合理运用规则语法、从而自定义规则...
Java、Spring、Spring Boot ≈ Node.js、Express/Koa、Egg.js/NestJS
效果图
仓库代码图
对比理解后端宏观架构流程、数据流转
当我们知道后端具体做了什么事情以后,就更好理解了,即宏观架构流程、数据流转要清晰
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指的是Model、View、Controller
- 简约而言,后端主要做这几件事:
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 8 | Node.js | ⚙️ 运行环境,学Java装JDK,就像学JS装Node |
| Spring && Spring MVC | Express/Koa/Fastify | 🚀 后端基础框架,快速搭建应用服务 |
| Spring Boot 2.7.18 | Egg.js/Nest.js 或 Express/Koa/Fastify + 一堆插件 | 🚀 后端进阶完善的框架,可开箱即用 |
| MyBatis-Plus 3.5.3.1 | Sequelize/Prisma | 🗄️ ORM框架,简化数据库操作,不用手搓sql了 |
| Swagger 3.0.0 + Knife4j 3.0.3 | swagger-ui-express | 📖 API文档自动生成 |
| Hutool 5.8.22 | lodash/day.js | 🛠️ 工具库,提供各种实用函数 |
| Apache POI 4.1.2 | node-xlsx / xlsx | 📊 Excel文件处理,导入导出解析excel的数据 |
| 数据库驱动(JDBC Driver) | mysql或者mysql2 | 🔌 数据库连接 |
| HikariCP | mysql或者mysql2内置的连接池 | 🔌 数据库连接池,管理数据库连接 |
- Maven 就像 npm,
pom.xml就是package.json,依赖管理方式几乎一样!- Java 的包管理比 npm 更严格,但概念相同
- Spring Boot 的注解就像 Vue的自定义指令
后端五件事(简约版)
-
- 定义请求路由接口
-
- 请求参数验证
-
- 业务逻辑处理
-
- 操作数据库
-
- 返回响应数据(JSON / 流)
整体流程
| 流程节点 | 核心操作 |
|---|---|
| 前端 → Nginx → Controller | 前端发请求(如 GET /user/1),Controller 用 UserQueryDTO 接收参数(如 id=1),校验参数合法性 |
| Controller → Service | Controller 调用 Service 方法,传入 UserQueryDTO 或提取后的参数(如 id=1) |
| Service → Mapper | Service 处理业务逻辑(如权限判断),调用 Mapper 方法(如 userMapper.selectById(1)) |
| Mapper → 数据库 | Mapper 执行 SQL,将查询条件(id=1)转为数据库语句,同时通过 Entity 映射表结构(如 User 类对应 user 表) |
| 数据库 → Mapper | 数据库返回结果集,Mapper 自动将结果集转为 User 实体对象 |
| Mapper → Service | Mapper 将 User 实体返回给 Service |
| Service → Controller | Service 将 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())
- @PathVariable注解
- 等很多注解...
注解还可以自定义,有点像函数
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中写入数据 ... 7 8 // 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); 12 13 // 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(T entity):插入一条记录deleteById(Serializable id):根据主键删除updateById(T entity):根据主键更新selectById(Serializable id):根据主键查询selectList(Wrapper<T> queryWrapper):条件查询列表
所以可以直接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 框架的核心优势
- 简化开发:不用写 SQL,减少重复工作(比如拼接 SQL、解析结果),开发效率大幅提升;
- 屏蔽数据库差异:同一套代码,通过 ORM 适配 MySQL、PostgreSQL、SQLite 等不同数据库(ORM 负责翻译不同数据库的 SQL 语法);
- 降低学习成本:不用精通各种数据库的 SQL 细节,专注于面向对象编程;
- 安全性更高:自动防止 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 仍应隔离
| 维度 | Entity | DTO | VO(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语法对比:
- 更简洁的语法: 无需
useState、useEffect等Hook - 真正的响应式: 直接赋值触发更新,不需要
setState() - 更少的样板代码: 无需频繁的解构和回调
- 更小的包体积: 编译时优化,运行时更轻量
- 内置动画支持:
transition、animate指令 - CSS作用域自动化: 无需CSS Modules或CSS-in-JS
更多完整代码和注释,参见github仓库的前后端代码,创作不易,感谢支持点赞鼓励😉😉😉
《类比前端知识来学习Java的Spring Boot实现MySql的全栈CRUD功能——搭配Svelte+Vite》 是转载文章,点击查看原文。
