前言
- 本文梳理了后端的相关操作流程环节
- 使用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
-
简约而言,后端主要做这几件事:
- 定义请求路由接口 (C路由)
- 请求参数验证 (C参数验证)
- 业务逻辑处理 (M业务逻辑)
- 操作数据库 (M业务逻辑)
- 返回响应数据JSON、下载返回流文件 (C路由返回 V视图概念消失弱化)
-
前后端不分离JSP时代,MVC基本后端做。即:
- 过去: 后端 = M + C + V (渲染HTML)
- 现在: 后端 = 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命令调用如下
json
curl -X POST http://localhost:8080/people \
-H "Content-Type: application/json" \
-d '{"age": 20, "home": "string", "name": "string", "remark": "string"}'
Controller控制层
PeopleController.java
java
import com.people_sys.people.service.PeopleService; // 导入业务服务层 PeopleService
// @RestController = @Controller + @ResponseBody = 返回JSON格式的数据
@RestController
@RestController // REST风格的接口
@RequestMapping("/people") // 接口请求url 统一请求前缀/people
public class PeopleController { // PeopleController类
// 定义变量存储peopleService
private final PeopleService peopleService;
// 构造器注入(初始化执行)
// 也可以使用注解 @Resource 或 @Autowired 一步到位
public PeopleController(PeopleService peopleService) {
this.peopleService = peopleService; // 存起来
}
// 新增人员 - POST /people
@PostMapping
public ApiResponse<Boolean> create(@Valid @RequestBody PeopleDTO people) throws Exception {
// 调用peopleService层的create方法新增用户人员
boolean result = peopleService.create(people);
return ApiResponse.success(result);
}
// 根据id查询 - GET /people/{id}
@GetMapping("/{id}")
public ApiResponse<People> getById(@PathVariable Integer id) {
// 调用peopleService层的getById方法,根据id查询对应人员的
People people = peopleService.getById(id);
return ApiResponse.success(people);
}
......
}
何为注解&常见的注解举例
简而言之:
- 注解有点像前端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) // 数值范围
-
跨域注解
java
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
// 仅当前接口支持跨域,允许所有来源(*)
@CrossOrigin
@GetMapping("/api/test")
public String testCrossOrigin() {
return "跨域请求成功!";
}
}
-
拿到前端传参的注解
-
@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这个类统一控制
java
package com.people_sys.people.config;
import lombok.Data;
@Data // 自动生成getter/setter
public class ApiResponse<T> {
private int code; // 状态码(如200成功,400参数错误,500系统错误)
private String message; // 响应消息(成功时为"success",失败时为错误信息)
private T data; // 业务数据(成功时返回)
public ApiResponse(int code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
// 成功响应
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data);
}
// 错误响应
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null);
}
}
前端查询id为300的这条数据,http://localhost:8080/people/300返回
js
{
"code": 200,
"message": "success",
"data": {
"id": 300,
"name": "张三",
"age": 3,
"home": "山东",
"remark": "zhangsan",
"delFlag": 0,
"createTime": "2025-11-04T14:48:05",
"updateTime": "2025-11-04T17:23:32"
}
}
ApiResponse实际上就是一个公共函数,统一加工处理数据的
返回流文件给前端下载
动态生成Excel并下载(伪代码示例)
java
@GetMapping("/downloadExcel")
public void downloadExcel(HttpServletResponse response) throws IOException {
// 1. 动态生成Excel(使用POI等工具)
XSSFWorkbook workbook = new XSSFWorkbook(); // POI的Excel对象
XSSFSheet sheet = workbook.createSheet("Sheet1");
// ... 向sheet中写入数据 ...
// 2. 设置响应头(同上)
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
String fileName = URLEncoder.encode("动态生成的Excel.xlsx", "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName);
// 3. 直接将工作簿写入响应流
workbook.write(response.getOutputStream());
workbook.close(); // 关闭资源
}
workbook.write(response.getOutputStream()) 意思就是:将内存中动态生成的文件(如 Excel)以二进制流的形式写入 HTTP 响应输出流,最终返回给前端,以便于前端使用a标签实现文件下载
2. 请求参数验证 (Controller层)
数据新增接口细节拆解
接下来,看对应新增接口注释,新增接口前端Post的Body传参为
people: { name: 'tom', age: 2, home: 'New York', 'remark': 'xyz' }
java
// 1. 接口请求类型注解,等价于:@RequestMapping(method = RequestMethod.POST)
@PostMapping
// 2. public公开的create方法,允许其他类调用(若写成private,Spring无法扫描到这个接口,前端会访问失败)
public ApiResponse<Boolean> create( // 方法名叫做create
// 3. 数据校验触发注解(想要使用PeopleDTO里面的校验,必须要使用@Valid标明开启校验)
@Valid
// 4. 请求体接收注解,通过这个可以拿到前端请求体里面的参数,并将其赋值给people参数
@RequestBody
// 5. 方法参数(DTO 实体 + 参数名)
PeopleDTO people // people为函数的形参存储的前端参数
) throws Exception { // 6. 异常则抛出声明
// 7. 业务逻辑:把前端传递进来的people对象参数,调用 Service 层新增方法,得到布尔类型结果
boolean result = peopleService.create(people);
// 8. 返回统一响应结果,新增成功返回 {"code":200,"message":"success","data":true}
return ApiResponse.success(result);
}
通过@RequestBody 可以拿到前端参数 存到people变量里面,然后交由peopleService.create方法去做业务逻辑处理进而写入数据库
但是,前端可能乱传参,所以,需要搭配PeopleDTO和 @Valid 进行校验一下
什么是DTO,能做什么
简而言之:DTO就是规范接收前端传参、规范返回接口数据
新增的DTO
java
package com.people_sys.people.dto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
public class PeopleDTO {
@NotBlank(message = "姓名不能为空") // 必传,字符串类型
private String name;
@NotNull(message = "年龄不能为空") // 必传,数字类型
private Integer age;
private String home; // 非必传
private String remark; // 非必传
}
编辑的DTO多了一个id,其他和新增一样(毕竟编辑要找到对应id再去编辑)
java
@Data
public class PeopleUpdateDTO {
@NotNull(message = "ID不能为空")
private Integer id;
@NotBlank(message = "姓名不能为空")
private String name;
...
}
我们可以把DTO看成一个工具函数,在接到前端传参的时候,做规范限制------过滤掉不需要的参数,校验是否符合传参要求
用JavaScript模拟DTO功能
假设,我们要求前端传参规则是这样的:
name字段必传且为字符串类型、age字段非必传(若传了必须要是数字类型),若多传则忽略之
java
const Dto = {
name: {
type: "string",
required: true,
},
age: {
type: "number",
required: false,
},
}
- 假设用户传递
{ name: '孙悟空', age: 500, home: '花果山' }那么经过DTO处理以后,能得到{ name: '孙悟空', age: 500}多传的home字段和其值,被忽略 - 假设用户传递
{ age: 500 }那么经过DTO处理以后,要校验提示name是必传的 - 假设用户传递
{ name: true }那么经过DTO处理以后,要校验提示name的数据类型不对
于是,就可以有以下模拟代码
js
/**
* 模拟定义DTO
* 姓名为字符串,必传
* 年龄为数字类型,非必传
* */
const dtoDef = (params) => {
// 定义Dto字段规则
const Dto = {
name: {
type: "string",
required: true,
},
age: {
type: "number",
required: false,
},
}
/**
* 1. 必传字段校验
* */
const mustHaveKeys = []
// 1.1 收集那些字段是必传的
for (const key in Dto) {
if (Dto[key].required) {
mustHaveKeys.push(key)
}
}
// 1.2 收集传递进来的key组成的数组
const paramsKeys = Object.keys(params)
// 1.3 看看是否每一个必传字段,都在参数key数组里面
const flag = mustHaveKeys.every((mk) => paramsKeys.includes(mk))
// 1.4 必传参数校验
if (!flag) {
console.warn(`必传字段缺失,必传字段有这些:${mustHaveKeys.join(",")}`)
return false
}
/**
* 2. 字段类型校验
* */
const resDto = {}
for (const key in params) {
// 在Dto里的做对应校验
if (key in Dto) {
// 类型校验
if (typeof params[key] === Dto[key].type) {
// 校验通过则转存一份
resDto[key] = params[key]
} else {
console.warn(`字段${key}类型错误,类型应为${Dto[key].type}`)
return false
}
}
// 不在Dto里面的忽略,这样resDto里存的就是定义好的
else { }
}
return resDto
}
const par = { name: '孙悟空', age: 500, home: '花果山' }
// 经过dtoDef的校验、过滤就能得到符合要求的dto了
const result = dtoDef(par)
console.log('result', result) // {name: '孙悟空', age: 500}
-
在java中,dto定义好以后,可在接受前端参数的时候使用
- 只拿自己需要的字段值
- 使用注解快速校验前端传参
-
也可在从数据库捞出数据返回给前端的时候使用
- 比如返回的时候,password和gender字段作为保密,不返回给前端
- 只返回能返回的数据
所以,再总结一下:DTO就是规范接收前端传参、规范返回接口数据的一个工具
问:如果有一些额外的字段,要返回给前端,又不是前端要传递的字段,怎么定义呢?比如返数据时,需加一个字段isAdults来确认是否成年(规则是大于18岁成年为true,小于则为false)
答:这个时候,VO就闪亮登场了
VO是最终返回给前端数据结构格式
总结:VO是最终返回给前端数据结构格式------如果小项目,直接用dto也行(可以看成把dto当成vo用)
java
@Data
public class PeopleVO {
private Long id; // 额外字段:数据库主键(前端不用传,要展示)
// 复用 DTO 中的字段
private String name;
private Integer age;
private String home;
private String remark;
private Boolean isAdults; // 额外字段:计算后信息,是否成年
}
3. 业务逻辑处理 & 4.操作数据库
先假设没有复杂业务逻辑处理,直接把前端传递的数据,存到数据库里,就需要编写sql语句
古法手搓sql
现在接口定义好了,且用DTO规范了前端传参,接下来应该把前端传递来的参数转成sql语句
比如,新增一条数据:{ "name": "tom", "age": 18 }
java
public void addPerson(String name, int age) { // 拿到前端传进来的参数name和age
// 拼接手搓sql
String sql = "INSERT INTO people (name, age) VALUES (?, ?)";
// 调用Spring的jdbcTemplate类的update方法新增数据
int rowsAffected = jdbcTemplate.update(sql, name, age);
// 成功插入 1 条记录 → 返回 1
if (rowsAffected > 0) {
System.out.println("成功插入 " + rowsAffected + " 条记录");
}
}
但是这个手搓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框架),则这样写即可
java
public void addPerson(String name, int age) {
// 创建实体对象并设置参数
Person person = new Person();
person.setName(name);
person.setAge(age);
// 调用 MyBatis-Plus 的 insert 方法插入数据
int rowsAffected = personMapper.insert(person);
if (rowsAffected > 0) {
System.out.println("成功插入 " + rowsAffected + " 条记录");
}
}
这里的personMapper继承自BaseMapper(自带一套通用的crud的方法)如
insert(T entity):插入一条记录deleteById(Serializable id):根据主键删除updateById(T entity):根据主键更新selectById(Serializable id):根据主键查询selectList(Wrapper<T> queryWrapper):条件查询列表
所以可以直接insert数据
java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
// 定义一个接口PersonMapper继承了BaseMapper上所用功能,比如crud的api
public interface PersonMapper extends BaseMapper<Person> {
// 无需编写任何方法,BaseMapper 已提供 CRUD 基础功能
}
注意,
BaseMapper<Person>中的泛型<Person>就是告诉 MyBatis-Plus:这个 Mapper 要操作的是与Person类绑定的那张表(即people表)
所以,一定要告诉 MyBatis-Plus要操作那张表,怎么告诉呢 就通过entity告诉
所以,这里的<Person>就是一个entity,如下
java
@Data // 无需手动编写 getter、setter 等方法,@Data 会自动生成
@TableName("people") // 映射数据库表名------告诉mybatis,是那张表
public class Person {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String name;
private Integer age;
}
所以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
java
// DTO(接收前端)
public class PersonDTO {
private String name;
private Integer age;
// getter/setter
}
我们会发现,dto中的东西不够用,毕竟DTO只是用来定义传输的部分数据,完整数据还是在表里。但是orm必须要知道完整数据,才方便操作数据库
而我们又不能把所有信息都丢到dto里面
所以,需要一个新的东西,来告知orm,完整的表、字段、数据类型是啥。用对象(类)的形式告知,映射数据库,于是就有了Entity这个文件
java
// Entity(映射数据库)
@TableName("people") // 告知表名字
public class People {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String name;
private Integer age;
// getter/setter
}
现在orm知道了操作的表名是people,和表里的对应字段信息,那么orm就方便操作数据库中的表了
java
// 3. Service 层
public void addPerson(PersonDTO dto) {
People people = new People();
people.setName(dto.getName());
people.setAge(dto.getAge());
// 无需写 SQL!ORM 自动 INSERT
boolean saved = peopleService.save(people);
if (!saved) {
throw new BusinessException("保存失败");
}
}
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. 业务逻辑之新增的人员不能重名
java
@Override
// 新增人员的校验:新增的人名字不能和数据库中已经有的人名字重复
public boolean create(PeopleDTO dto) throws Exception {
// 校验名字是否重复
if (lambdaQuery()
.eq(People::getName, dto.getName())
.eq(People::getDelFlag, 0)
.count() > 0) {
throw new BusinessException(400, "人员姓名已存在,请使用其他姓名");
}
People entity = BeanUtil.copyProperties(dto, People.class);
return save(entity);
}
这里的save也是mybatis-plus提供的,比直接insert更加智能
由于我们的业务逻辑是------新增的人名字不能和数据库中已经有的人名字重复,所以,在写入数据库之前,还需要编写sql查询一下,数据库中有多少条数据,和当前人名一样。
Mybatis也提前准备好了lambdaQuery()以供我们进行链式调用,进行条件构造链式查询,语法简洁
若使用手写sql,则是如下写法
java
@Autowired
private JdbcTemplate jdbcTemplate; // Spring 的 JDBC 工具类
public void checkDuplicateName(String name) {
// 手写 SQL 字符串
String sql = "SELECT COUNT(*) FROM people WHERE name = ? AND del_flag = 0";
// 执行查询,获取记录数
Integer count = jdbcTemplate.queryForObject(
sql,
new Object[]{name}, // 绑定参数(姓名)
Integer.class // 返回类型
);
// 判断并抛异常
if (count != null && count > 0) {
throw new BusinessException(400, "人员姓名已存在,请使用其他姓名");
}
}
由此可见,当真是使用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 - 也有条件渲染,如
js
{#if condition}
<!-- 内容 -->
{:else}
<!-- 其他内容 -->
{/if}
- 也有循环v-for、map渲染
js
{#each items as item (item.id)}
<!-- 循环内容 -->
{/each}
- 也可组件化开发项目(单文件直接和vue类似,直接
<script>、<style>、HTML模板) - 也可以import和export
- 分页组件父组件传对象和做事件处理
<Page {pageInfo} on:pageChange={handlePageChange} /> - 对应子组件接收是
js
// Props - 接收整个分页信息对象
export let pageInfo = {
currentPage: 1,
pageSize: 10,
total: 0,
};
- 子组件触发父组件使用
createEventDispatcher,如
js
import { createEventDispatcher } from "svelte";
// 创建事件分发器
const dispatch = createEventDispatcher();
// 按钮点击的回调
dispatch("pageChange", newPage);
比如和React语法对比:
- 更简洁的语法 : 无需
useState、useEffect等Hook - 真正的响应式 : 直接赋值触发更新,不需要
setState() - 更少的样板代码: 无需频繁的解构和回调
- 更小的包体积: 编译时优化,运行时更轻量
- 内置动画支持 :
transition、animate指令 - CSS作用域自动化: 无需CSS Modules或CSS-in-JS
更多完整代码和注释,参见github仓库的前后端代码,创作不易,感谢支持点赞鼓励😉😉😉