一、insert
1、插入操作
測試:
@SpringBootTest
class MybatisPlusApplicationTests {
@Autowired
UserMapper userMapper;
@Test
void insertUser() {
User user = new User();
user.setName("Ada");
user.setAge(30);
user.setEmail("[email protected]");
int i = userMapper.insert(user);
System.out.println("insert : " + i);
}
}
結果:
**注意:**數據庫插入id值默認爲:全局唯一id(雪花算法生成,下面會介紹)
2、常見主鍵生成策略
1)數據庫自增長序列或字段
最常見的方式。利用數據庫,全數據庫唯一。
優點:
- 簡單,代碼方便,性能可以接受。
- 數字ID天然排序,對分頁或者需要排序的結果很有幫助。
缺點:
- 在單個數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風險。
- 如果遇見多個系統需要合併或者涉及到數據遷移會相當痛苦。
- 分表分庫的時候會有麻煩。
2)UUID
常見的方式。可以利用數據庫也可以利用程序生成,一般來說全球唯一。
優點:
- 簡單,代碼方便。
- 生成ID性能非常好,基本不會有性能問題。
- 全球唯一,在遇見數據遷移,系統數據合併,或者數據庫變更等情況下,可以從容應對。
缺點:
- 沒有排序,無法保證趨勢遞增
- UUID往往是使用字符串存儲,查詢的效率比較低。
- 存儲空間比較大,如果是海量數據庫,就需要考慮存儲量的問題。
- 傳輸數據量大
3)Redis生成ID
當使用數據庫來生成ID性能不夠要求的時候,我們可以嘗試使用Redis來生成ID。這主要依賴於Redis是單線程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY來實現。
可以使用Redis集羣來獲取更高的吞吐量。假如一個集羣中有5臺Redis。可以初始化每臺Redis的值分別是1,2,3,4,5,然後步長都是5。各個Redis生成的ID爲:
A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25
優點:
- 不依賴於數據庫,靈活方便,且性能優於數據庫。
- 數字ID天然排序,對分頁或者需要排序的結果很有幫助。
缺點:
- 如果系統中沒有Redis,還需要引入新的組件,增加系統複雜度。
- 需要編碼和配置的工作量比較大。
4)Twitter的snowflake算法
snowflake是Twitter開源的分佈式ID生成算法,結果是一個long型的ID。其核心思想是:使用41bit作爲毫秒數,10bit作爲機器的ID(5個bit是數據中心,5個bit的機器ID),12bit作爲毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID),最後還有一個符號位,永遠是0。具體實現的代碼可以參看https://github.com/twitter-archive/snowflake/releases/tag/snowflake-2010
snowflake算法可以根據自身項目的需要進行一定的修改。比如估算未來的數據中心個數,每個數據中心的機器數以及統一毫秒可以能的併發數來調整在算法中所需要的bit數。
優點:
- 不依賴於數據庫,靈活方便,且性能優於數據庫。
- ID按照時間在單機上是遞增的。
缺點:
- 在單機上是遞增的,但是由於涉及到分佈式環境,每臺機器上的時鐘不可能完全同步,也許有時候也會出現不是全局遞增的情況。
3、MP的主鍵生成策略
1)ID_WORKER
MyBatis-Plus默認的主鍵策略是:ID_WORKER 全局唯一ID(雪花算法)
2)自增策略
要想主鍵自增需要配置如下主鍵策略:
- 需要在創建數據表的時候設置主鍵自增
- 實體字段中配置 @TableId(type = IdType.AUTO)
@TableId(type = IdType.AUTO)
private Long id;
要想影響所有實體的配置,可以設置全局主鍵配置
#全局設置主鍵生成策略
mybatis-plus.global-config.db-config.id-type=auto
其它主鍵策略:分析 IdType 源碼可知:
public enum IdType {
/**
* 數據庫ID自增
*/
AUTO(0),
/**
* 該類型爲未設置主鍵類型
*/
NONE(1),
/**
* 用戶輸入ID
* <p>該類型可以通過自己註冊自動填充插件進行填充</p>
*/
INPUT(2),
/* 以下3種類型、只有當插入對象ID 爲空,才自動填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);
private final int key;
IdType(int key) {
this.key = key;
}
}
二、update
1、根據Id更新操作
@Autowired
UserMapper userMapper;
@Test
void updateUser() {
User user = new User();
user.setId(2L);
user.setAge(120);
int row = userMapper.updateById(user);
System.out.println(row);
}
此時自動生成的SQL語句爲:UPDATE user SET age=? WHERE id=?
2、自動填充
項目中經常會遇到一些數據,每次都使用相同的方式填充,例如記錄的創建時間,更新時間等。
我們可以使用MyBatis Plus的自動填充功能,完成這些字段的賦值工作:
1)數據庫表中添加自動填充字段
在User表中添加datetime類型的新的字段 create_time、update_time
2)實體類上添加屬性以及註解
@Data
public class User {
...
//注意使用小駝峯,框架會自動將下劃線轉爲小駝峯
//設置自動填充時機
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
3)實現元對象處理器接口
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
//使用mp實現添加的自動填充時,這個方法就會執行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
}
//使用mp實現更新的自動填充時,這個方法就會執行
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
}
}
注意:不要忘記添加 @Component 註解
4)測試
添加:
@Test
void insertUser() {
User user = new User();
user.setName("Eva");
user.setAge(22);
user.setEmail("[email protected]");
int i = userMapper.insert(user);
System.out.println("insert : " + i);
}
修改:
@Test
void updateUser() {
User user = new User();
user.setId(1273807629415706625L);
user.setAge(22);
int row = userMapper.updateById(user);
System.out.println(row);
}
3、樂觀鎖
**主要適用場景:**當要更新一條記錄的時候,希望這條記錄沒有被別人更新,也就是說實現線程安全的數據更新
樂觀鎖實現方式:
- 取出記錄時,獲取當前version
- 更新時,帶上這個version
- 執行更新時,
set version = newVersion where version = oldVersion
- 如果version不對,就更新失敗
1)數據庫中添加version字段
ALTER TABLE `user` ADD COLUMN `version` INT
2)實體類添加version字段
並添加 @Version 註解
@Version
@TableField(fill = FieldFill.INSERT)
private Integer version; //版本號,用於樂觀鎖
3)元對象處理器接口添加version的insert默認值
//使用mp實現添加的自動填充時,這個方法就會執行
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("version", 1, metaObject);
...
}
特別說明:
- 支持的數據類型只有 int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整數類型下
newVersion = oldVersion + 1
newVersion
會回寫到entity
中- 僅支持
updateById(id)
與update(entity, wrapper)
方法 - 在
update(entity, wrapper)
方法下,wrapper
不能複用!!!
4)創建配置類 MybatisPlusConfig 並註冊 Bean
//不要忘了@Configuration註解
@Configuration
@MapperScan("cn.hanzhuang42.mybatisplus.mapper")
public class MyBatisPlusConfig {
/**
*樂觀鎖插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
5)測試樂觀鎖
插入數據:
@Test
void insertUser() {
User user = new User();
user.setName("奎託斯");
user.setAge(99);
user.setEmail("[email protected]");
int i = userMapper.insert(user);
System.out.println("insert : " + i);
}
更新數據:
@Test
void testOptimisticLocker() {
//先查詢
User user = userMapper.selectById(1273814734310735873L);
//再修改
user.setAge(200);
userMapper.updateById(user);
}
三、select
1、根據id查詢記錄
@Test
void testOptimisticLocker() {
//先查詢
User user = userMapper.selectById(1273814734310735873L);
//再修改
user.setAge(200);
userMapper.updateById(user);
}
2、通過多個id批量查詢
//多個id批量查詢
@Test
void testSelectBatch(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);
}
SQL語句用的時IN
==> Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user WHERE id IN ( ? , ? , ? )
==> Parameters: 1(Integer), 2(Integer), 3(Integer)
<== Columns: id, name, age, email, create_time, update_time, version
<== Row: 1, Jone, 18, test1@baomidou.com, null, null, null
<== Row: 2, Jack, 120, test2@baomidou.com, null, null, null
<== Row: 3, Tom, 28, test3@baomidou.com, null, null, null
<== Total: 3
3、分頁查詢
MyBatis Plus自帶分頁插件,只要簡單的配置即可實現分頁功能
1)創建配置類
//分頁插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2)測試selectPage分頁
@Test
void testPage() {
//1、創建page對象
//傳入兩個參數:當前頁碼 和 每頁顯示記錄數
Page<User> page = new Page<User>(1, 3);
//2、調用mp的分頁查詢方法
userMapper.selectPage(page, null);
//3、通過page獲取分頁數據
//分頁的所有信息都會封裝到page對象中
System.out.println(page.getCurrent());//當前頁碼
System.out.println(page.getRecords());//每個頁的數據
System.out.println(page.getSize());//每頁的記錄數量
System.out.println(page.getTotal());//表中的總記錄數
System.out.println(page.getPages()); //總頁數
System.out.println(page.hasNext()); //是否有下一頁
System.out.println(page.hasPrevious()); //是否有前一頁
}
可以在控制檯看到首先使用SELECT COUNT(1) FROM user
查詢了數據的總個數,然後使用LIMIT進行了分頁查詢
==> Preparing: SELECT COUNT(1) FROM user
==> Parameters:
<== Columns: COUNT(1)
<== Row: 8
==> Preparing: SELECT id,name,age,email,create_time,update_time,version FROM user LIMIT ?,?
==> Parameters: 0(Long), 3(Long)
<== Columns: id, name, age, email, create_time, update_time, version
<== Row: 1, Jone, 18, test1@baomidou.com, null, null, null
<== Row: 2, Jack, 120, test2@baomidou.com, null, null, null
<== Row: 3, Tom, 28, test3@baomidou.com, null, null, null
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@68b58644]
四、delete
1、根據id刪除記錄
//測試刪除,物理刪除
@Test
void testDelete() {
int row = userMapper.deleteById(1L);
System.out.println(row);
}
可以看到id爲1的數據被刪除
2、批量刪除
//批量物理刪除
@Test
void testDeleteBatch(){
int row = userMapper.deleteBatchIds(Arrays.asList(2, 3, 4));
System.out.println(row);
}
3、邏輯刪除
- 物理刪除:真實刪除,將對應數據從數據庫中刪除,之後查詢不到此條被刪除數據
- 邏輯刪除:假刪除,將對應數據中代表是否被刪除字段狀態修改爲“被刪除狀態”,之後在數據庫中仍舊能看到此條數據記錄
1)數據庫中添加 deleted字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean DEFAULT 0
2)實體類添加deleted 字段
@TableLogic
private Integer deleted;
3)application.properties 加入配置
此爲默認值,如果你的默認值和mp默認的一樣,該配置可無
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0
4)在 MybatisPlusConfig 中註冊 邏輯刪除Bean
//邏輯刪除插件
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
5)測試
首先插入一條數據,可以看到deleted默認值爲0
@Test
void insertUser() {
User user = new User();
user.setName("里昂");
user.setAge(99);
user.setEmail("[email protected]");
int i = userMapper.insert(user);
System.out.println("insert : " + i);
}
然後將這條數據進行刪除
//測試刪除,邏輯刪除
@Test
void testLogicDelete() {
int row = userMapper.deleteById(1273835938400808961L);
System.out.println(row);
}
從控制檯的sql語句可以看出,執行的操作時update,並不是delete
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 1273835938400808961(Long)
<== Updates: 1
數據庫中deleted的值也被設爲1
如果這時進行查詢所有操作可以看到,無法將上述數據查出:
@Test
void findAll() {
List<User> userList = userMapper.selectList(null);
System.out.println(userList);
}
可以看到查詢時也加了deleted=0
的判斷條件
==> Preparing: SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0
==> Parameters:
<== Columns: id, name, age, email, create_time, update_time, version, deleted
<== Row: 2, Jack, 120, test2@baomidou.com, null, null, null, 0
<== Row: 3, Tom, 28, test3@baomidou.com, null, null, null, 0
<== Row: 4, Sandy, 21, test4@baomidou.com, null, null, null, 0
<== Row: 5, Billie, 24, test5@baomidou.com, null, null, null, 0
<== Row: 1273618618428489730, Ada, 22, ada@qq.com, null, null, null, 0
<== Row: 1273807629415706625, Eva, 22, Eva@qq.com, 2020-06-19 10:39:40, 2020-06-19 10:43:32, null, 0
<== Row: 1273814734310735873, 奎託斯, 200, Eva@qq.com, 2020-06-19 11:07:54, 2020-06-19 11:11:29, 2, 0
<== Total: 7
五、性能分析
1、配置插件
1)在 MybatisPlusConfig 中配置
/**
* SQL執行效率插件
* 該插件只用於開發環境,不建議生產環境使用。
* dev: 開發環境
* test:測試環境
* prod:生產環境
*/
@Bean
@Profile({"dev","test"})// 設置 dev test 環境開啓
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(1000); //ms, maxTime SQL 執行最大時長,超過自動停止運行
performanceInterceptor.setFormat(true); //format SQL SQL是否格式化,默認false。
return performanceInterceptor;
}
2)Spring Boot 中設置dev環境
#環境設置:dev、test、prod
spring.profiles.active=dev
2、測試
1)常規測試
@Test
void findAll() {
List<User> userList = userMapper.selectList(null);
System.out.println(userList);
}
控制檯輸出:
Time:98 ms - ID:cn.hanzhuang42.mybatisplus.mapper.UserMapper.selectList
Execute SQL:SELECT id,name,age,email,create_time,update_time,version,deleted FROM user WHERE deleted=0
2)將maxTime 改小之後再次進行測試
performanceInterceptor.setMaxTime(1);
如果執行時間過長,則拋出異常:The SQL execution time is too large,
Time:279 ms - ID:cn.hanzhuang42.mybatisplus.mapper.UserMapper.selectList
...
com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The SQL execution time is too large, please optimize !
六、wapper介紹
1、相關類
- Wrapper : 條件構造抽象類,最頂端父類
- AbstractWrapper : 用於查詢條件封裝,生成 sql 的 where 條件
- QueryWrapper : Entity 對象封裝操作類,不使用lambda語法(常用)
- UpdateWrapper : Update 條件封裝,用於Entity對象更新操作
- AbstractLambdaWrapper : Lambda 語法使用 Wrapper統一處理解析 lambda 獲取 column。
- LambdaQueryWrapper :看名稱也能明白就是用於Lambda語法使用的查詢Wrapper
- LambdaQueryWrapper :看名稱也能明白就是用於Lambda語法使用的查詢Wrapper
- LambdaUpdateWrapper : Lambda 更新封裝Wrapper
2、QueryWrapper 使用
具體參考官網:https://mp.baomidou.com/guide/wrapper.html
//測試條件查詢
@Test
void testSelectQuery(){
//1、創建QueryWrapper對象
QueryWrapper<User> wrapper = new QueryWrapper<>();
//ge、gt、le、lt:大於等於 、大於、小於等於 、小於
//查詢age大於等30的用戶
// wrapper.ge("age", 30);
// List<User> users = userMapper.selectList(wrapper);
// users.forEach(System.out::println);
//eq、ne: 等於、不等於
// wrapper.eq("name", "奎託斯");
// wrapper.ne("name", "奎託斯");
// userMapper.selectList(wrapper).forEach(System.out::println);
//between
//查詢年齡在20到30之間的
// wrapper.between("age", 20, 30);
// userMapper.selectList(wrapper).forEach(System.out::println);
//like
// wrapper.like("name", "a");
// userMapper.selectList(wrapper).forEach(System.out::println);
//orderBy
// wrapper.orderByDesc("id");
// userMapper.selectList(wrapper).forEach(System.out::println);
//last:直接在sql語句後追加語句
// wrapper.between("age",20,30)
// .last("limit 1");
// userMapper.selectList(wrapper).forEach(System.out::println);
//指定查詢的列
wrapper.select("id","name");
userMapper.selectList(wrapper).forEach(System.out::println);
}