# 前言
程式碼生成器外掛選擇去這裡:https://www.cnblogs.com/zixq/p/16726534.html
相關外掛在那裡面已經提到了
# 上手
MyBatis-Plus 是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生
PS:要開啟官網需要將瀏覽器的廣告攔截外掛新增白名單,具體操作訪問官網即可看到
官網入手示例:https://www.baomidou.com/getting-started/
1、依賴
<!-- 這個starter包含對mybatis的自動裝配,完全可以替換掉Mybatis的starter -->
<!-- Spring Boot2 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.7</version>
</dependency>
<!-- Spring Boot3 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
2、介面 extends BaseMapper<T>
。該介面定義了單表CRUD的一些常用API
// 泛型 User 是與資料庫對應的實體類
public interface UserMapper extends BaseMapper<User> {
}
Mybatis-Plus就是根據PO實體的資訊來推斷出表的資訊,從而生成SQL的。預設情況下:
-
MybatisPlus會把PO實體的類名駝峰轉下劃線作為表名
-
MybatisPlus會把PO實體的所有變數名駝峰轉下劃線作為表的欄位名,並根據變數型別推斷欄位型別
-
MybatisPlus會把名為id的欄位作為主鍵
但很多情況下,預設的實現與實際場景不符,因此MybatisPlus提供了一些註解便於我們宣告表資訊
3、然後就可以像呼叫自定義mapper介面方法一樣呼叫了
# 註解
更多註解看官網:https://www.baomidou.com/reference/annotation/
@TableName
:用來指定表名
@TableId
:用來指定表中的主鍵欄位資訊。IdType列舉如下
- AUTO:資料庫自增長
- INPUT:透過set方法自行輸入
- ASSIGN_ID:分配 ID(預設方式),介面IdentifierGenerator的方法nextId來生成id,預設實現類為DefaultIdentifierGenerator雪花演算法
@TableField
:用來指定表中的普通欄位資訊
使用@TableField
的常見場景:
- 成員變數名與資料庫欄位名不一致
- 成員變數名以is開頭,且是布林值
- 成員變數名與資料庫關鍵字衝突
- 成員變數不是資料庫欄位
# 配置
相容MyBatis的配置
另外更多配置,檢視官網:https://www.baomidou.com/reference/
mybatis-plus:
type-aliases-package: com.zixq.mp.domain.po # 別名掃描包
mapper-locations: "classpath*:/mapper/**/*.xml" # Mapper.xml檔案地址,預設值
configuration: map-underscore-to-camel-case: true # 是否開啟下劃線和駝峰的對映
cache-enabled: false # 是否開啟二級快取
global-config:
db-config:
id-type: assign_id # id為雪花演算法生成 這個優先順序沒有使用 @TableId 註解高
update-strategy: not_null # 更新策略:只更新非空欄位
# 全域性邏輯刪除的實體欄位名(since 3.3.0,配置後可以忽略不配置步驟2)
logic-delete-field: deleted
logic-delete-value: 1 # 邏輯已刪除值(預設為 1)
logic-not-delete-value: 0 # 邏輯未刪除值(預設為 0)
邏輯刪除進行上面配置後mp就可自動實現delete變update操作了,但邏輯刪除會佔用空間,影響效能,所以可採用刪除前將資料遷移到另一張表中
如果要表刪除標誌欄位沒統一的話,可以使用 @TableLogic
來指定
@TableLogic
private Boolean deleted;
# 條件構造器
1、QueryWrapper 和 LambdaQueryWrapper一般用來構建select、delete、update的where條件部分
2、UpdateWrapper 和 LambdaUpdateWrapper通常只有在set語句比較特殊才使用,如set的是
money = money - 1000
3、儘量使用LambdaQueryWrapper 和 LambdaUpdateWrapper,避免硬編碼
BaseMapper包含的Wrapper構建起
AbstractWrapper
QueryWrapper
UpdateWrapper
# 示例
1、QueryWrapper根據指定欄位和條件查詢
SQL
SELECT id, username, info, balance
FROM `user`
WHERE username LIKE ? AND balance >= ?;
mp構建:
// 1、構建查詢條件
Querywrapper<User> wrapper = new QueryWrapper<User()
.select("id", "username", "info","balance")
.like("username", "o")
·ge("balance", 1000);
// 2、查詢
List<User> users = userMapper.selectList(wrapper);
// 使用 LambdaWrapper 的方式
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User()
.select(User::getId, User::getUsername, User::getInfo,User::getBalance)
.like(User::getUsername, "o")
·ge(User::getBalance, 1000);
// 2、查詢
List<User> users = userMapper.selectList(wrapper);
2、QueryWrapper更新構建
SQL
UPDATE `user`
SET balance = 2000
WHERE username = "jack"
mp構建:
// 1、要更新的資料
User user = new User();
user.setBalance(2000);
// 2、更新的條件
QueryWrapper<User> wrapper = new Querywrapper<User>()
.eq("username", "jack");
// 3、執行更新
userMapper.update(user, wrapper);
3、UpdateWrapper構建set的特殊情況
SQL:
UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)
mp構建:
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 4L));
userMapper.update(null, wrapper) ;
# 自定義SQL
適用場景:要構建的SQL除了where條件之外的語句很複雜,那麼就讓mp幫我們構建where條件部分,其他部分由我們自己構建
上手
SQL:
UPDATE user
SET balance = balance - 200
WHERE id in (1, 2, 4)
mp構建:
UpdateWrapper<User> wrapper = new UpdateWrapper<User>()
.setSql("balance = balance - 200")
.in("id", List.of(1L, 2L, 4L));
userMapper.update(null, wrapper) ;
setSql("balance = balance -2oo")
這部分應該在mapper層,而不是在業務層,所以需要進行傳遞,然後在mapper層進行SQL拼接
1、業務層
// 1、準備自定義查詢條件
List<Long> ids = List.of(1L, 2L, 4L);
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("id", ids);
// 2、呼叫mapper的自定義方法,直接傳遞Wrapper
userMapper.deductBalanceByIds(200, wrapper);
2、mapper層
public interface UserMapper extends BaseMapper<User> {
/**
* <p>
* @Param("ew") 引數名必須是這個 也可以使用 @Param(Constants.WRAPPER)
* ${ew.customSqlSegment} 是mp自動解析自定義SQL片段
* </p>
*/
@Select("UPDATE user SET balance = balance - #{money} ${ew.customSqlSegment}")
void deductBalanceByIds(@Param("money") int money,
@Param("ew") QueryWrapper<User> wrapper);
}
# 多表關聯
MyBatis構建的方式:
<select id="queryUserByIdAndAddr" resultType="com.itheima.mp.domain.po.User">
SELECT *
FROM user u
INNER JOIN address a ON u.id = a.user_id
WHERE u.id
<foreach collection="ids" separator="," item="id" open="IN (" close=")">
#{id}
</foreach>
AND a.city = #{city}
</select>
利用Wrapper來構建查詢條件,然後手寫SELECT及FROM部分,實現多表查詢
業務層:
// 1、準備自定義查詢條件
QueryWrapper<User> wrapper = new QueryWrapper<User>()
.in("u.id", List.of(1L, 2L, 4L))
.eq("a.city", "北京");
// 2、呼叫mapper的自定義方法
List<User> users = userMapper.queryUserByWrapper(wrapper);
mapper層:
@Select("SELECT u.* FROM user u INNER JOIN address a ON u.id = a.user_id ${ew.customSqlSegment}")
List<User> queryUserByWrapper(@Param("ew")QueryWrapper<User> wrapper);
# IService介面
MybatisPlus不僅提供了BaseMapper,還提供了通用的Service介面及預設實現,封裝了一些常用的service模板方法
通用介面為IService,預設實現為ServiceImpl
# 上手
如果直接繼承
IService
介面,則需要實現裡面的方法,因此mp提供的一個預設實現ServiceImp
1、自定義Service介面
public interface IUserService extends IService<User> {}
2、自定義Service介面實現類:繼承mp的 ServiceImpl<M, T>
/**
* ServiceImpl<UserMapper, User> UserMapper指定對應的mapper User指定對應的實體類
*/
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService {}
3、正常使用上面那些增刪改、單個查、多個查之類的API即可
# Lambda
IService中還提供了Lambda功能來簡化我們的複雜查詢(LambdaQuery)及更新功能(LambdaUpdate)
# LambdaQuery
根據複雜條件查詢使用者的介面,查詢條件如下:
- name:使用者名稱關鍵字,可以為空
- status:使用者狀態,可以為空
- minBalance:最小余額,可以為空
- maxBalance:最大餘額,可以為空
GetMapping("/list")
@ApiOperation("根據id集合查詢使用者")
public List<UserVO> queryUsers(UserQuery query){
// 1、組織條件
String username = query.getName();
Integer status = query.getStatus();
Integer minBalance = query.getMinBalance();
Integer maxBalance = query.getMaxBalance();
// 2、查詢使用者
List<User> users = userService.lambdaQuery()
.like(username != null, User::getUsername, username)
.eq(status != null, User::getStatus, status)
.ge(minBalance != null, User::getBalance, minBalance)
.le(maxBalance != null, User::getBalance, maxBalance)
.list(); // 告訴MP我們的呼叫結果需要的是一個list集合 上面是構建條件
// 3、處理vo
return BeanUtil.copyToList(users, UserVO.class);
}
UserQuery實體
@Data
@ApiModel(description = "使用者查詢條件實體")
public class UserQuery {
@ApiModelProperty("使用者名稱關鍵字")
private String name;
@ApiModelProperty("使用者狀態:1-正常,2-凍結")
private Integer status;
@ApiModelProperty("餘額最小值")
private Integer minBalance;
@ApiModelProperty("餘額最大值")
private Integer maxBalance;
}
除了list()
,還可選的方法有:
one()
:最多1個結果list()
:返回集合結果count()
:返回計數結果
# LambdaUpdate
根據id修改使用者餘額,如果扣減後餘額為0,則將使用者status修改為凍結狀態2
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService {
@Override
@Transactional(rollbackFor = Exception.class)
public void deductBalance(Long id, Integer money) {
// 1、查詢使用者
User user = this.getById(id);
// 2.校驗使用者狀態
if (user == null || user.getStatus() == 2) {
throw new RuntimeException("使用者狀態異常!");
}
// 3、校驗餘額是否充足
if (user.getBalance() < money) {
throw new RuntimeException("使用者餘額不足!");
}
// 4.扣減餘額 update tb_user set balance = balance - ?
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(User::getBalance, remainBalance) // 更新餘額
.set(remainBalance == 0, User::getStatus, 2) // 動態判斷,是否更新status
.eq(User::getId, id)
.eq(User::getBalance, user.getBalance()) // 樂觀鎖
.update(); // 上面為構建條件,這一步才為真正去修改
}
}
# saveBatch 批次新增 說明
YAML中datesource的url後新增引數
rewriteBatchedStatements=true
private User buildUser(int i) {
User user = new User();
user.setUsername("user_" + i);
user.setPassword("123");
user.setPhone("" + (18688190000L + i));
user.setBalance(2000);
user.setInfo("{\"age\": 24, \"intro\": \"英文老師\", \"gender\": \"female\"}");
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(user.getCreateTime());
return user;
}
@Test
void testSaveBatch() {
// 準備10萬條資料
List<User> list = new ArrayList<>(1000);
long b = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
list.add(buildUser(i));
// 每1000條批次插入一次
if (i % 1000 == 0) {
userService.saveBatch(list);
list.clear();
}
}
long e = System.currentTimeMillis();
System.out.println("耗時:" + (e - b));
}
saveBatch()
原始碼
@Transactional(rollbackFor = Exception.class)
@Override
public boolean saveBatch(Collection<T> entityList, int batchSize) {
String sqlStatement = getSqlStatement(SqlMethod.INSERT_ONE);
return executeBatch(entityList, batchSize, (sqlSession, entity) -> sqlSession.insert(sqlStatement, entity));
}
// ...SqlHelper
public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, sqlSession -> {
int size = list.size();
int idxLimit = Math.min(batchSize, size);
int i = 1;
for (E element : list) {
consumer.accept(sqlSession, element);
if (i == idxLimit) {
sqlSession.flushStatements();
idxLimit = Math.min(idxLimit + batchSize, size);
}
i++;
}
});
}
可見Mybatis-Plus的批處理是基於PrepareStatement的預編譯模式,形成的SQL是如下樣式:
Preparing: INSERT INTO user ( username, password, phone, info, balance, create_time, update_time ) VALUES ( ?, ?, ?, ?, ?, ?, ? )
Parameters: user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01
Parameters: user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01
而我們要的是合併成一條SQL,從而提高效能
INSERT INTO user ( username, password, phone, info, balance, create_time, update_time )
VALUES
(user_1, 123, 18688190001, "", 2000, 2023-07-01, 2023-07-01),
(user_2, 123, 18688190002, "", 2000, 2023-07-01, 2023-07-01),
(user_3, 123, 18688190003, "", 2000, 2023-07-01, 2023-07-01),
(user_4, 123, 18688190004, "", 2000, 2023-07-01, 2023-07-01);
這就需要修改SQL的配置,新增 &rewriteBatchedStatements=true:
引數
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
這樣配置之後,效能可以得到極大地提升
# Db 靜態工具類
官網示例:https://gitee.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus/src/test/java/com/baomidou/mybatisplus/test/toolkit/DbTest.java
Db這個類裡面的靜態方法就是IService中的方法,實現方式有點區別(提供Class物件)
適用場景:A類中注入B類來進行呼叫問題。使用該靜態工具就不需要注入,直接使用Class就可進行呼叫了,從而減少迴圈依賴風險
注意
- 使用 Db Kit 前,需要確保專案中已注入對應實體的 BaseMapper。
- 當引數為 Wrapper 時,需要在 Wrapper 中傳入 Entity 或者 EntityClass,以便尋找對應的 Mapper。
- 不建議在迴圈中頻繁呼叫 Db Kit 的方法,如果是批次操作,建議先將資料構造好,然後使用
Db.saveBatch(資料)
等批次方法進行儲存
示例:
@Override
public UserVO queryUserAndAddressById(Long userId) {
// 1、查詢使用者
User user = getById(userId);
if (user == null) {
return null;
}
// 2、查詢收貨地址 不需要注入 AddressSerivce 了
List<Address> addresses = Db.lambdaQuery(Address.class)
.eq(Address::getUserId, userId)
.list();
// 3、處理vo
UserVO userVO = BeanUtil.copyProperties(user, UserVO.class);
userVO.setAddresses(BeanUtil.copyToList(addresses, AddressVO.class));
return userVO;
}
# 列舉處理器
解決Java列舉型別和資料庫型別轉換
@Data
public class UserEntity {
// ......................
// 0 正常 1禁用
private Integer status;
}
上面這種和資料庫int型別轉換方便,但是不符合編碼,因為0和1得手動輸入,程式碼多了很麻煩且容易弄錯甚至混亂不堪(不信邪的可以去看若依專案:RuoYi-Vue),因此我們需要統一狀態,即列舉
@Getter
public enum UserStatusEnum {
NORMAL(1, "正常"),
FREEZE(2, "凍結")
;
private final int value;
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
實體類
@Data
public class UserEntity {
// ......................
// 0 正常 1禁用
private UserStatusEnum status;
}
但此時實體是 UserStatusEnum
而資料庫是int,涉及型別轉換,這就需要mp來做了
1、YAML配置
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
2、@EnumValue
標記列舉中哪個值為資料庫欄位值
@Getter
public enum UserStatusEnum {
NORMAL(0, "正常"),
FREEZE(1, "凍結")
;
@EnumValue
private final int value;
@JsonValue // Jackson的,返回前端的值是正常或凍結 而不是 NORMAL 這種 SpringMVC底層使用的是 Jackson
private final String desc;
UserStatus(int value, String desc) {
this.value = value;
this.desc = desc;
}
}
# JSON型別處理器
官網:https://www.baomidou.com/guides/type-handler/
資料庫中欄位是JSON格式,和Java實體類對應欄位進行適配,方便操作JSON格式
mp提供的JSON處理器預設是JacksonHandler,也推薦使用它,因為安全
Java定義JSON對應的類
@Data
public class Userlnfo {
privateIntegerage;
privateString intro;
privateString gender;
}
Java實體類
@Data@TableName(value="user", autoResultMap = true) // 操作1
public class User{
private Long id;
private String username;
@TableField(typeHandler = JacksonTypeHandler.class) // 操作2
private String UserInfoinfo
# 分頁外掛
官網:https://www.baomidou.com/plugins/pagination/
# 上手
1、註冊外掛
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
// 初始化核心外掛
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分頁外掛
PaginationInnerInterceptor pgInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 設定分頁最大條數
pgInterceptor.setMaxLimit(1000);
// 註冊分頁外掛
interceptor.addInnerInterceptor();
return interceptor;
}
}
使用分頁:
int pageNo = 1, pageSize = 2;
// 1、準備分頁條件
// 1.1、分頁條件
Page<User> page = Page.of(pageNo,pageSize);
// 1.2、排序條件 true 為升序 false 為降序
page.addorder(new OrderItem("balance", true));
page.addorder(new OrderItem("id", true)); // balance 相同下,以 id 排序
// 2、分頁查詢
Page<User> p = userservice.page(page);
// 3、解析
long total = p·getTotal(); // 總條數
System.out.println("total = " + total);
long pages = p.getPages(); // 總頁數
System.out.println("pages = " ++ pages);
List<User> users = p·getRecords(); // 分頁後的資料
# 企業級mp相關設計
直接去看這個專案:RuoYi企業級改造版:AgileBoot-Back-End-Basic
對應md文件
連結:MyBatis-Plus。檔案格式:md