博客 / 詳情

返回

類比前端知識來學習Java的Spring Boot實現MySql的全棧CRUD功能——搭配Svelte+Vite

前言

  • 本文梳理了後端的相關操作流程環節
  • 使用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指的是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 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的自定義指令

後端五件事(簡約版)

    1. 定義請求路由接口
    1. 請求參數驗證
    1. 業務邏輯處理
    1. 操作數據庫
    1. 返回響應數據(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 框架的核心優勢

  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 仍應隔離

維度 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語法對比:

  1. 更簡潔的語法: 無需 useStateuseEffect 等Hook
  2. 真正的響應式: 直接賦值觸發更新,不需要 setState()
  3. 更少的樣板代碼: 無需頻繁的解構和回調
  4. 更小的包體積: 編譯時優化,運行時更輕量
  5. 內置動畫支持: transitionanimate 指令
  6. CSS作用域自動化: 無需CSS Modules或CSS-in-JS
更多完整代碼和註釋,參見github倉庫的前後端代碼,創作不易,感謝支持點贊鼓勵😉😉😉
user avatar lantianhaijiao 頭像 nxmin 頭像 xiaoqianduan_58b28cfebff36 頭像 nihaoanihao 頭像 myriam-frisano 頭像
5 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.