一、為什么SpringBoot不推薦使用Mybatis
Spring Boot 不推薦使用 MyBatis,主要源于二者在設計理念、生態融合和開發風格上的差異。Spring Boot 強調“約定優于配置”,追求高效的開發體驗和統一的框架風格。它通過自動配置和依賴注入,將復雜的基礎設施工作隱藏在背后,讓開發者可以專注于業務邏輯。而 MyBatis 更偏向于手動控制,強調 SQL 編寫的靈活性與精確性,這種“顯式配置”的風格在 Spring Boot 的體系中顯得不夠“Spring 化”。
在 Spring Boot 中,JPA(通常基于 Hibernate)得到了默認和深度的支持。它不僅提供了實體類到數據庫表之間的自動映射能力,還能通過接口繼承如 JpaRepository 來實現大部分常見的 CRUD 操作,幾乎不需要寫 SQL。這種面向對象的操作方式非常契合 Spring Boot 的開發理念。而 MyBatis 缺乏這樣的統一接口標準,每一個查詢都必須手動書寫 Mapper 接口和 SQL 映射,導致代碼重復多、維護成本高,不利于構建統一規范的架構。
此外,Spring Boot 的許多生態組件都是圍繞 JPA 構建的。例如 Spring Data、Spring Data REST、Spring Security 等在與 JPA 協同工作時,可以自動識別實體類、權限注解和倉庫接口,實現快速集成與配置。而 MyBatis 在這些方面往往需要額外的手工代碼或第三方擴展來彌補功能差距,使得整個開發過程不夠簡潔和一致。
MyBatis 對復雜 SQL 的支持是它的優勢,但也意味著它無法享受到像 JPA 那樣的自動建表、字段同步、懶加載、級聯操作等特性,這使得在開發初期或者需求頻繁變化的項目中,使用 MyBatis 會顯得繁瑣與低效。同時,它的類型轉換和對象關系映射能力相對較弱,復雜嵌套對象處理起來也需要手動配置,這與 Spring Boot 鼓勵的自動化、低侵入、高復用的理念相悖。
因此,Spring Boot 并不是技術上無法使用 MyBatis,而是在它所倡導的開發模式中,MyBatis 顯得不夠“現代”,不夠“自動”,也不夠“統一”。在快速開發和規范統一的項目場景中,Spring Boot 更愿意推薦使用 JPA 或 Spring Data,只有在 SQL 控制要求高或性能調優需求明顯的項目中,MyBatis 才是一個合適的選擇。這也解釋了為什么 Spring Boot 對 MyBatis 提供了官方 Starter,但始終沒有像支持 JPA 那樣將其設為默認或推薦選項。
二、Mybatis Plus 簡介
MyBatis-Plus 是在 MyBatis 基礎上進行增強的一個持久層框架,旨在簡化 MyBatis 的開發過程,提高效率,降低重復代碼。它并不改變 MyBatis 的核心理念,而是在其上層進行了封裝,使開發者可以更方便地進行 CRUD 操作和復雜查詢。MyBatis 本身需要手動編寫 SQL 和 Mapper 接口,而 MyBatis-Plus 提供了豐富的自動化功能,如內置通用 Mapper、Service、分頁插件、條件構造器等,從而大大減少了模板代碼的數量。
通過集成 MyBatis-Plus,開發者可以僅通過繼承 BaseMapper 接口,立即擁有一整套通用的數據庫操作方法,如 selectById、insert、updateById、deleteById 等,避免了重復的 Mapper 方法定義。此外,它還支持強大的 Lambda 條件構造器,能用鏈式方式編寫類型安全的查詢條件,提升代碼可讀性和可維護性。MyBatis-Plus 還提供分頁插件、樂觀鎖插件、邏輯刪除、SQL 性能分析等實用功能,幫助開發者應對企業級應用中常見的持久層需求。
雖然 MyBatis-Plus 不屬于 Spring Boot 官方推薦的技術棧,但它卻很好地彌補了 MyBatis 開發繁瑣、重復代碼多的問題,使得 MyBatis 在與 Spring Boot 集成時更加現代化和高效。對于那些既想保留 SQL 控制力,又不想從零開始構建 CRUD 接口的團隊來說,MyBatis-Plus 是一個非常實用且易于上手的選擇。它在不改變原有 MyBatis 使用方式的前提下,通過增強功能提升了開發體驗,越來越多的企業項目也因此傾向于在 Spring Boot 中選擇 MyBatis-Plus 作為持久層方案。
三、SpringBoot 集成 Mybatis Plus
1、準備 SpringBoot 項目
創建如上結構
2、導入依賴
<dependencies><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.3.1</version></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
</dependencies>
3、創建實體類
package com.goose.entity;import lombok.*;
import org.springframework.format.annotation.DateTimeFormat;import java.util.Date;@Data
@AllArgsConstructor
// @Getter
// @Setter
// @ToString
@NoArgsConstructor
public class Teacher {private Integer id;private String name;private String gender;private String subject;@DateTimeFormat(pattern = "yyyy-MM-dd")private Date hireDate;
}
4、創建好配置文件
application.yml
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourceurl: jdbc:mysql://localhost:3306/springbootdemousername: rootpassword: 123456# 配置MyBatis日志
mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5、創建配置類
package com.goose.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class MyConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));return interceptor;}
}
這個配置類為 MyBatis-Plus 啟用分頁功能,在執行分頁查詢時自動處理?LIMIT?語句等邏輯,無需手寫 SQL 來分頁。
6、創建mapper 接口
package com.goose.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.goose.entity.Teacher;
import org.springframework.stereotype.Repository;@Repository
public interface TeacherMapper extends BaseMapper<Teacher> {}
7、安裝 lombok 插件
File——Settings——Plugins
搜索找到 lombok 插件,下載安裝后,重啟 IDE
?
Lombok 是一個 Java 編譯器級別的工具庫,它的作用是通過注解的方式,自動為 Java 類生成常見的樣板代碼,如 getter、setter、構造函數、toString、equals、hashCode 等,從而大幅度簡化代碼、提升開發效率、減少冗余。
具體來說,Lombok 插件的主要作用如下:
- 簡化 Getter/Setter 編寫:使用 @Getter 和 @Setter 注解,可以自動為類的字段生成對應的 getter 和 setter 方法,無需手寫。
- 自動生成構造函數:@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor 可以自動生成無參、全參和指定字段的構造函數。
- 簡化 toString/equals/hashCode 方法:使用 @ToString、@EqualsAndHashCode 注解后,不再需要手動重寫這些方法。
- 簡化 Builder 模式編寫:@Builder 可以自動實現鏈式調用構建對象的方式,適用于構造參數多的類。
- 提供 Data 注解一站式生成常見方法:@Data 相當于同時加上 @Getter、@Setter、@ToString、@EqualsAndHashCode、@RequiredArgsConstructor,是實體類開發中非常常用的注解。
- 簡化日志對象創建:@Slf4j、@Log4j 等注解可以自動為類生成對應類型的日志對象。
Lombok 插件的運行機制是在編譯期通過注解處理器修改字節碼,并不會在源碼中直接生成方法,因此你看不到生成的 getter/setter,但它們確實存在于最終編譯后的 class 文件中。Ctrl + F12 進行查看
由于它改寫的是編譯行為,所以在 IDEA 中使用時,必須安裝 Lombok 插件并開啟注解處理器,否則會出現提示找不到方法的錯誤。
8、創建數據庫表
生成測試數據
9、編寫測試類
簡單的CRUD操作可以不用再自己編寫SQL,直接調用BaseMapper 中的方法即可。
這里直接使用持久層進行注入,不涉及到頁面的使用
package com.goose;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.goose.entity.Teacher;
import com.goose.mapper.TeacherMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import java.text.SimpleDateFormat;
import java.util.*;@SpringBootTest
class MybatisDemo1ApplicationTests {@Autowiredpublic TeacherMapper teacherMapper;@Testpublic void findAll() {teacherMapper.selectList(null).forEach(System.out::println);}/* 插入實體對象*/@Testpublic void insert() {Date now = new Date();SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");simpleDateFormat.format(now);Teacher teacher = new Teacher();teacher.setName("李華");teacher.setGender("male");teacher.setSubject("化學");teacher.setHireDate(now);teacherMapper.insert(teacher);}/* 根據主鍵id刪除一條數據*/@Testpublic void deleteById() {teacherMapper.deleteById(16);}/* 傳入實體數據,根據實體中的數據進行刪除*/@Testpublic void deleteById_Entity() {Teacher teacher = new Teacher();teacher.setId(18);teacherMapper.deleteById(teacher);}@Testpublic void deleteByMap() {// string放列名,Object放想要刪除該列中滿足某條件Map<String, Object> map = new HashMap<>();// 這里使用name作為條件map.put("name", "Ivy Ma");map.put("gender", "female");// 刪除所有name 為 Ivy Ma 的教師// 可以多行刪除,但是不會操作具有外鍵約束的內容// DELETE FROM teacher WHERE gender = ? AND name = ?teacherMapper.deleteByMap(map);}@Testpublic void delete() {QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();// eq應該是等于的,le應該是小于,ge應該是大于// teacherQueryWrapper.eq("name","Queena");// DELETE FROM teacher WHERE (name = ?)// teacherMapper.delete(teacherQueryWrapper);teacherQueryWrapper.eq("name", "Queena");teacherQueryWrapper.eq("subject", "Biology");// DELETE FROM teacher WHERE (name = ? AND subject = ?)teacherMapper.delete(teacherQueryWrapper);}@Testpublic void deleteBathIds() {List<Integer> list = new ArrayList<>();list.add(14);list.add(22);list.add(6);list.add(18);// ==> Preparing: DELETE FROM teacher WHERE id IN ( ? , ? , ? , ? )// ==> Parameters: 14(Integer), 22(Integer), 6(Integer), 18(Integer)teacherMapper.deleteBatchIds(list);}@Testpublic void update() {Teacher teacher = new Teacher();teacher.setId(21);teacher.setSubject("English");teacher.setGender("male");// 傳入的對象需要主鍵// ==> Preparing: UPDATE teacher SET gender=?, subject=? WHERE id=?// ==> Parameters: male(String), English(String), 21(Integer)teacherMapper.updateById(teacher);}@Testpublic void updateBy() {QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("subject", "English");Teacher teacher = new Teacher();teacher.setGender("female");// ==> Preparing: UPDATE teacher SET gender=? WHERE (subject = ?)// ==> Parameters: female(String), English(String)teacherMapper.update(teacher, teacherQueryWrapper);}@Testpublic void selectById() {Teacher teacher = teacherMapper.selectById(32);// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE id=?// ==> Parameters: 32(Integer)System.out.println(teacher);}@Testpublic void selectBatchIds() {List<Integer> list = new ArrayList<>();list.add(34);list.add(42);list.add(26);list.add(38);// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE id IN ( ? , ? , ? , ? )// ==> Parameters: 34(Integer), 42(Integer), 26(Integer), 38(Integer)List<Teacher> teachers = teacherMapper.selectBatchIds(list);System.out.println(teachers);}@Testpublic void selectByMap(){Map<String, Object> map = new HashMap<>();map.put("gender","male");map.put("subject","Chemistry");// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE gender = ? AND subject = ?// ==> Parameters: male(String), Chemistry(String)List<Teacher> teachers = teacherMapper.selectByMap(map);System.out.println(teachers);}@Testpublic void selectOne(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("name","Jason");// 只能查一條數據,數據多于一條,拋出錯誤,查詢條件比較嚴格Teacher teacher = teacherMapper.selectOne(teacherQueryWrapper);System.out.println(teacher);}@Testpublic void exist(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("subject","Chemistry");// ==> Preparing: SELECT COUNT( * ) FROM teacher WHERE (subject = ?)boolean exists = teacherMapper.exists(teacherQueryWrapper);System.out.println(exists);}@Testpublic void selectCount(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("gender","female");// ==> Preparing: SELECT COUNT( * ) FROM teacher WHERE (gender = ?)// ==> Parameters: female(String)Long aLong = teacherMapper.selectCount(teacherQueryWrapper);System.out.println(aLong);}@Testpublic void selectList(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("gender","female");// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE (gender = ?)// ==> Parameters: female(String)List<Teacher> teachers = teacherMapper.selectList(teacherQueryWrapper);System.out.println(teachers);}@Testpublic void selectMaps(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("gender","female");// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE (gender = ?)// ==> Parameters: female(String)List<Map<String, Object>> maps = teacherMapper.selectMaps(teacherQueryWrapper);// 把每個Teacher對象拆開了,拆成單獨的map對象System.out.println(maps);}@Testpublic void selectObjs(){QueryWrapper<Teacher> teacherQueryWrapper = new QueryWrapper<>();teacherQueryWrapper.eq("gender","female");// ==> Preparing: SELECT id,name,gender,subject,hire_date FROM teacher WHERE (gender = ?)// ==> Parameters: female(String)List<Object> objects = teacherMapper.selectObjs(teacherQueryWrapper);// [1, 3, 4, 5, 7, 9, 11, 13, 15, 19, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 38, 39, 41, 43, 45, 47, 49]// 只返回第一個字段的值System.out.println(objects);}@Testpublic void testSelectPage() {// 創建分頁對象:第1頁,每頁5條記錄Page<Teacher> page = new Page<>(1, 5);// 構造查詢條件:只查 gender = 'female'QueryWrapper<Teacher> wrapper = new QueryWrapper<>();wrapper.eq("gender", "female");// 執行分頁查詢Page<Teacher> resultPage = teacherMapper.selectPage(page, wrapper);// 打印分頁結果System.out.println("總記錄數: " + resultPage.getTotal());System.out.println("總頁數: " + resultPage.getPages());System.out.println("當前頁數據:");resultPage.getRecords().forEach(System.out::println);}@Testpublic void testSelectMapsPage() {// 分頁參數Page<Map<String, Object>> page = new Page<>(1, 5);// 查詢條件:查 subject = 'Math'QueryWrapper<Teacher> wrapper = new QueryWrapper<>();wrapper.eq("subject", "Math");// 分頁查詢返回 Map 列表Page<Map<String, Object>> resultPage = teacherMapper.selectMapsPage(page, wrapper);// 打印結果System.out.println("總記錄數: " + resultPage.getTotal());System.out.println("總頁數: " + resultPage.getPages());System.out.println("當前頁 Map 數據:");resultPage.getRecords().forEach(System.out::println);}
}