前言
- 本文梳理了後端的相關操作流程環節
- 使用Svelte+Vite(前端)搭配Spring Boot(後端)
- 實現了一個增刪改查全棧項目
- 有助於前端更好理解後端java的分層思想,數據流轉控制
- 和Svelte嚐鮮學習瞭解
- 完整前後端代碼在github:https://github.com/shuirongshuifu/svelte-springBoot-crud
大道至簡,一些知識點是相通的比如——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命令調用如下
<!---->
curl -X POST http://localhost:8080/people \
-H "Content-Type: application/json" \
-d '{"age": 20, "home": "string", "name": "string", "remark": "string"}'
Controller控制層
PeopleController.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) // 數值範圍
- 跨域註解
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這個類統一控制
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返回
{
"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並下載(偽代碼示例)
@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' }
// 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
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再去編輯)
@Data
public class PeopleUpdateDTO {
@NotNull(message = "ID不能為空")
private Integer id;
@NotBlank(message = "姓名不能為空")
private String name;
...
}
我們可以把DTO看成一個工具函數,在接到前端傳參的時候,做規範限制——過濾掉不需要的參數,校驗是否符合傳參要求
用JavaScript模擬DTO功能
假設,我們要求前端傳參規則是這樣的:
name字段必傳且為字符串類型、age字段非必傳(若傳了必須要是數字類型),若多傳則忽略之
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的數據類型不對
於是,就可以有以下模擬代碼
/**
* 模擬定義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用)
@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 }
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框架),則這樣寫即可
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數據
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,如下
@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
// DTO(接收前端)
public class PersonDTO {
private String name;
private Integer age;
// getter/setter
}
我們會發現,dto中的東西不夠用,畢竟DTO只是用來定義傳輸的部分數據,完整數據還是在表裏。但是orm必須要知道完整數據,才方便操作數據庫
而我們又不能把所有信息都丟到dto裏面
所以,需要一個新的東西,來告知orm,完整的表、字段、數據類型是啥。用對象(類)的形式告知,映射數據庫,於是就有了Entity這個文件
// Entity(映射數據庫)
@TableName("people") // 告知表名字
public class People {
@TableId(type = IdType.AUTO) // 主鍵自增
private Long id;
private String name;
private Integer age;
// getter/setter
}
現在orm知道了操作的表名是people,和表裏的對應字段信息,那麼orm就方便操作數據庫中的表了
// 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. 業務邏輯之新增的人員不能重名
@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,則是如下寫法
@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 - 也有條件渲染,如
{#if condition}
<!-- 內容 -->
{:else}
<!-- 其他內容 -->
{/if}
- 也有循環v-for、map渲染
{#each items as item (item.id)}
<!-- 循環內容 -->
{/each}
- 也可組件化開發項目(單文件直接和vue類似,直接
<script>、<style>、HTML模板) - 也可以import和export
- 分頁組件父組件傳對象和做事件處理
<Page {pageInfo} on:pageChange={handlePageChange} /> - 對應子組件接收是
// Props - 接收整個分頁信息對象
export let pageInfo = {
currentPage: 1,
pageSize: 10,
total: 0,
};
- 子組件觸發父組件使用
createEventDispatcher,如
import { createEventDispatcher } from "svelte";
// 創建事件分發器
const dispatch = createEventDispatcher();
// 按鈕點擊的回調
dispatch("pageChange", newPage);
比如和React語法對比:
- 更簡潔的語法: 無需
useState、useEffect等Hook - 真正的響應式: 直接賦值觸發更新,不需要
setState() - 更少的樣板代碼: 無需頻繁的解構和回調
- 更小的包體積: 編譯時優化,運行時更輕量
- 內置動畫支持:
transition、animate指令 - CSS作用域自動化: 無需CSS Modules或CSS-in-JS
更多完整代碼和註釋,參見github倉庫的前後端代碼,創作不易,感謝支持點贊鼓勵😉😉😉