MyBatis增強器——Mybatis-Plus
- 一、Mybatis-Plus簡介
- 1.簡介
- 2.特性
- 3.支援資料庫
- 4.架構結構
- 二、入門案例
- 1.開發環境
- 2.建立資料庫及表
- 建立表
- 添加資料
- 3.建立SpringBoot工程
- 4.編寫代碼
- 三、基本crud
- 四、常用注解
- 1.@TableName
- 通過@TableName解決問題
- 通過全局配置解決問題
- 2.@TableId
- 通過@TableId解決問題
- @TableId的value屬性
- @TableId的type屬性
- 3.@TableField
- 4.@TableLogic
- 邏輯删除
- 五、條件構造函數和常用接口
- 1.wapper介紹
- 2.QueryWrapper
- 3.**UpdateWrapper**
- 4、condition
- 5、LambdaQueryWrapper
- 6、LambdaUpdateWrapper
- 六、插件
- 1.分頁插件
- 2.xml自定義分頁
- 3.樂觀鎖
- a>場景
- b>樂觀鎖與悲觀鎖
- c>模拟修改沖突
- d>樂觀鎖實作流程
- e>Mybatis-Plus實作樂觀鎖
- 七、通用枚舉
- 八、代碼生成器
- 九、多資料源
- 十、MybatisX插件
一、Mybatis-Plus簡介
1.簡介
MyBatis-Plus(簡稱 MP)是一個 MyBatis的增強工具**,在 MyBatis 的基礎上隻做增強不做改變,為簡化開發、提高效率而生**。
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 操作智能分析阻斷,也可自定義攔截規則,預防誤操作
3.支援資料庫
任何能使用MyBatis進行 CRUD, 并且支援标準 SQL 的資料庫,具體支援情況如下
MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
達夢資料庫,虛谷資料庫,人大金倉資料庫,南大通用(華庫)資料庫,南大通用資料庫,神通資料庫,瀚高資料庫
4.架構結構

二、入門案例
1.開發環境
IDE:idea 2019.2
JDK:JDK8+
建構工具:maven 3.5.4
MySQL版本:MySQL 5.7
Spring Boot:2.6.3
MyBatis-Plus:3.5.1
2.建立資料庫及表
建立表
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]');
3.建立SpringBoot工程
使用 Spring Initializr快速初始化一個SpringBoot工程
引入依賴
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-boot-starterartifactId>
<version>3.5.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
4.編寫代碼
配置application.yml
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: 123456
注意連接配接位址url
MySQL5.7版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
MySQL8.0版本的url:
jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
否則運作測試用例報告如下錯誤:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
啟動類
在Spring Boot啟動類中添加@MapperScan注解,掃描mapper包
@SpringBootApplication
//掃描mapper接口所在的包
@MapperScan("com.mybatisplus.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
添加實體
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
添加mapper
BaseMapper是Mybatis-plus提供的模闆mapper,其中包含了基本的CRUD方法,泛型為操作的實體類型
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
測試
@SpringBootTest
public class MybatisPlusTest {
@Autowired private UserMapper userMapper;
@Test public void testSelectList(){
//selectList()根據MP内置的條件構造器查詢一個list集合,null表示沒有條 //件,即查詢所有 userMapper.selectList(null).forEach(System.out::println);
}
}
注意:
IDEA在userMapper處報錯,因為找不到注入的對象,因為類是動态建立的,但是程式可以正确的執行,為了避免報錯,可以在mapper接口上添加@Respository、
添加日志
在application.yml中配置日志輸出
# 配置MyBatis日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
三、基本crud
1.BaseMapper
Mybatis-plus中的基本CRUD在内置的BaseMapper中都已經得到了實作,我們可以直接使用
2.插入
@SpringBootTest
public class MybatisPlusTest {
@Test
public void testInsert(){
User user = new User(null,"王世超",23,"[email protected]");
int result = userMapper.insert(user);
System.out.println("受影響行數"+result);
System.out.println("id自動擷取"+user.getId());
}
}
3.删除
通過id删除記錄
@Test
public void testDeleteById(){
//通過id删除使用者資訊
//DELETE FROM user WHERE id=? int result = userMapper.deleteById(1475754982694199298L); System.out.println("受影響行數:"+result);
}
4.修改
@Test
public void testUpdateById(){
User user = new User(4L, "admin", 22, null);
//UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println("受影響行數:"+result);
}
5.查詢
@Test
public void testSelectById(){
//根據id查詢使用者資訊
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
6.通用Service
說明:
通用 Service CRUD 封裝IService接口,進一步封裝 CRUD 采用 get 查詢單行 remove 删除 list 查詢集合 page 分頁 字首命名方式區分 Mapper 層避免混淆,
泛型 T 為任意實體對象
建議如果存在自定義通用 Service 方法的可能,請建立自己的 IBaseService 繼承
Mybatis-Plus 提供的基類
官網位址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%A3
a>IService
MyBatis-Plus中有一個接口 IService和其實作類 ServiceImpl,封裝了常見的業務層邏輯,詳情檢視源碼IService和ServiceImpl
b>建立Service接口和實作類
/**
* UserService繼承IService模闆提供的基礎功能
*/
public interface UserService extends IService<User> {}
/*** ServiceImpl實作了IService,提供了IService中基礎功能的實作 * 若ServiceImpl無法滿足業務需求,則可以使用自定的UserService定義方法,并在實作類中實作 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
四、常用注解
1.@TableName
經過以上的測試,在使用MyBatis-Plus實作基本的CRUD時,我們并沒有指定要操作的表,隻是在Mapper接口繼承BaseMapper時,設定了泛型User,而操作的表為user表
由此得出結論,MyBatis-Plus在确定操作的表時,由BaseMapper的泛型決定,即實體類型決定,且預設操作的表名和實體類型的類名一緻
提出問題若實體類型的類名和要操作的表的表名不一緻,會出現什麼問題?
程式抛出異常,Table ‘mybatis_plus.user’ doesn’t exist,因為現在的表名為t_user,而預設操作的表名和實體類型的類名一緻,即user表
通過@TableName解決問題
隻需要在實體類上添加@TableName(“t_user”),辨別實體類對應的表,即可成功執行SQL語句
@TableName("t_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
通過全局配置解決問題
在開發的過程中,我們經常遇到以上的問題,即實體類所對應的表都擁有固定的字首,例如t_或tbl_此時,可以使用MyBatis-Plus提供的全局配置,為實體類所對應的表名設定預設的字首,那麼就不需要在每個實體類上通過@TableName辨別實體類對應的表
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的預設字首
table-prefix: t_
2.@TableId
經過以上的測試,MyBatis-Plus在實作CRUD時,會預設将id作為主鍵列,并在插入資料時,預設基于雪花算法的政策生成id
提出問題若實體類和表中表示主鍵的不是id,而是其它字段,例如uid,Mybatis-Plus會自動識别uid為主鍵列嗎?我們實體類中的屬性id改為uid,将表中的字段id也改為uid,測試添加功能
這時候程式抛出異常,Field ‘uid’ doesn’t have a default value,說明MyBatis-Plus沒有将uid作為主鍵指派
通過@TableId解決問題
在實體類中uid屬性上通過@TableId将其辨別為主鍵,即可成功執行SQL語句
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
@TableId
private Long uid;
private String name;
private Integer age;
private String email;
}
@TableId的value屬性
若實體類中對應的屬性為id,而表中表示主鍵的字段為uid,此時若隻在屬性id上添加注解@TableId,則抛出異常Unknown column ‘id’ in ‘field list’,即MyBatis-Plus仍然會将id作為表的主鍵操作,而表中表示主鍵的是字段uid
此時需要通過過@TableId注解的value屬性,指定表中的主鍵字段,@TableId(“uid”)或@TableId(value=“uid”)
@TableName("t_user")
public class User {
@TableId(value = "uid")
private Long id;
private String name;
private Integer age;
private String email;
}
@TableId的type屬性
type屬性用來定義主鍵政策
常用的主鍵政策:
值 | 描述 |
IdType.ASSIGN_ID(預設) | 基于雪花算法的政策生成資料id,與資料庫id是否設定自增無關 |
IdType.AUTO | 使用資料庫的自增政策,注意,該類型請確定資料庫設定了id自增, |
配置全局主鍵政策:
mybatis-plus:
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的預設字首
table-prefix: t_
# 配置MyBatis-Plus的主鍵政策
id-type: auto
3.@TableField
經過以上的測試,我們可以發現,MyBatis-Plus在執行SQL語句時,要保證明體類中的屬性名和表中的字段名一緻,如果實體類中的屬性名和字段名不一緻的情況,會出現什麼問題呢?
情況1
若實體類中的屬性使用的是駝峰命名風格,而表中的字段使用的是下劃線命名風格
例如實體類屬性userName,表中字段user_name
此時MyBatis-Plus會自動将下劃線命名風格轉化為駝峰命名風格
相當于在MyBatis中配置
情況2
若實體類中的屬性和表中的字段不滿足情況1
例如實體類屬性name,表中字段username
此時需要在實體類屬性上使用@TableField(“username”)設定屬性所對應的字段名
@TableName("t_user")
public class User {
@TableId(value = "uid")
private Long id;
@TableField("username")
private String name;
private Integer age;
private String email;
}
4.@TableLogic
邏輯删除
實體删除:真實删除,将對應的資料從資料庫删除,之後查詢不到此條被删除的資料
邏輯删除:假删除,将對應資料中代表是否被删除字段的狀态修改為“被删除狀态”,之後在 資料庫中仍舊看到此條資料記錄
使用場景:可以進行資料恢複
@TableName("t_user")
public class User {
@TableId(value = "uid")
private Long id;
@TableField("username")
private String name;
private Integer age;
private String email;
@TableLogic
private Integer isDeleted;
}
測試
測試删除功能,真正執行的是修改
UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0
測試查詢功能,被邏輯删除的資料預設不會被查詢
SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
五、條件構造函數和常用接口
1.wapper介紹
Wrapper : 條件構造抽象類,最頂端父類
AbstractWrapper : 用于查詢條件封裝,生成 sql 的 where 條件
QueryWrapper : 查詢條件封裝
UpdateWrapper : Update 條件封裝
AbstractLambdaWrapper : 使用Lambda 文法
LambdaQueryWrapper :用于Lambda文法使用的查詢Wrapper
LambdaUpdateWrapper : Lambda 更新封裝Wrapper
2.QueryWrapper
a.組裝查詢條件
@Test
public void test01(){
// 查詢條件包含a 年齡在20到30之間,并且郵箱不為null的使用者資訊
//SELECT 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)
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("username","張")
.between("age",20,30)
.isNotNull("email");
List<User> lists = userMapper.selectList(wrapper);
lists.forEach(System.out::println);
}
b.組裝排序條件
@Test
public void test02(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
c.組裝删除條件
@Test
public void test03(){
// 删除email為空的使用者
//DELETE FROM t_user WHERE (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
// 條件構造器也可以建構删除語句的條件
int delete = userMapper.delete(queryWrapper);
System.out.println("受影響的行數"+delete);
}
d.條件的優先級
@Test
public void test04(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username","a")
.gt("age",20)
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("[email protected]");
int update = userMapper.update(user, queryWrapper);
System.out.println("受影響的行數"+update);
}
@Test
public void test041(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username","a")
.and(i -> i.gt("age",20).or().isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("[email protected]");
int update = userMapper.update(user, queryWrapper);
System.out.println("受影響的行數"+update);
}
e.組裝select子句
@Test
public void test05(){
// 查詢使用者資訊的username和age字段
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username","age");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
f.實作子查詢
@Test
public void test06(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id","select id from t_user where id <= 3");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}
3.UpdateWrapper
@Test
public void test07(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 将(年齡大于20或郵箱為null)并且使用者中包含有a的使用者資訊修改
// 組裝set子句以及修改條件
// lambda表達式内的邏輯優先運算
updateWrapper
.set("age",18)
.set("email","[email protected]")
.like("username","a")
.and(i -> i.gt("age",20).or().isNull("email"));
// 這裡必須要建立user對象,否則無法應用自動填充,如果沒有自動填充,可以設定為null
int update = userMapper.update(null, updateWrapper);
System.out.println(update);
}
4、condition
在真正的開發過程中,組裝條件是常見的功能,而這些條件資料來源與使用者輸入,是可選的,是以我們在組裝這些條件時,必須先判斷使用者是否選擇了這些條件,若選擇則需要組裝這些條件,若沒有選擇則一定不能組裝,以免影響SQL執行的結果
@Test
public void test08(){
// 定義查詢條件,有可能為空 (使用者未輸入或未選擇)
String username = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// Stringutils.isNotBlank判斷某字元串是否不為空且長度不為0且不由空白符(whitespace)構成
if (StringUtils.isBlank(username)){
queryWrapper.like("username","a");
}
if (ageBegin != null){
queryWrapper.ge("age",ageBegin);
}
if (ageEnd != null){
queryWrapper.le("age",ageEnd);
}
// select id,username as name,age,email,is_deleted from t_user where(age >= ? AND age<= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test
public void test081(){
// 定義查詢條件,有可能為null(使用者魏輸入或未選擇)
String username = null;
Integer ageBegin = 10;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.like(StringUtils.isNotBlank(username),"username","張")
.ge(ageBegin != null,"age",ageBegin)
.le(ageEnd != null,"age",ageEnd);
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (age >= ? AND age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
5、LambdaQueryWrapper
@Test
public void test09() {
//定義查詢條件,有可能為null(使用者未輸入)
String username = "a"; Integer ageBegin = 10; Integer ageEnd = 24; 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> users = userMapper.selectList(queryWrapper); users.forEach(System.out::println); }
6、LambdaUpdateWrapper
@Test public void test10() {
//組裝set子句
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper
.set(User::getAge, 18)
.set(User::getEmail, "[email protected]")
.like(User::getName, "a")
.and(i -> i.lt(User::getAge, 24)
.or().isNull(User::getEmail));
//lambda 表達式内的邏輯優先運算
User user = new User();
int result = userMapper.update(user, updateWrapper); System.out.println("受影響的行數:" + result);
}
六、插件
1.分頁插件
a添加配置類
@Configuration
@MapperScan("com.mybatisplus.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
b測試
@Test
public void testPage(){
// 設定分頁參數
Page<User> page = new Page<>(2, 5);
userMapper.selectPage(page,null);
// 擷取分頁資料
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("目前頁"+page.getCurrent());
System.out.println("每頁顯示的條數"+page.getSize());
System.out.println("總記錄數:"+page.getTotal());
System.out.println("總頁數"+page.getPages());
System.out.println("是否有上一頁"+page.hasPrevious());
System.out.println("是否有下一頁"+page.hasNext());
}
2.xml自定義分頁
a.UserMapper中定義接口方法
/**
* 根據年齡查詢使用者清單,分頁顯示
* @param page 分頁對象 xml中可以從裡面進行取值,傳遞參數PAGE即自動分頁,必須放在第一位
* @param age 年齡
* @return
*/
Page<User> selectPageVo(@Param("page") Page<User> page,
@Param("age") Integer age);
b>UserMapper.xml中編寫SQL
<select id="selectPageVo" resultType="User">
select uid,username,age,email from t_user where age > #{age}
select>
c>測試
@Test
public void testPage(){
// 設定分頁參數
Page<User> page = new Page<>(1, 5);
userMapper.selectPageVo(page,20);
// 擷取分頁資料
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("目前頁"+page.getCurrent());
System.out.println("每頁顯示的條數"+page.getSize());
System.out.println("總記錄數:"+page.getTotal());
System.out.println("總頁數"+page.getPages());
System.out.println("是否有上一頁"+page.hasPrevious());
System.out.println("是否有下一頁"+page.hasNext());
}
3.樂觀鎖
a>場景
一件商品,成本價是80元,售價是100元,老闆先是通知小李,說你去把商品價格增加50元。小李正在玩遊戲,耽擱了一個小時,正好一個小時後,老闆覺得商品價格增加到150元,價格太高,可能影響銷量,又通知小王,你把商品價格降低30元。
此時,小李和小王同時操作商品背景系統。小李操作的時候,系統先取出商品價格100元;小王也在操作,取出的商品價格也是100元。小李将價格加了50元,并将100+50=150元存入了資料庫;小王将商品減了30元,并将100-30=70元存入了資料庫。是的,如果沒有鎖,小李的操作就完全被小王的覆寫了。現在商品價格是70元,比成本價低10元。幾分鐘後,這個商品很快出售了1千多件商品,老闆虧1萬多。
b>樂觀鎖與悲觀鎖
上面的故事,如果是樂觀鎖,小王儲存價格前,會檢查價格是否被修改過了,如果被修改過了,則重新取出的被修改後的價格,150元,這樣它會将120元存入資料庫。
如果是悲觀鎖,小李取出資料後,小王隻能等小李操作完之後,才能對價格進行操作,也會保證最終的價格是120元。
c>模拟修改沖突
資料庫中增加商品表
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);
添加實體
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_product")
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
添加mapper
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}
測試
@Test
public void testConUpdate(){
// 1.小李
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的價格"+p1.getPrice());
// 2.小王
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的價格"+p2.getPrice());
// 3.小李價格加了50元,存入了資料庫
p1.setPrice(p1.getPrice()+50);
int update = productMapper.updateById(p1);
System.out.println("小李修改的結果"+update);
// 4.小娃昂将商品減了元,存入資料庫
p2.setPrice(p2.getPrice()-30);
int result = productMapper.updateById(p2);
System.out.println("小王修改的結果"+result);
// 最後的結果
Product p3 = productMapper.selectById(1L);
System.out.println("最後的結果魏"+p3);
}
d>樂觀鎖實作流程
資料庫中添加version字段
取出記錄時,擷取目前version
SELECT id,`name`,price,`version` FROM product WHERE id=1
更新時,version + 1,如果where語句中的version版本不對,則更新失敗
UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
e>Mybatis-Plus實作樂觀鎖
修改實體類
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_product")
public class Product {
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
添加樂觀鎖配置
@Configuration
@MapperScan("com.mybatisplus.mapper")
public class MybatisPlusConfig {
@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 testConUpdate(){
// 1.小李
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的價格"+p1.getPrice());
// 2.小王
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的價格"+p2.getPrice());
// 3.小李價格加了50元,存入了資料庫
p1.setPrice(p1.getPrice()+50);
int update = productMapper.updateById(p1);
System.out.println("小李修改的結果"+update);
// 4.小娃昂将商品減了元,存入資料庫
p2.setPrice(p2.getPrice()-30);
int result = productMapper.updateById(p2);
System.out.println("小王修改的結果"+result);
if (result == 0){
p2=productMapper.selectById(1L);
p2.setPrice(p2.getPrice()-30);
result = productMapper.updateById(p2);
}
System.out.println("小王修改重試的結果"+result);
// 最後的結果
Product p3 = productMapper.selectById(1L);
System.out.println("最後的結果魏"+p3);
}
七、通用枚舉
表中的有些字段值是固定的,例如性别(男或女),此時我們可以使用Mybatis-Plus的通用美劇來實作
1.資料庫添加字段sex
2.建立通用枚舉類型
@Getter
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
3.配置掃描通用枚舉
mybatis-plus:
configuration:
# 配置Mybatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置Mybatis-plus操作表的預設字首
table-prefix: t_
# 配置Mybatis-plus的主鍵政策
# id-type: auto
# 配置掃描通用枚舉
type-enums-package: com.mybatisplus.enums
# 配置類型别名所對應的包
type-aliases-package: com.mybatisplus.pojo
4.測試
@Test
public void test01(){
User user = new User();
user.setName("Enum");
user.setAge(20);
// 設定性别資訊為枚舉項,會将@EnumValue注解所辨別的屬性值存儲到資料庫
user.setSex(SexEnum.MALE);
//INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? )
// Parameters: Enum(String), 20(Integer), 1(Integer)
userMapper.insert(user);
}
八、代碼生成器
1.引入依賴
<dependency>
<groupId>com.baomidougroupId>
<artifactId>mybatis-plus-generatorartifactId>
<version>3.5.1version>
dependency>
<dependency>
<groupId>org.freemarkergroupId>
<artifactId>freemarkerartifactId>
<version>2.3.31version>
dependency>
2.快速生成
public class Test03 {
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("mybatisplus") //設定作者
.fileOverride() //覆寫已經生成的檔案
.outputDir("D://mybatis_plus");//指定輸出目錄
})
.packageConfig(builder -> {
builder.parent("com.mybatisplus")//設定父包名
.moduleName("mybatisplus")//設定父包子產品名
.pathInfo((Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")));
// 設定mapperXml生成路徑
})
.strategyConfig(builder -> {
builder.addInclude("t_user")//設定需要生成的表名
.addTablePrefix("t_","c_");//設定字首
})
.templateEngine(new FreemarkerTemplateEngine()).execute(); //使用Freemarker引擎模闆,預設的是velocity引擎模闆)
}
}
九、多資料源
适用于多種場景:純粹多庫、 讀寫分離、 一主多從、 混合模式等
目前我們就來模拟一個純粹多庫的一個場景,其他場景類似
場景說明:
我們建立兩個庫,分别為:mybatis_plus(以前的庫不動)與mybatis_plus_1(建立),将
mybatis_plus庫的product表移動到mybatis_plus_1庫,這樣每個庫一張表,通過一個測試用例
分别擷取使用者資料與商品資料,如果擷取到說明多庫模拟成功
1.建立資料庫及表
CREATE 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;
2.引入依賴
<dependency>
<groupId>com.baomidougroupId>
<artifactId>dynamic-datasource-spring-boot-starterartifactId> <version>3.5.0version>
dependency>
3.配置多資料源
spring:
# 配置資料源資訊
datasource:
dynamic:
# 設定預設的資料源或者資料源組,預設值即為master
primary:
# 嚴格比對資料源,預設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: 123456
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: 123456
4.建立使用者service
public interface UserService extends IService<User> {
}
@DS("master")
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
}
5.建立商品service
public interface ProductService extends IService<Product> { }
@DS("slave_1")
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService { }
6.測試
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Test
public void testDataSource(){
System.out.println(userService.getById(1L));
System.out.println(productService.getById(1L));
}
十、MybatisX插件
MyBatis-Plus為我們提供了強大的mapper和service模闆,能夠大大的提高開發效率
但是在真正開發過程中,MyBatis-Plus并不能為我們解決所有問題,例如一些複雜的SQL,多表
聯查,我們就需要自己去編寫代碼和SQL語句,我們該如何快速的解決這個問題呢,這個時候可
以使用MyBatisX插件