穀粒學院-2-mybatisplus

Wang發表於2021-07-25

一、參考文件

官網:http://mp.baomidou.com/

參考教程:http://mp.baomidou.com/guide/

MyBatis-Plus(簡稱 MP)是一個 MyBatis 的增強工具,在 MyBatis 的基礎上只做增強不做改變,為簡化 開發、提高效率而生。


快速開始參考:http://mp.baomidou.com/guide/quick-start.html

測試專案: mybatis_plus

資料庫:mybatis_plus

二、資料庫準備工作

先建立mybatis_plus資料庫

在內執行指令碼

CREATE TABLE user
(
 id BIGINT(20) NOT NULL COMMENT '主鍵ID',
 name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
 age INT(11) NULL DEFAULT NULL COMMENT '年齡',
 email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
 PRIMARY KEY (id)
);

插入資料

INSERT INTO USER (id, NAME, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

結果:

穀粒學院-2-mybatisplus

三、流程:

匯入依賴

<!--mybatis-plus-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

資料來源配置

# 資料來源
# 資料庫驅動在mysql8之前使用com.mysql.cj.jdbc.Driver,之後使用com.mysql.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# Spring Boot 2.1 整合了 8.0版本的jdbc驅動,這個版本的 jdbc 驅動需要新增這個字尾
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

建立pojo類

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

建立包 mapper 編寫Mapper 介面

@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {

}

測試

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    UserMapper userMapper;

    @Test
    void contextLoads() {
        System.out.println(userMapper.selectList(null));
    }

}

結果:

image-20210724223001359

四、新增日誌操作

配置資訊

#mybatis日誌
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

出現日誌

image-20210724223648240

五、insert操作

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    UserMapper userMapper;

    @Test
    void contextLoads() {
        User hell = new User();
        hell.setName("hell");
        hell.setAge(23);
        hell.setEmail("22@qq,com");
        int result = userMapper.insert(hell);
        //result是程式碼影響的函式
        System.out.println(result);
        //可以看到id值自動填入,這是mp幫我們做到的
        //注意:資料庫插入id值預設為:全域性唯一id
        System.out.println(hell);
    }

}

結果:

穀粒學院-2-mybatisplus

六、主鍵策略

1、全域性唯一ID策略

MyBatis-Plus預設的主鍵策略是:ID_WORKER 全域性唯一ID 參考資料:分散式系統唯一ID生成方案彙總:https://www.cnblogs.com/haoxinyue/p/5208136.html

2、自增策略

流程:

建立資料庫時設定id自增

建立pojo類時

@TableId(type = IdType.AUTO)
private Long id;

結果:

穀粒學院-2-mybatisplus

其他主鍵策略

/**
* 資料庫ID自增
*/
AUTO(0),
/**
* 該型別為未設定主鍵型別
* 使用者自己輸入
*/
NONE(1),
/**
* 使用者輸入ID
* 使用者自己輸入
*/
INPUT(2),
/**
* 全域性唯一ID (idWorker)
* 一般對應Long數字型別
*/
ID_WORKER(3),
/**
* 全域性唯一ID (UUID)
*/
UUID(4),
/**
* 字串全域性唯一ID (idWorker 的字串表示)
*/
ID_WORKER_STR(5);

七、更新資料操作

流程:

@SpringBootTest
class DemoApplicationTests {
    @Autowired
    UserMapper userMapper;

    @Test
    void contextLoads() {
        User hell = new User();
        hell.setId(2L);
        hell.setName("hell");
        hell.setAge(23);
        hell.setEmail("22@qq,com");
        int result = userMapper.updateById(hell);
        //result是程式碼影響的函式
        System.out.println(result);
        //可以看到id值自動填入,這是mp幫我們做到的
        System.out.println(hell);
    }

}

mp實現樂觀鎖

什麼是樂觀鎖:

是一種解決方案,主要解決多人併發操作時更新丟失問題,對應mysql事務的隔離性

悲觀鎖:

與樂觀鎖對應都是解決多人併發操作時的更新丟失問題,但時悲觀鎖是序列的,簡單來說就是我運算元據庫的時候別人不能運算元據庫

主要適用場景:當要更新一條記錄的時候,希望這條記錄沒有被別人更新,也就是說實現執行緒安全的資料更新

樂觀鎖實現方法:

  • 取出記錄時,獲取當前version
  • 更新時,帶上這個version
  • 執行更新時, set version = newVersion where version = oldVersion
  • 如果version不對,就更新失敗

流程:

在資料庫中新增版本欄位

穀粒學院-2-mybatisplus

pojo類中

@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;

需要在欄位自動填充中初始化版本號

@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        /*metaObject是後設資料物件,就比如在jdbc中query一個資料之後也會得到一個後設資料
        *這個後設資料內包含了查詢到的資料在資料庫中儲存的x和y資訊
         */
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        /*給版本號在建立時提供一個初始值1*/
        this.setFieldValByName("version",1,metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

然後就直接測試了

但注意,一定要遵守先查後改的原則,在查的時候得到包含了version資訊的user物件,在更新的時候就會去找到這個版本號,確認是否對應,如果一致然後才更新,否則更新失敗,而且再更新user時會對version進行+1操作

可見mp的樂觀鎖主要解決從查到資料到更新資料途中是否有其他人或其他執行緒修改了資料

/*樂觀鎖成功*/
@Test
public void testOptimisticLockerSuccess(){
    /*查詢出使用者*/
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","asdfasdf");
    List<User> users = userMapper.selectByMap(map);
    System.out.println(users);

    /*修改使用者*/
    for (User user:users){
        System.out.println("修改前"+user);
        user.setAge(20);
        int i = userMapper.updateById(user);
        System.out.println(i);
        System.out.println("修改後"+user);
    }

}

/*樂觀鎖失敗*/
@Test
public void testOptimisticLockerFailures(){
    /*查詢出使用者*/
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","asdfasdf");
    List<User> users = userMapper.selectByMap(map);
    System.out.println(users);

    /*修改使用者*/
    for (User user:users){
        System.out.println("修改前"+user);
        user.setAge(20);
        user.setVersion(user.getVersion()-1);
        int i = userMapper.updateById(user);
        System.out.println(i);
        System.out.println("修改後"+user);
    }

}

八、自動填充

比如建立時間和更新時間需要自動填充就會很方便

流程:

建立資料庫時新增兩個欄位

穀粒學院-2-mybatisplus

在pojo類中

@TableField(fill = FieldFill.INSERT)
private Date createTime;

@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

建立handle包下面建立一個元件

@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        /*metaObject是後設資料物件,就比如在jdbc中query一個資料之後也會得到一個後設資料
        *這個後設資料內包含了查詢到的資料在資料庫中儲存的x和y位置資訊
         */
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

九、查詢操作

1、通過id查詢

//查詢單個使用者
        User user = userMapper.selectById(1);
        System.out.println(user);

2、通過多個id查詢

//查詢多個使用者
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
System.out.println(users);

3、條件查詢

//條件查詢
HashMap<String, Object> map = new HashMap<>();
map.put("name","Tom");
map.put("age",28);
List<User> users1 = userMapper.selectByMap(map);
System.out.println(users1);

4、分頁查詢

MyBatis Plus自帶分頁外掛,只要簡單的配置即可實現分頁功能

流程:

需要新增一個bean到IOC容器中去

//mybatisplus的分頁外掛
@Bean
public PaginationInterceptor paginationInterceptor(){
    return new PaginationInterceptor();
}

再分頁查詢

//分頁查詢
Page<User> page = new Page<>(1,5);
//selectPage方法後一個引數是wrapper,包裝條件
userMapper.selectPage(page, null);

//通過page獲取全部的分頁資料
List<User> records = page.getRecords();
System.out.println(records);
System.out.println(page.getCurrent());	//當前頁
System.out.println(page.getPages());	//有多少頁
System.out.println(page.getSize()); //每頁多少條記錄
System.out.println(page.getTotal());	//總共有多少條記錄
System.out.println(page.hasNext());		//是否有下一頁
System.out.println(page.hasPrevious());		//是否有上一頁

十、刪除操作

  • 物理刪除:真實刪除,將對應資料從資料庫中刪除,之後查詢不到此條被刪除資料

  • 邏輯刪除:假刪除,將對應資料中代表是否被刪除欄位狀態修改為“被刪除狀態”,之後在資料庫中仍 舊能看到此條資料記錄

1、物理刪除:

通過id刪除

/*通過id刪除*/
@Test
public void deleteById(){

    int i = userMapper.deleteById(2L);
    System.out.println(i);
}

通過多個id批量刪除

/*通過多個id批量刪除*/
@Test
public void deleteBatchIds(){

    int i = userMapper.deleteBatchIds(Arrays.asList(1L, 3L));
    System.out.println(i);
}

條件刪除

/*條件刪除*/
@Test
public void deleteByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("id",1418944135407554564L);

    int i = userMapper.deleteByMap(map);
    System.out.println(i);
}

2、邏輯刪除

流程:

在資料庫中新增deleted欄位

穀粒學院-2-mybatisplus

pojo中操作

@TableLogic
@TableField(fill = FieldFill.INSERT)
public Integer deleted;

用自動填充來初始化deleted值

@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        /*metaObject是後設資料物件,就比如在jdbc中query一個資料之後也會得到一個後設資料
        *這個後設資料內包含了查詢到的資料在資料庫中儲存的x和y資訊
         */
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        /*給版本號在建立時提供一個初始值1*/
        this.setFieldValByName("version",1,metaObject);
        /*給被刪除狀態設定一個初始值;
        * 預設0為沒刪除,1為刪除,
        * 可以在配置引數檔案中中修改*/
        this.setFieldValByName("deleted",0,metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime",new Date(),metaObject);
    }
}

配置Bean元件

/*mybatisplus的邏輯刪除外掛*/
@Bean
public ISqlInjector sqlInjector() {
    return new LogicSqlInjector();
}

測試:

/*通過id刪除*/
@Test
public void deleteById(){

    int i = userMapper.deleteById(2L);
    System.out.println(i);
}

注意:內部的sql語句包含了where deleted = 0,所以沒有初始化deleted為0的話是刪除不了的,會找不到這個資料

測試刪除之後的查詢:

MyBatis Plus中查詢操作也會自動新增邏輯刪除欄位的判斷

/**
* 測試 邏輯刪除後的查詢:
* 不包括被邏輯刪除的記錄
*/
@Test
public void testLogicDeleteSelect() {
User user = new User();
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}

邏輯層的刪除資料也查不到了

十一、效能分析外掛

效能分析攔截器,用於輸出每條 SQL 語句及其執行時間 SQL 效能執行分析,開發環境使用,超過指定時間,停止執行。有助於發現問題

主要用於開發環境

流程:

加一個配置bean即可

/*sql效能分析外掛*/
@Bean
@Profile({"dev"})   //只有開發環境為dev時才會開啟開啟這個bean配置
public PerformanceInterceptor performanceInterceptor(){
    PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
    performanceInterceptor.setMaxTime(100); //超過此設定的ms的sql不執行
    performanceInterceptor.setFormat(true); //sql語句是否格式化,預設為false
    return performanceInterceptor;
}

測試:

穀粒學院-2-mybatisplus

十二、wrapper實現複雜查詢

穀粒學院-2-mybatisplus
  • Wrapper : 條件構造抽象類,最頂端父類
    • AbstractWrapper : 用於查詢條件封裝,生成 sql 的 where 條件
      • QueryWrapper : Entity 物件封裝操作類,不是用lambda語法
      • UpdateWrapper : Update 條件封裝,用於Entity物件更新操作
    • AbstractLambdaWrapper : Lambda 語法使用 Wrapper統一處理解析 lambda 獲取 column。
      • LambdaQueryWrapper :看名稱也能明白就是用於Lambda語法使用的查詢Wrapper
      • LambdaUpdateWrapper : Lambda 更新封裝Wrapper

1、ge、gt、le、lt、isNull、isNotNull

大於等於、大於、小於等於、小於、為空、非空

@Test
public void testDelete() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper
 		.isNull("name")
 		.ge("age", 12)
 		.isNotNull("email");
	int result = userMapper.delete(queryWrapper);
	System.out.println("delete return count = " + result);
}

對應sql:

UPDATE user SET deleted=1 WHERE deleted=0 AND name IS NULL AND age >= ? AND email IS NOT NULL

2、eq、ne

等於、不等於

@Test
public void testSelectOne() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.eq("name", "Tom");
	User user = userMapper.selectOne(queryWrapper);
	System.out.println(user);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name = ?

3、between、notBetween

@Test
public void testSelectCount() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.between("age", 20, 30);
	Integer count = userMapper.selectCount(queryWrapper);
	System.out.println(count);
}

對應sql:

SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN ? AND ?

4、allEq

所有條件全部一致

@Test
public void testSelectList() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	Map<String, Object> map = new HashMap<>();
	map.put("id", 2);
	map.put("name", "Jack");
	map.put("age", 20);
	queryWrapper.allEq(map);
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 AND name = ? AND id = ? AND age = ?

5、like、notLike、likeLeft、likeRight

@Test
public void testSelectMaps() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper
		.notLike("name", "e")
		.likeRight("email", "t");
	List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);//返回值是Map列表
	maps.forEach(System.out::println);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND name NOT LIKE ? AND email LIKE ?

6、in、notIn、inSql、notinSql、exists、notExists

in、notIn:

  • notIn("age",{1,2,3})--->age not in (1,2,3)
  • notIn("age", 1, 2, 3)--->age not in (1,2,3)

inSql、notinSql:可以實現子查詢

  • 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
  • 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
@Test
public void testSelectObjs() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	//queryWrapper.in("id", 1, 2, 3);
	queryWrapper.inSql("id", "select id from user where id < 3");
	List<Object> objects = userMapper.selectObjs(queryWrapper);//返回值是Object列表
	objects.forEach(System.out::println);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)

7、or、and

@Test
public void testUpdate1() {
	//修改值
	User user = new User();
	user.setAge(99);
	user.setName("Andy");
	//修改條件
	UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
	userUpdateWrapper
		 .like("name", "h")
        //不呼叫or則預設為使用 and 連
		 .or()
 		.between("age", 20, 30);
	int result = userMapper.update(user, userUpdateWrapper);
System.out.println(result);
}

對應sql:

UPDATE user SET name=?, age=?, update_time=? WHERE deleted=0 AND name LIKE ? OR age
BETWEEN ? AND ?

8、巢狀or、巢狀and

這裡使用了lambda表示式,or中的表示式最後翻譯成sql時會被加上圓括號

@Test
public void testUpdate2() {
	//修改值
	User user = new User();
	user.setAge(99);
	user.setName("Andy");
	//修改條件
	UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
	userUpdateWrapper
		 .like("name", "h")
		 .or(i -> i.eq("name", "李白").ne("age", 20));
	 int result = userMapper.update(user, userUpdateWrapper);
	System.out.println(result);
}

9、orderBy、orderByDesc、orderByAsc

@Test
public void testSelectListOrderBy() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.orderByDesc("id");
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version
FROM user WHERE deleted=0 ORDER BY id DESC

10、last

直接拼接到 sql 的最後

只能呼叫一次,多次呼叫以最後一次為準 有sql注入的風險,請謹慎使用

@Test
public void testSelectListLast() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.last("limit 1");
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

對應sql:

SELECT id,name,age,email,create_time,update_time,deleted,version FROM user WHERE deleted=0 limit 1

11、指定要查詢的列

@Test
public void testSelectListColumn() {
	QueryWrapper<User> queryWrapper = new QueryWrapper<>();
	queryWrapper.select("id", "name", "age");
	List<User> users = userMapper.selectList(queryWrapper);
	users.forEach(System.out::println);
}

對應sql:

SELECT id,name,age FROM user WHERE deleted=0

12、set、setSql

最終的sql會合並 user.setAge(),以及 userUpdateWrapper.set() 和 setSql() 中 的欄位

@Test
public void testUpdateSet() {
	//修改值
	User user = new User();
	user.setAge(99);
	//修改條件
	UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
	userUpdateWrapper
		.like("name", "h")
 		.set("name", "老李頭")//除了可以查詢還可以使用set設定修改的欄位
	 	.setSql(" email = '123@qq.com'");//可以有子查詢
	int result = userMapper.update(user, userUpdateWrapper);
}

對應sql:

UPDATE user SET age=?, update_time=?, name=?, email = '123@qq.com' WHERE deleted=0 AND name LIKE ?

相關文章