一、MyBatis-Plus
1.1 簡介
MyBatis-Plus (opens new window)(簡稱 MP)是一個 MyBatis (opens new window)的增強工具,在 MyBatis 的基礎上只做增強不做改變,爲簡化開發、提高效率而生。
我們的願景是成爲 MyBatis 最好的搭檔,就像 魂鬥羅 中的 1P、2P,基友搭配,效率翻倍。
1.2 特性
- 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
- 損耗小:啓動即會自動注入基本 CURD,性能基本無損耗,直接面向對象操作
- 強大的 CRUD 操作:內置通用 Mapper、通用 Service,僅僅通過少量配置即可實現單表大部分 CRUD 操作,更有強大的條件構造器,滿足各類使用需求
- 支持 Lambda 形式調用:通過 Lambda 表達式,方便的編寫各類查詢條件,無需再擔心字段寫錯
- 支持主鍵自動生成:支持多達 4 種主鍵策略(內含分佈式唯一 ID 生成器 - Sequence),可自由配置,完美解決主鍵問題
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式調用,實體類只需繼承 Model 類即可進行強大的 CRUD 操作
- 支持自定義全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 內置代碼生成器:採用代碼或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 層代碼,支持模板引擎,更有超多自定義配置等您來使用
- 內置分頁插件:基於 MyBatis 物理分頁,開發者無需關心具體操作,配置好插件之後,寫分頁等同於普通 List 查詢
- 分頁插件支持多種數據庫:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多種數據庫
- 內置性能分析插件:可輸出 SQL 語句以及其執行時間,建議開發測試時啓用該功能,能快速揪出慢查詢
- 內置全局攔截插件:提供全表 delete 、 update 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
1.3 支持數據庫
任何能使用
MyBatis
進行 CRUD, 並且支持標準 SQL 的數據庫,具體支持情況如下,如果不在下列表查看分頁部分教程 PR 您的支持。
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
- 達夢數據庫,虛谷數據庫,人大金倉數據庫,南大通用(華庫)數據庫,南大通用數據庫,神通數據庫,瀚高數據庫
1.4 框架結構
1.5 官方地址
二、入門案例
2.1 開發環境
- IDE:IDEA 2019.3.5
- JDK:JDK8+
- 構建工具:Maven 3.5.4
- MySQL:MySQL 8.0.24
- Navicat:Navicat Premium 15
- Spring Boot:2.6.7
- MyBatis-Plus:3.5.1
2.2 建庫建表
-
打開Navicat運行以下SQL腳本進行建庫建表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `mybatis_plus`; CREATE TABLE `user` ( `id` bigint(20) NOT NULL COMMENT '主鍵ID', `name` varchar(30) DEFAULT NULL COMMENT '姓名', `age` int(11) DEFAULT NULL COMMENT '年齡', `email` varchar(50) DEFAULT NULL COMMENT '郵箱', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
插入幾條測試數據
INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, '[email protected]'), (2, 'Jack', 20, '[email protected]'), (3, 'Tom', 28, '[email protected]'), (4, 'Sandy', 21, '[email protected]'), (5, 'Billie', 24, '[email protected]');
2.3 創建工程
-
使用
Spring Initializer
快速初始化一個 Spring Boot 工程 -
引入
MyBatis-Plus
的依賴<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency>
-
安裝
Lombok
插件
2.4 配置編碼
-
配置
application.yml
文件#配置端口 server: port: 80 spring: #配置數據源 datasource: #配置數據源類型 type: com.zaxxer.hikari.HikariDataSource #配置連接數據庫的信息 driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false username: {username} password: {password} #MyBatis-Plus相關配置 mybatis-plus: configuration: #配置日誌 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-
在 Spring Boot 啓動類中添加
@MapperScan
註解,掃描 Mapper 文件夾@SpringBootApplication @MapperScan("指定Mapper接口所在的包") public class MybatisPlusDemoApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusDemoApplication.class, args); } }
-
編寫實體類
User.java
(此處使用了 Lombok 簡化代碼)@Data public class User { private Long id; private String name; private Integer age; private String email; }
-
編寫 Mapper 包下的
UserMapper
接口public interface UserMapper extends BaseMapper<User> {}
2.5 測試查詢
-
編寫一個測試類
MyBatisPlusTest.java
@SpringBootTest public class MyBatisPlusTest { @Resource private UserMapper userMapper; /** * 測試查詢所有數據 */ @Test void testSelectList(){ //通過條件構造器查詢一個list集合,若沒有條件,則可以設置null爲參數 List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
-
控制檯打印查詢結果
三、增刪改查
3.1 BaseMapper<T>
說明:
- Mapper 繼承該接口後,無需編寫 mapper.xml 文件,即可獲得CRUD功能
- 通用 CRUD 封裝BaseMapper 接口,爲
Mybatis-Plus
啓動時自動解析實體表關係映射轉換爲Mybatis
內部對象注入容器- 泛型
T
爲任意實體對象- 參數
Serializable
爲任意類型主鍵Mybatis-Plus
不推薦使用複合主鍵約定每一張表都有自己的唯一id
主鍵- 對象
Wrapper
爲條件構造器
MyBatis-Plus中的基本CRUD在內置的BaseMapper中都已得到了實現,因此我們繼承該接口以後可以直接使用。
本次演示的CRUD操作不包含參數帶有條件構造器的方法,關於條件構造器將單獨在一個章節進行演示。
BaseMapper中提供的CRUD方法:
-
增加:Insert
// 插入一條記錄 int insert(T entity);
-
刪除:Delete
// 根據 entity 條件,刪除記錄 int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper); // 刪除(根據ID 批量刪除) int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根據 ID 刪除 int deleteById(Serializable id); // 根據 columnMap 條件,刪除記錄 int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
-
修改:Update
// 根據 whereWrapper 條件,更新記錄 int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper); // 根據 ID 修改 int updateById(@Param(Constants.ENTITY) T entity);
-
查詢:Selete
// 根據 ID 查詢 T selectById(Serializable id); // 根據 entity 條件,查詢一條記錄 T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查詢(根據ID 批量查詢) List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList); // 根據 entity 條件,查詢全部記錄 List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查詢(根據 columnMap 條件) List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap); // 根據 Wrapper 條件,查詢全部記錄 List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢全部記錄。注意: 只返回第一個字段的值 List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 entity 條件,查詢全部記錄(並翻頁) IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢全部記錄(並翻頁) IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢總記錄數 Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
3.2 調用Mapper層實現CRUD
3.2.1 插入
最終執行的結果,所獲取的id爲1527206783590903810
這是因爲MyBatis-Plus在實現插入數據時,會默認基於雪花算法的策略生成id
/**
* 測試插入一條數據
* MyBatis-Plus在實現插入數據時,會默認基於雪花算法的策略生成id
*/
@Test
public void testInsert(){
User user = new User();
user.setName("Vz");
user.setAge(21);
user.setEmail("[email protected]");
int result = userMapper.insert(user);
System.out.println(result > 0 ? "添加成功!" : "添加失敗!");
System.out.println("受影響的行數爲:" + result);
//1527206783590903810(當前 id 爲雪花算法自動生成的id)
System.out.println("id自動獲取" + user.getId());
}
3.2.2 刪除
a、根據ID刪除數據
調用方法:int deleteById(Serializable id);
/**
* 測試根據id刪除一條數據
*/
@Test
public void testDeleteById(){
int result = userMapper.deleteById(1527206783590903810L);
System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
System.out.println("受影響的行數爲:" + result);
}
b、根據ID批量刪除數據
調用方法:int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 測試通過id批量刪除數據
*/
@Test
public void testDeleteBatchIds(){
List<Long> ids = Arrays.asList(6L,7L,8L);
int result = userMapper.deleteBatchIds(ids);
System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
System.out.println("受影響的行數爲:" + result);
}
c、根據Map條件刪除數據
調用方法:int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 測試根據Map集合中所設置的條件刪除數據
*/
@Test
public void testDeleteByMap(){
//當前演示爲根據name和age刪除數據
//執行SQL爲:DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> map = new HashMap<>();
map.put("name","Vz");
map.put("age",21);
int result = userMapper.deleteByMap(map);
System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
System.out.println("受影響的行數爲:" + result);
}
2.3 修改
調用方法:int updateById(@Param(Constants.ENTITY) T entity);
/**
* 測試根據id修改用戶信息
*/
@Test
public void testUpdateById(){
//執行SQL爲: UPDATE user SET name=?, age=?, email=? WHERE id=?
User user = new User();
user.setId(6L);
user.setName("VzUpdate");
user.setAge(18);
user.setEmail("[email protected]");
int result = userMapper.updateById(user);
System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
System.out.println("受影響的行數爲:" + result);
}
2.4 查詢
a、根據ID查詢用戶信息
調用方法:T selectById(Serializable id);
/**
* 測試根據id查詢用戶數據
*/
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
b、根據多個ID查詢多個用戶信息
調用方法:List
selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 根據多個id查詢用戶數據
*/
@Test
public void testSelectBatchIds(){
//執行SQL爲:SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
List<Long> ids = Arrays.asList(1L,2L,3L);
List<User> users = userMapper.selectBatchIds(ids);
users.forEach(System.out::println);
}
c、根據Map條件查詢用戶信息
調用方法:List
selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根據Map所設置的條件查詢用戶
*/
@Test
public void testSelectByMap(){
//執行SQL爲:SELECT id,name,age,email FROM user WHERE age = ?
Map<String,Object> map = new HashMap<>();
map.put("age",18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
d、查詢所有用戶信息
調用方法:List
selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper);
/**
* 測試查詢所有數據
*/
@Test
void testSelectList(){
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
3.3 通用Service
說明:
- 通用 Service CRUD 封裝
IService
接口,進一步封裝 CRUD 採用get 查詢單行
remove 刪除
list 查詢集合
page 分頁
前綴命名方式區分Mapper
層避免混淆,- 泛型
T
爲任意實體對象- 建議如果存在自定義通用 Service 方法的可能,請創建自己的
IBaseService
繼承Mybatis-Plus
提供的基類- 對象
Wrapper
爲 條件構造器
MyBatis-Plus中有一個接口 IService
和其實現類 ServiceImpl
,封裝了常見的業務層邏輯,詳情查看源碼IService和ServiceImpl
因此我們在使用的時候僅需在自己定義的Service
接口中繼承IService
接口,在自己的實現類中實現自己的Service並繼承ServiceImpl
即可
IService中的CRUD方法
-
增加:Save、SaveOrUpdate
// 插入一條記錄(選擇字段,策略插入) boolean save(T entity); // 插入(批量) boolean saveBatch(Collection<T> entityList); // 插入(批量) boolean saveBatch(Collection<T> entityList, int batchSize); // TableId 註解存在更新記錄,否插入一條記錄 boolean saveOrUpdate(T entity); // 根據updateWrapper嘗試更新,否繼續執行saveOrUpdate(T)方法 boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList); // 批量修改插入 boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
-
刪除:Remove
// 根據 entity 條件,刪除記錄 boolean remove(Wrapper<T> queryWrapper); // 根據 ID 刪除 boolean removeById(Serializable id); // 根據 columnMap 條件,刪除記錄 boolean removeByMap(Map<String, Object> columnMap); // 刪除(根據ID 批量刪除) boolean removeByIds(Collection<? extends Serializable> idList);
-
修改:Update
// 根據 UpdateWrapper 條件,更新記錄 需要設置sqlset boolean update(Wrapper<T> updateWrapper); // 根據 whereWrapper 條件,更新記錄 boolean update(T updateEntity, Wrapper<T> whereWrapper); // 根據 ID 選擇修改 boolean updateById(T entity); // 根據ID 批量更新 boolean updateBatchById(Collection<T> entityList); // 根據ID 批量更新 boolean updateBatchById(Collection<T> entityList, int batchSize);
-
查詢:Get、List、Count
// 根據 ID 查詢 T getById(Serializable id); // 根據 Wrapper,查詢一條記錄。結果集,如果是多個會拋出異常,隨機取一條加上限制條件 wrapper.last("LIMIT 1") T getOne(Wrapper<T> queryWrapper); // 根據 Wrapper,查詢一條記錄 T getOne(Wrapper<T> queryWrapper, boolean throwEx); // 根據 Wrapper,查詢一條記錄 Map<String, Object> getMap(Wrapper<T> queryWrapper); // 根據 Wrapper,查詢一條記錄 <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); // 查詢所有 List<T> list(); // 查詢列表 List<T> list(Wrapper<T> queryWrapper); // 查詢(根據ID 批量查詢) Collection<T> listByIds(Collection<? extends Serializable> idList); // 查詢(根據 columnMap 條件) Collection<T> listByMap(Map<String, Object> columnMap); // 查詢所有列表 List<Map<String, Object>> listMaps(); // 查詢列表 List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper); // 查詢全部記錄 List<Object> listObjs(); // 查詢全部記錄 <V> List<V> listObjs(Function<? super Object, V> mapper); // 根據 Wrapper 條件,查詢全部記錄 List<Object> listObjs(Wrapper<T> queryWrapper); // 根據 Wrapper 條件,查詢全部記錄 <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper); // 查詢總記錄數 int count(); // 根據 Wrapper 條件,查詢總記錄數 int count(Wrapper<T> queryWrapper);
-
分頁:Page
// 根據 ID 查詢 T getById(Serializable id); // 根據 Wrapper,查詢一條記錄。結果集,如果是多個會拋出異常,隨機取一條加上限制條件 wrapper.last("LIMIT 1") T getOne(Wrapper<T> queryWrapper); // 根據 Wrapper,查詢一條記錄 T getOne(Wrapper<T> queryWrapper, boolean throwEx); // 根據 Wrapper,查詢一條記錄 Map<String, Object> getMap(Wrapper<T> queryWrapper); // 根據 Wrapper,查詢一條記錄 <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
3.4 調用Service層操作數據
我們在自己的Service接口中通過繼承MyBatis-Plus提供的IService接口,不僅可以獲得其提供的CRUD方法,而且還可以使用自身定義的方法。
-
創建
UserService
並繼承IService
/** * UserService繼承IService模板提供的基礎功能 */ public interface UserService extends IService<User> {}
-
創建
UserService
的實現類並繼承ServiceImpl
/** * ServiceImpl實現了IService,提供了IService中基礎功能的實現 * 若ServiceImpl無法滿足業務需求,則可以使用自定的UserService定義方法,並在實現類中實現 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
-
測試查詢記錄數
調用方法:int count();
@Test public void testGetCount(){ //查詢總記錄數 //執行的SQL爲:SELECT COUNT( * ) FROM user long count = userService.count(); System.out.println("總記錄數:" + count); }
-
測試批量插入數據
調用方法:boolean saveBatch(Collection
entityList); @Test public void test(){ List<User> list = new ArrayList<>(); for (int i = 1; i <= 10; i++) { User user = new User(); user.setName("Vz"+i); user.setAge(20+i); list.add(user); } boolean b = userService.saveBatch(list); System.out.println(b ? "添加成功!" : "添加失敗!"); }
四、常用註解
MyBatis-Plus提供的註解可以幫我們解決一些數據庫與實體之間相互映射的問題。
4.1 @TableName
經過以上的測試,在使用MyBatis-Plus實現基本的CRUD時,我們並沒有指定要操作的表,只是在Mapper接口繼承BaseMapper時,設置了泛型User,而操作的表爲user表,由此得出結論,MyBatis-Plus在確定操作的表時,由BaseMapper的泛型決定,即實體類型決定,且默認操作的表名和實體類型的類名一致。
4.1.1 引出問題
若實體類類型的類名和要操作的表的表名不一致,會出現什麼問題?
-
我們將表
user
更名爲t_user
,測試查詢功能 -
程序拋出異常,Table 'mybatis_plus.user' doesn't exist,因爲現在的表名爲
t_user
,而默認操作的表名和實體類型的類名一致,即user
表
4.1.2 解決問題
a、使用註解解決問題
在實體類類型上添加
@TableName("t_user")
,標識實體類對應的表,即可成功執行SQL語句
@Data
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
b、使用全局配置解決問題
在開發的過程中,我們經常遇到以上的問題,即實體類所對應的表都有固定的前綴,例如
t_
或tbl_
此時,可以使用MyBatis-Plus提供的全局配置,爲實體類所對應的表名設置默認的前綴,那麼就不需要在每個實體類上通過@TableName標識實體類對應的表
mybatis-plus:
global-config:
db-config:
# 設置實體類所對應的表的統一前綴
table-prefix: t_
4.2 @TableId
經過以上的測試,MyBatis-Plus在實現CRUD時,會默認將id作爲主鍵列,並在插入數據時,默認基於雪花算法的策略生成id
4.2.1 引出問題
若實體類和表中表示主鍵的不是id,而是其他字段,例如uid,MyBatis-Plus會自動識別uid爲主鍵列嗎?
-
我們實體類中的屬性
id
改爲uid
,將表中的字段id
也改爲uid
,測試添加功能 -
程序拋出異常,Field 'uid' doesn't have a default value,說明MyBatis-Plus沒有將
uid
作爲主鍵賦值
4.2.2 解決問題
在實體類中uid屬性上通過
@TableId
將其標識爲主鍵,即可成功執行SQL語句
@Date
public class User {
@TableId
private Long uid;
private String name;
private Integer age;
private String email;
}
4.2.3 @TableId的value屬性
若實體類中主鍵對應的屬性爲id,而表中表示主鍵的字段爲uid,此時若只在屬性id上添加註解@TableId,則拋出異常Unknown column 'id' in 'field list',即MyBatis-Plus仍然會將id作爲表的主鍵操作,而表中表示主鍵的是字段uid此時需要通過@TableId註解的value屬性,指定表中的主鍵字段,
@TableId("uid")
或@TableId(value="uid")
4.2.4 @TableId的type屬性
type屬性用來定義主鍵策略:默認雪花算法
常用的主鍵策略:
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默認) | 基於雪花算法的策略生成數據id,與數據庫id是否設置自增無關 |
IdType.AUTO | 使用數據庫的自增策略,注意,該類型請確保數據庫設置了id自增, |
@TableId(value = "uid", type = IdType.AUTO)
...
配置全局主鍵策略:
#MyBatis-Plus相關配置
mybatis-plus:
configuration:
#配置日誌
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
#配置mp的主鍵策略爲自增
id-type: auto
# 設置實體類所對應的表的統一前綴
table-prefix: t_
雪花算法
背景
需要選擇合適的方案去應對數據規模的增長,以應對逐漸增長的訪問壓力和數據量。
數據庫的擴展方式主要包括:業務分庫、主從複製,數據庫分表。
數據庫分表
將不同業務數據分散存儲到不同的數據庫服務器,能夠支撐百萬甚至千萬用戶規模的業務,但如果業務繼續發展,同一業務的單表數據也會達到單臺數據庫服務器的處理瓶頸。例如,淘寶的幾億用戶數據,如果全部存放在一臺數據庫服務器的一張表中,肯定是無法滿足性能要求的,此時就需要對單表數據進行拆分。
單表數據拆分有兩種方式:垂直分表和水平分表。示意圖如下:
垂直分表
垂直分表適合將表中某些不常用且佔了大量空間的列拆分出去。
例如,前面示意圖中的 nickname 和 description 字段,假設我們是一個婚戀網站,用戶在篩選其他用戶的時候,主要是用 age 和 sex 兩個字段進行查詢,而 nickname 和 description 兩個字段主要用於展示,一般不會在業務查詢中用到。description 本身又比較長,因此我們可以將這兩個字段獨立到另外一張表中,這樣在查詢 age 和 sex 時,就能帶來一定的性能提升。
水平分表
水平分表適合錶行數特別大的表,有的公司要求單錶行數超過 5000 萬就必須進行分表,這個數字可以作爲參考,但並不是絕對標準,關鍵還是要看錶的訪問性能。對於一些比較複雜的表,可能超過 1000萬就要分表了;而對於一些簡單的表,即使存儲數據超過 1 億行,也可以不分表。
但不管怎樣,當看到表的數據量達到千萬級別時,作爲架構師就要警覺起來,因爲這很可能是架構的性能瓶頸或者隱患。
水平分表相比垂直分表,會引入更多的複雜性,例如要求全局唯一的數據id該如何處理
主鍵自增
①以最常見的用戶 ID 爲例,可以按照 1000000 的範圍大小進行分段,1 ~ 999999 放到表 1中,
1000000 ~ 1999999 放到表2中,以此類推。
②複雜點:分段大小的選取。分段太小會導致切分後子表數量過多,增加維護複雜度;分段太大可能會導致單表依然存在性能問題,一般建議分段大小在 100 萬至 2000 萬之間,具體需要根據業務選取合適的分段大小。
③優點:可以隨着數據的增加平滑地擴充新的表。例如,現在的用戶是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的數據不需要動。
④缺點:分佈不均勻。假如按照 1000 萬來進行分表,有可能某個分段實際存儲的數據量只有 1 條,而另外一個分段實際存儲的數據量有 1000 萬條。
取模
①同樣以用戶 ID 爲例,假如我們一開始就規劃了 10 個數據庫表,可以簡單地用 user_id % 10 的值來表示數據所屬的數據庫表編號,ID 爲 985 的用戶放到編號爲 5 的子表中,ID 爲 10086 的用戶放到編號爲 6 的子表中。
②複雜點:初始表數量的確定。表數量太多維護比較麻煩,表數量太少又可能導致單表性能存在問題。
③優點:表分佈比較均勻。
④缺點:擴充新的表很麻煩,所有數據都要重分佈。
雪花算法
https://www.bilibili.com/video/BV19k4y1B7aJ/
雪花算法是由Twitter公佈的分佈式主鍵生成算法,它能夠保證不同表的主鍵的不重複性,以及相同表的主鍵的有序性。
①核心思想:
長度共64bit(一個long型)。
首先是一個符號位,1bit標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0。
41bit時間截(毫秒級),存儲的是時間截的差值(當前時間截 - 開始時間截),結果約等於69.73年。
10bit作爲機器的ID(5個bit是數據中心,5個bit的機器ID,可以部署在1024個節點)。
12bit作爲毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID)。
②優點:整體上按照時間自增排序,並且整個分佈式系統內不會產生ID碰撞,並且效率較高。
4.3 @TbaleField
經過以上的測試,我們可以發現,MyBatis-Plus在執行SQL語句時,要保證實體類中的屬性名和表中的字段名一致
如果實體類中的屬性名和字段名不一致的情況,會出現什麼問題呢?
4.3.1 情況一
若實體類中的屬性使用的是駝峯命名風格,而表中的字段使用的是下劃線命名風格
例如實體類屬性userName
,表中字段user_name
此時MyBatis-Plus會自動將下劃線命名風格轉化爲駝峯命名風格
相當於在MyBatis中配置
4.3.2 情況二
若實體類中的屬性和表中的字段不滿足情況1
例如實體類屬性
name
,表中字段username
此時需要在實體類屬性上使用
@TableField("username")
設置屬性所對應的字段名
public class User {
@TableId("uid")
private Long id;
@TableField("username")
private String name;
private Integer age;
private String email;
}
4.4 @TableLogic
4.4.1 邏輯刪除
物理刪除:真實刪除,將對應數據從數據庫中刪除,之後查詢不到此條被刪除的數據
邏輯刪除:假刪除,將對應數據中代表是否被刪除字段的狀態修改爲“被刪除狀態”,之後在數據庫中仍舊能看到此條數據記錄
使用場景:可以進行數據恢復
4.4.2 實現邏輯刪除
-
數據庫中創建邏輯刪除狀態列,設置默認值爲0
-
實體類中添加邏輯刪除屬性
-
測試刪除功能,真正執行的是修改
public void testDeleteById(){ int result = userMapper.deleteById(1527472864163348482L); System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!"); System.out.println("受影響的行數爲:" + result); }
-
此時執行查詢方法,查詢的結果爲自動添加條件
is_deleted=0
五、條件構造器
5.1 Wrapper介紹
-
Wrapper
: 條件構造抽象類,最頂端父類-
AbstractWrapper
: 用於查詢條件封裝,生成 sql 的 where 條件-
QueryWrapper
: 查詢條件封裝 -
UpdateWrapper
: Update 條件封裝 -
AbstractLambdaWrapper
: 使用Lambda 語法-
LambdaQueryWrapper
:用於Lambda語法使用的查詢Wrapper -
LambdaUpdateWrapper
: Lambda 更新封裝Wrapper
-
-
-
5.2 QueryWrapper
-
組裝查詢條件
執行SQL:SELECT uid AS id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
public void test01(){ //查詢用戶名包含a,年齡在20到30之間,郵箱信息不爲null的用戶信息 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like("username","a") .between("age",20,30) .isNotNull("email"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); }
-
組裝排序條件
執行SQL:SELECT uid AS id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
public void test02(){ //查詢用戶信息,按照年齡的降序排序,若年齡相同,則按照id升序排序 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("age") .orderByAsc("id"); List<User> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); }
-
組裝刪除條件
執行SQL:UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
public void test03(){ //刪除郵箱地址爲null的用戶信息 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.isNull("email"); int result = userMapper.delete(queryWrapper); System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!"); System.out.println("受影響的行數爲:" + result); }
-
條件的優先級
執行SQL:UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)
public void test04(){ //將(年齡大於20並且用戶名中包含有a)或郵箱爲null的用戶信息修改 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.gt("age",20) .like("username","a") .or() .isNull("email"); User user = new User(); user.setName("Oz"); user.setEmail("[email protected]"); int result = userMapper.update(user, updateWrapper); System.out.println(result > 0 ? "修改成功!" : "修改失敗!"); System.out.println("受影響的行數爲:" + result); }
執行SQL:UPDATE t_user SET username=?, email=? WHERE is_deleted=0 AND (username LIKE ? AND (age > ? OR email IS NULL))
public void test05(){ //將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); updateWrapper.like("username","a").and(i->i.gt("age",20).or().isNull("email")); User user = new User(); user.setName("Vz7797"); user.setEmail("[email protected]"); int result = userMapper.update(user, updateWrapper); System.out.println(result > 0 ? "修改成功!" : "修改失敗!"); System.out.println("受影響的行數爲:" + result); }
-
組裝select子句
執行SQL:SELECT username,age,email FROM t_user WHERE is_deleted=0
public void test06(){ //查詢用戶的用戶名、年齡、郵箱信息 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.select("username","age","email"); List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper); maps.forEach(System.out::println); }
-
實現子查詢
執行SQL:SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 100))
public void test07(){ //查詢id小於等於100的用戶信息 QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.inSql("uid", "select uid from t_user where uid <= 100"); List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); }
5.3 UpdateWrapper
UpdateWrapper不僅擁有QueryWrapper的組裝條件功能,還提供了set方法進行修改對應條件的數據庫信息
public void test08(){
//將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("username","a").and( i -> i.gt("age",20).or().isNull("email")).set("email","[email protected]");
int result = userMapper.update(null, updateWrapper);
System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
System.out.println("受影響的行數爲:" + result);
}
5.4 condition
在真正開發的過程中,組裝條件是常見的功能,而這些條件數據來源於用戶輸入,是可選的,因此我們在組裝這些條件時,必須先判斷用戶是否選擇了這些條件,若選擇則需要組裝該條件,若沒有選擇則一定不能組裝,以免影響SQL執行的結果
-
思路一
執行SQL:SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)
public void test09(){ String username = "a"; Integer ageBegin = null; Integer ageEnd = 30; QueryWrapper<User> queryWrapper = new QueryWrapper<>(); if(StringUtils.isNotBlank(username)){ //isNotBlank判斷某個字符創是否不爲空字符串、不爲null、不爲空白符 queryWrapper.like("user_name", username); } if(ageBegin != null){ queryWrapper.ge("age", ageBegin); } if(ageEnd != null){ queryWrapper.le("age", ageEnd); } List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); }
-
思路二
上面的實現方案沒有問題,但是代碼比較複雜,我們可以使用帶condition參數的重載方法構建查詢條件,簡化代碼的編寫
public void test10(){ String username = "a"; Integer ageBegin = null; Integer ageEnd = 30; QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.like(StringUtils.isNotBlank(username), "user_name", username) .ge(ageBegin != null, "age", ageBegin) .le(ageEnd != null, "age", ageEnd); List<User> list = userMapper.selectList(queryWrapper); list.forEach(System.out::println); }
5.5 LambdaQueryWrapper
功能等同於QueryWrapper,提供了Lambda表達式的語法可以避免填錯列名。
public void test11(){
String username = "a";
Integer ageBegin = null;
Integer ageEnd = 30;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
5.6 LambdaUpdateWrapper
功能等同於UpdateWrapper,提供了Lambda表達式的語法可以避免填錯列名。
public void test12(){
//將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.like(User::getName, "a")
.and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
updateWrapper.set(User::getName, "小黑").set(User::getEmail,"[email protected]");
int result = userMapper.update(null, updateWrapper);
System.out.println("result:"+result);
}
六、常用插件
6.1 分頁插件
MyBatis Plus自帶分頁插件,只要簡單的配置即可實現分頁功能
-
添加配置類
MyBatisPlusConfig
@Configuration //掃描mapper接口所在的包 @MapperScan("com.atguigu.mybatisplus.mapper") public class MyBatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 添加分頁插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // paginationInterceptor.setLimit(你的最大單頁限制數量,默認 500 條,小於 0 如 -1 不受限制); return interceptor; } }
-
編寫測試方法
@Test public void testPage(){ //new Page()中的兩個參數分別是當前頁碼,每頁顯示數量 Page<User> page = userMapper.selectPage(new Page<>(1, 2), null); List<User> users = page.getRecords(); users.forEach(System.out::println); }
6.2 自定義分頁
上面調用的是MyBatis-Plus提供的帶有分頁的方法,那麼我們自己定義的方法如何實現分頁呢?
-
在
UserMapper
接口中定義一個方法/** * 根據年齡查詢用戶列表,分頁顯示 * @param page 分頁對象,xml中可以從裏面進行取值,傳遞參數 Page 即自動分頁,必須放在第一位 * @param age 年齡 * @return */ Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
-
在
UserMapper.xml
中編寫SQL實現該方法<!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);--> <select id="selectPageVo" resultType="User"> select id,username as name,age,email from t_user where age > #{age} </select>
-
編寫測試方法
@Test public void testPageVo(){ Page<User> page = userMapper.selectPageVo(new Page<User>(1,2), 20); List<User> users = page.getRecords(); users.forEach(System.out::println); }
6.3 樂觀鎖
樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。
作用:當要更新一條記錄的時候,希望這條記錄沒有被別人更新
樂觀鎖的實現方式:
- 取出記錄時,獲取當前 version
- 更新時,帶上這個 version
- 執行更新時, set version = newVersion where version = oldVersion
- 如果 version 不對,就更新失敗
6.3.1 場景
- 一件商品,成本價是80元,售價是100元。老闆先是通知小李,說你去把商品價格增加50元。小李正在玩遊戲,耽擱了一個小時。正好一個小時後,老闆覺得商品價格增加到150元,價格太高,可能會影響銷量。又通知小王,你把商品價格降低30元。
- 此時,小李和小王同時操作商品後臺系統。小李操作的時候,系統先取出商品價格100元;小王也在操作,取出的商品價格也是100元。小李將價格加了50元,並將100+50=150元存入了數據庫;小王將商品減了30元,並將100-30=70元存入了數據庫。是的,如果沒有鎖,小李的操作就完全被小王的覆蓋了。
- 現在商品價格是70元,比成本價低10元。幾分鐘後,這個商品很快出售了1千多件商品,老闆虧1萬多。
6.3.2 樂觀鎖與悲觀鎖
- 上面的故事,如果是樂觀鎖,小王保存價格前,會檢查下價格是否被人修改過了。如果被修改過了,則重新取出的被修改後的價格,150元,這樣他會將120元存入數據庫。
- 如果是悲觀鎖,小李取出數據後,小王只能等小李操作完之後,才能對價格進行操作,也會保證最終的價格是120元。
6.3.3 模擬修改衝突
-
數據庫中增加商品表
CREATE TABLE t_product ( id BIGINT(20) NOT NULL COMMENT '主鍵ID', NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱', price INT(11) DEFAULT 0 COMMENT '價格', VERSION INT(11) DEFAULT 0 COMMENT '樂觀鎖版本號', PRIMARY KEY (id) );
-
添加一條數據
INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人筆記本', 100);
-
添加一個實體類
Product
@Data public class Product { private Long id; private String name; private Integer price; private Integer version; }
-
添加一個Mapper接口
ProductMapper
public interface ProductMapper extends BaseMapper<Product> {}
-
測試方法
@Test public void testProduct01(){ //1.小李獲取商品價格 Product productLi = productMapper.selectById(1); System.out.println("小李獲取的商品價格爲:" + productLi.getPrice()); //2.小王獲取商品價格 Product productWang = productMapper.selectById(1); System.out.println("小李獲取的商品價格爲:" + productWang.getPrice()); //3.小李修改商品價格+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi); //4.小王修改商品價格-30 productWang.setPrice(productWang.getPrice()-30); productMapper.updateById(productWang); //5.老闆查詢商品價格 Product productBoss = productMapper.selectById(1); System.out.println("老闆獲取的商品價格爲:" + productBoss.getPrice()); }
-
執行結果
6.3.4 樂觀鎖解決問題
-
實體類
version
字段添加註解@Version
@Data public class Product { private Long id; private String name; private Integer price; @Version private Integer version; }
-
添加樂觀鎖插件配置
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); //添加分頁插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); //添加樂觀鎖插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }
-
再次執行測試方法
小李查詢商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小王查詢商品信息:
SELECT id,name,price,version FROM t_product WHERE id=?
小李修改商品價格,自動將version+1
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人筆記本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)
小王修改商品價格,此時version已更新,條件不成立,修改失敗
UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=?
Parameters: 外星人筆記本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)
最終,小王修改失敗,查詢價格:150
SELECT id,name,price,version FROM t_product WHERE id=?
-
優化執行流程
@Test public void testProduct01(){ //1.小李獲取商品價格 Product productLi = productMapper.selectById(1); System.out.println("小李獲取的商品價格爲:" + productLi.getPrice()); //2.小王獲取商品價格 Product productWang = productMapper.selectById(1); System.out.println("小李獲取的商品價格爲:" + productWang.getPrice()); //3.小李修改商品價格+50 productLi.setPrice(productLi.getPrice()+50); productMapper.updateById(productLi); //4.小王修改商品價格-30 productWang.setPrice(productWang.getPrice()-30); int result = productMapper.updateById(productWang); if(result == 0){ //操作失敗,重試 Product productNew = productMapper.selectById(1); productNew.setPrice(productNew.getPrice()-30); productMapper.updateById(productNew); } //5.老闆查詢商品價格 Product productBoss = productMapper.selectById(1); System.out.println("老闆獲取的商品價格爲:" + productBoss.getPrice()); }
七、通用枚舉
表中的有些字段值是固定的,例如性別(男或女),此時我們可以使用MyBatis-Plus的通用枚舉來實現
-
數據庫表添加字段
sex
-
創建通用枚舉類型
@Getter public enum SexEnum { MALE(1, "男"), FEMALE(2, "女"); @EnumValue //將註解所標識的屬性的值存儲到數據庫中 private int sex; private String sexName; SexEnum(Integer sex, String sexName) { this.sex = sex; this.sexName = sexName; } }
-
User實體類中添加屬性sex
public class User { private Long id; @TableField("username") private String name; private Integer age; private String email; @TableLogic private int isDeleted; //邏輯刪除 private SexEnum sex; }
-
配置掃描通用枚舉
#MyBatis-Plus相關配置 mybatis-plus: #指定mapper文件所在的地址 mapper-locations: classpath:mapper/*.xml configuration: #配置日誌 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: banner: off db-config: #配置mp的主鍵策略爲自增 id-type: auto # 設置實體類所對應的表的統一前綴 table-prefix: t_ #配置類型別名所對應的包 type-aliases-package: com.atguigu.mybatisplus.pojo # 掃描通用枚舉的包 type-enums-package: com.atguigu.mybatisplus.enums
-
執行測試方法
@Test public void test(){ User user = new User(); user.setName("admin"); user.setAge(33); user.setSex(SexEnum.MALE); int result = userMapper.insert(user); System.out.println("result:"+result); }
八、代碼生成器
1、引入依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
2、快速生成
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus?characterEncoding=utf-8&userSSL=false", "root", "123456")
.globalConfig(builder -> {
builder.author("atguigu") // 設置作者
//.enableSwagger() // 開啓 swagger 模式
.fileOverride() // 覆蓋已生成文件
.outputDir("D://mybatis_plus"); // 指定輸出目錄
})
.packageConfig(builder -> {
builder.parent("com.atguigu") // 設置父包名
.moduleName("mybatisplus") // 設置父包模塊名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 設置mapperXml生成路徑
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 設置需要生成的表名
.addTablePrefix("t_", "c_"); // 設置過濾表前綴
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板
.execute();
}
}
九、多數據源
適用於多種場景:純粹多庫、 讀寫分離、 一主多從、 混合模式等
場景說明:
我們創建兩個庫,分別爲:mybatis_plus
(以前的庫不動)與mybatis_plus_1
(新建),將mybatis_plus庫的product
表移動到mybatis_plus_1庫,這樣每個庫一張表,通過一個測試用例分別獲取用戶數據與商品數據,如果獲取到說明多庫模擬成功
9.1 創建數據庫及表
-
創建數據庫
mybatis_plus_1
和表`productCREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */; use `mybatis_plus_1`; CREATE TABLE product ( id BIGINT(20) NOT NULL COMMENT '主鍵ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名稱', price INT(11) DEFAULT 0 COMMENT '價格', version INT(11) DEFAULT 0 COMMENT '樂觀鎖版本號', PRIMARY KEY (id) );
-
添加測試數據
INSERT INTO product (id, NAME, price) VALUES (1, '外星人筆記本', 100);
-
刪除
mybatis_plus
庫中的product
表use mybatis_plus; DROP TABLE IF EXISTS product;
9.2 新建工程引入依賴
自行新建一個Spring Boot工程並選擇MySQL驅動及Lombok依賴
引入MyBaits-Plus的依賴及多數據源的依賴
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
9.3 編寫配置文件
spring:
# 配置數據源信息
datasource:
dynamic:
# 設置默認的數據源或者數據源組,默認值即爲master
primary: master
# 嚴格匹配數據源,默認false.true未匹配到指定數據源時拋異常,false使用默認數據源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 132537
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 132537
9.4 創建實體類
-
新建一個
User
實體類(如果數據庫表名有t_前綴記得配置)@Data public class User { private Long id; private String name; private Integer age; private String email; }
-
新建一個實體類
Product
@Data public class Product { private Long id; private String name; private Integer price; private Integer version; }
9.5 創建Mapper及Service
-
新建接口
UserMapper
public interface UserMapper extends BaseMapper<User> {}
-
新建接口
ProductMapper
public interface ProductMapper extends BaseMapper<Product> {}
-
新建Service接口
UserService
指定操作的數據源@DS("master") //指定操作的數據源,master爲user表 public interface UserService extends IService<User> {}
-
新建Service接口
ProductService
指定操作的數據源@DS("slave_1") public interface ProductService extends IService<Product> {}
-
自行建立Service的實現類
...
9.6 編寫測試方法
記得在啓動類中添加註解
@MapperScan()
class TestDatasourceApplicationTests {
@Resource
UserService userService;
@Resource
ProductService productService;
@Test
void contextLoads() {
User user = userService.getById(1L);
Product product = productService.getById(1L);
System.out.println("User = " + user);
System.out.println("Product = " + product);
}
}
10、MyBatisX插件
MyBatis-Plus爲我們提供了強大的mapper和service模板,能夠大大的提高開發效率。
但是在真正開發過程中,MyBatis-Plus並不能爲我們解決所有問題,例如一些複雜的SQL,多表聯查,我們就需要自己去編寫代碼和SQL語句,我們該如何快速的解決這個問題呢,這個時候可以使用MyBatisX插件。
MyBatisX一款基於 IDEA 的快速開發插件,爲效率而生。
10.1 安裝MyBatisX插件
打開IDEA,File-> Setteings->Plugins->MyBatisX,搜索欄搜索MyBatisX然後安裝。
10.2 快速生成代碼
-
新建一個Spring Boot項目引入依賴(創建工程時記得勾選lombok及mysql驅動)
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.5.0</version> </dependency>
-
配置數據源信息
spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false username: root password: 132537
-
在IDEA中與數據庫建立鏈接
-
填寫數據庫信息並保存
-
找到我們需要生成的表點擊右鍵
-
填寫完信息以後下一步
-
繼續填寫信息
-
大功告成(真特麼好用yyds)
10.2 快速生成CRUD
MyBaitsX可以根據我們在Mapper接口中輸入的方法名快速幫我們生成對應的sql語句