天天看點

MybatisPlus快速入門

MybatisPlus快速入門

一、MybatisPlus簡介

MybatisPlus

是一個MyBatis的增強工具,在 MyBatis 的基礎上隻做增強不做改變,為簡化開發、提高效率而生

二、快速開始

1、建立資料庫

mybatis_plus

2、建立User表

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)
);           

3、添加資料

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]');           

4、打開IDEA在pom.xml添加相關依賴

<!--mybatis-plus-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.3.1</version>
    </dependency>

    <!--mysql運作時依賴-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!--lombok用來簡化實體類-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>           

5、編寫連接配接資料庫的properties

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456

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

6、建立包entity編寫User實體類

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

7、建立mapper編寫UserMapper檔案(MybatisPlus核心持久層接口)

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

8、在主啟動類上添加掃面mapper檔案夾(稍後我們會統一放入MybatisPlusConfig檔案内)

@SpringBootApplication
@MapperScan("xxx.xxxx.mapper")
public class MybatisPlusApplication {
    ......
}           

9、在測試類中完成測試

@SpringBootTest
class MybatisPlusApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    void testMybatis_QuickStart() {
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    }

}           

10、在控制台檢視輸出效果

-

三、插入資料和主鍵政策

插入資料
@Autowired
    private UserMapper userMapper;

    @Test
    public void testInsert(){
        User user = new User();
        user.setName("張三");
        user.setAge(18);
        user.setEmail("[email protected]");

        int result = userMapper.insert(user);
        System.out.println("成功添加的行數:" + result);
        System.out.println("添加使用者的id為:" + user.getId());  //id:1272668550292856834 MybatisPlus預設主鍵政策為雪花算法
    }           
主鍵政策

1、ASSIGN_ID

MyBatis-Plus預設的主鍵政策是:ASSIGN_ID(使用了雪花算法)

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

2、AUTO自增政策

需要在資料庫的id字段設定主鍵

自增

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

3、要想影響所有實體的配置,可以設定全局主鍵配置(properties或yml檔案)

# 全局設定主鍵生成政策
mybatis-plus.global-config.db-config.id-type=auto           

四、更新資料、自動填充和樂觀鎖

更新資料
@Test
    public void testUpdate(){
        User user = new User();
        user.setId(1L);
        user.setName("李四");

        int result = userMapper.updateById(user);
        System.out.println("成功更新的行數:" + result);
    }           
自動填充

1、需求:項目中經常會遇到一些資料,每次都使用相同的方式填充,例如記錄的建立時間,更新時間等。

2、資料庫修改

在User表中添加datetime類型的新的字段 create_time、update_time

3、實體類修改

@Data
public class User {
    ......
        
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

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

4、實作元對象處理器接口

建立handler包編寫MyMetaObjectHandler檔案

@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("開始填充insertFill.......");
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("開始填充updateFill.......");
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}           

5、測試

@Test
    public void testAutoInsertFill(){
        User user = new User();
        user.setName("王五");
        user.setAge(20);
        user.setEmail("[email protected]");

        int result = userMapper.insert(user);
        System.out.println("成功添加的行數:" + result);  //2020-06-16 07:32:57.821(Timestamp), 2020-06-16 07:32:57.821(Timestamp)
    }           
樂觀鎖

1、場景:

一件商品,成本價是80元,售價是100元。老闆先是通知小李,說你去把商品價格增加50元。小李正在玩遊戲,耽擱了一個小時。正好一個小時後,老闆覺得商品價格增加到150元,價格太高,可能會影響銷量。又通知小王,你把商品價格降低30元

此時,小李和小王同時操作商品背景系統。小李操作的時候,系統先取出商品價格100元;小王也在操作,取出的商品價格也是100元。小李将價格加了50元,并将100+50=150元存入了資料庫;小王将商品減了30元,并将100-30=70元存入了資料庫。是的,如果沒有鎖,小李的操作就完全被小王的覆寫了

現在商品價格是70元,比成本價低10元。幾分鐘後,這個商品很快出售了1千多件商品,老闆虧1萬多

2、樂觀鎖與悲觀鎖

  • 上面的故事,如果是樂觀鎖,小王儲存價格前,會檢查下價格是否被人修改過了。如果被修改過了,則重新取出的被修改後的價格,150元,這樣他會将120元存入資料庫
  • 如果是悲觀鎖,小李取出資料後,小王隻能等小李操作完之後,才能對價格進行操作,也會保證最終的價格是120元

3、模拟上述場景

  • 資料庫中增加商品表
    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, 'RTX3080', 100);           
  • 編寫實體類
    @Data
    public class Product {
        private Long id;
        private String name;
        private Integer price;
        private Integer version;
    }           
  • 編寫ProductMapper
    @Repository
    public interface ProductMapper extends BaseMapper<Product> {
        
    }           
  • 測試
    @Autowired
        private ProductMapper productMapper;
    
        @Test
        public void testNoLockCurrentUpdate(){
            //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);
            productMapper.updateById(p1);
    
            //4、小王将商品減了30元,存入了資料庫
            p2.setPrice(p2.getPrice() - 30);
            int result = productMapper.updateById(p2);
            if(result == 0){//更新失敗,重試
                //重新擷取資料
                p2 = productMapper.selectById(1L);
                //更新
                p2.setPrice(p2.getPrice() - 30);
                productMapper.updateById(p2);
            }
    
            //5、最後的結果
            Product p3 = productMapper.selectById(1L);
            System.out.println("最後的結果:" + p3.getPrice());  //最後的結果:70
        }           

4、使用MybatisPlus的樂觀蘇解決問題

  • 資料庫中添加version字段
  • 修改實體類
    @Version
    private Integer version;           
  • 建立配置檔案
    • 建立config包,建立檔案MybatisPlusConfig.java
    • 此時可以删除主啟動類中的

      @MapperScan

      掃描注解
    @EnableTransactionManagement
    @Configuration
    @MapperScan("xxxx.xxxx.mapper")
    public class MybatisPlusConfig {
        
    }           
  • 編寫樂觀鎖插件
    /**
         * 樂觀鎖插件
         */
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor() {
            return new OptimisticLockerInterceptor();
        }           
  • @Autowired
        private ProductMapper productMapper;
    
        @Test
        public void testNoLockCurrentUpdate(){
            //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);
            productMapper.updateById(p1);
    
            //4、小王将商品減了30元,存入了資料庫
            p2.setPrice(p2.getPrice() - 30);
            int result = productMapper.updateById(p2);
            if(result == 0){//更新失敗,重試
                //重新擷取資料
                p2 = productMapper.selectById(1L);
                //更新
                p2.setPrice(p2.getPrice() - 30);
                productMapper.updateById(p2);
            }
    
            //5、最後的結果
            Product p3 = productMapper.selectById(1L);
            System.out.println("最後的結果:" + p3.getPrice());  //最後的結果:120
        }           
  • 總結:當我們使用了樂觀鎖後,如果出現了多個人同時修改同一資料的情況,那麼所有人在拿到資料的同時也擷取了樂觀鎖的版本号,當第一個人修改完畢後,樂觀鎖就會版本号+1,那麼當其他人送出資料時由于版本号不對應的關系,是以資料就會送出失敗

五、查詢資料和分頁插件

查詢資料

1、通過多個id批量查詢

@Test
    public void testSelectByBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        users.forEach(System.out::println);
    }           

2、通過map封裝完成簡單條件查詢

@Test
    public void testSelectByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", "張三");
        map.put("age", 18);
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }           
分頁插件

1、在MybatisPlusConfig添加元件

/**
 * 分頁插件
 */
@Bean
public PaginationInterceptor paginationInterceptor() {
    return new PaginationInterceptor();
}           

2、測試

@Test
    public void testSelectPage() {

        Page<User> page = new Page<>(1,5);
        Page<User> pageParam = userMapper.selectPage(page, null);

        pageParam.getRecords().forEach(System.out::println);
        System.out.println(pageParam.getCurrent());  // 擷取目前是第幾頁
        System.out.println(pageParam.getPages());    // 擷取一共的頁數
        System.out.println(pageParam.getSize());     // 擷取每一頁的個數
        System.out.println(pageParam.getTotal());    // 擷取總記錄條數
        System.out.println(pageParam.hasNext());     // 判斷是否還有下一頁
        System.out.println(pageParam.hasPrevious()); // 判斷是否還有前一頁
    }           

3、通過QueryWrapper完成條件查詢

@Test
    public void testSelectMapsPage() {
        Page<Map<String, Object>> page = new Page<>(1, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name", "age");   // 表示查詢結果隻顯示name和age列
        Page<Map<String, Object>> pageParam = userMapper.selectMapsPage(page, queryWrapper);

        List<Map<String, Object>> records = pageParam.getRecords();
        records.forEach(System.out::println);
    }           

六、删除資料與邏輯删除

删除資料

1、根據id删除資料

@Test
public void testDeleteById(){

    int result = userMapper.deleteById(5L);
    System.out.println(result);
}           

2、批量删除資料

@Test
public void testDeleteBatchIds() {

    int result = userMapper.deleteBatchIds(Arrays.asList(8, 9, 10));
    System.out.println(result);
}           

3、根據map封裝完成删除

@Test
public void testDeleteByMap() {

    HashMap<String, Object> map = new HashMap<>();
    map.put("name", "張三");
    map.put("age", 18);

    int result = userMapper.deleteByMap(map);
    System.out.println(result);
}           
邏輯删除

1、修改資料庫字段

ALTER TABLE `user` ADD COLUMN `deleted` tinyint DEFAULT false           

2、修改實體類

@TableLogic
private Integer deleted;           

3、測試

@Test
    public void testLogicDelete(){
        int result = userMapper.deleteById(1L);  //此時的删除隻是把deleted字段變成1
        System.out.println(result);
    }           

七、條件構造器和常用接口

案例一:

@Test
    public void testDelete() { //删除名字為空年齡大于12且郵箱不為空的使用者(這裡SQL為UPODATE因為邏輯删除原因)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("name").ge("age", 12).isNotNull("email");
        int result = userMapper.delete(queryWrapper); // UPDATE user SET deleted=1 WHERE deleted=0 AND (name IS NULL AND age >= ? AND email IS NOT NULL)
        System.out.println("delete return count = " + result);
    }           

案例二:

@Test
    public void testSelectObjs() { // 查詢id in (select id from user where id <= 3)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("id", "select id from user where id <= 3");

        List<Object> objects = userMapper.selectObjs(queryWrapper);  //傳回值是Object清單
        objects.forEach(System.out::println);
    }           

案例三:

@Test
public void testSelectCount() { // 查詢age在20-30之間的數量(包含20與30)

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.between("age", 20, 30);

    Integer count = userMapper.selectCount(queryWrapper);  //傳回資料數量
    System.out.println(count);
}           

案例四:

@Test
public void testSelectListOrderBy() { // 先按照age降序排列,如果age相同按照id降序排列

    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.orderByDesc("age", "id");

    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}           

Wrapper的常見查詢關鍵字: