MyBatisPlus入門學習

平遙發表於2021-05-03

MyBatisPlus

概述

官網:https://baomidou.com/

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

MyBatisPlus可以節省我們大量的工作時間,所有的CRUD可以自動化完成。

JPA,tk-mapper,MyBatisPlus。

特性:

  • 無侵入:只做增強不做改變,引入它不會對現有工程產生影響,如絲般順滑
  • 損耗小:啟動即會自動注入基本 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 操作智慧分析阻斷,也可自定義攔截規則,預防誤操作

快速入門

使用第三方外掛:

  1. 匯入對應的依賴
  2. 研究依賴如何配置
  3. 編寫程式碼,擴充

步驟

  1. 建立資料庫,mybaits_plus

  2. 建立user表

    DROP TABLE IF EXISTS 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)
    );
    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');
    
    --真實開發中,version(樂觀鎖),deleted(邏輯刪除),gmt_create,gmt_modified
    
  3. 初始化專案(使用SpringBoot初始化,匯入依賴)

    <!--資料庫驅動-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!--mybatis-plus-->
    <!--mybatis-plus是自己開發的,並非官方的-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.0.5</version>
    </dependency>
    

    mybatis-plus可以節省我們大量的程式碼,儘量不要同時匯入mybatis和mybatis-plus!避免版本的差異!

  4. 連線資料庫,和mybatis相同。

    application.properties

    # mysql5
    spring.datasource.username=root
    spring.datasource.password=123456
    spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false
    spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    
    #mysql8 驅動不同com.mysql.cj.jdbc.Driver(可相容mysql5),需要配置時區 serverTimezone=GMT%2B8
    
  5. 傳統方式:pojo-dao(連線mybatis,配置mapper.xml)-service-controller

  6. 使用mybatis-plus後,pojo-mapper介面-使用

    pojo

    package com.zr.pojo;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    

    mapper介面

    package com.zr.mapper;
    
    //在對應的mapper上實現基本的類 BaseMapper
    @Repository //代表持久層的
    public interface UserMapper extends BaseMapper<User> {
    
        //所有的CRUD操作已經編寫完成了
        //不需要配置其它檔案了
    
    }
    

    主啟動類

    package com.zr;
    
    //掃描mapper資料夾
    @MapperScan("com.zr.mapper")
    @SpringBootApplication
    public class MybatisPlusApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(MybatisPlusApplication.class, args);
        }
    
    }
    

    測試

    package com.zr;
    
    @SpringBootTest
    class MybatisPlusApplicationTests {
        //繼承了BaseMapper,所有的方法都來自於父類
        //我們也可以編寫自己的擴充方法
        @Autowired
        private UserMapper userMapper;
    
        @Test
        void contextLoads() {
            //引數是一個 Wrapper,條件構造器,這裡先不用 null
            //查詢全部使用者
            List<User> users = userMapper.selectList(null);
            users.forEach(System.out::println);
    
        }
    }
    

注意點:主啟動類上掃描mapper下的所有介面。

配置日誌輸出

我們所有的sql現在是不可見的,我們希望知道它是怎麼執行的,就必須看日誌。

application.properties中增加

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

日誌輸出結果:

CRUD擴充

插入

package com.zr;

@SpringBootTest
class MybatisPlusApplicationTests {
    //繼承了BaseMapper,所有的方法都來自於父類
    //我們也可以編寫自己的擴充方法
    @Autowired
    private UserMapper userMapper;

    //測試插入
    @Test
    public void testInsert(){

        User user = new User();
        user.setName("週週");
        user.setAge(20);
        user.setEmail("813794474@qq.com");

        userMapper.insert(user);  //自動生成ID
        System.out.println(user.toString());
    }

}

測試結果:

資料庫插入ID的預設值為:全域性唯一的ID。

主鍵生成策略

可查詢分散式系統唯一id生成解決方案。

雪花演算法:SnowFlake 演算法,是 Twitter 開源的分散式 id 生成演算法(long型的ID)。其核心思想就是:使用一個 64 bit 的 long 型的數字作為全域性唯一 id,其中41bit作為毫秒數,10bit作為機器的id(5bit是資料中心,5bit是機器id),12bit作為毫秒內的流水號(意味著每個節點在每秒可產生4096個ID),最後有一位符號位,永遠是0。在分散式系統中的應用十分廣泛,且ID 引入了時間戳,基本上保持自增的。

主鍵配置:

欄位上加 @TableId(type = IdType.AUTO)

資料庫欄位一定要是遞增。

package com.zr.pojo;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    //對應資料庫中的主鍵(uuid,自增id,雪花演算法,redis,zookeeper)
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

測試插入。

原始碼:

public enum IdType {
    AUTO(0), //資料庫id自增
    NONE(1),  //未設定主鍵
    INPUT(2),  //手動輸入
    ID_WORKER(3),  //預設的全域性唯一id
    UUID(4),  //全域性唯一id
    ID_WORKER_STR(5);  // ID_WORKER的字串表示法
}

更新操作

測試方法中新增:

//更新
@Test
public void testUpdate(){
    User user = new User();
    user.setId(22L);
    user.setName("週週zzzz");
    user.setAge(20);

    userMapper.updateById(user);
}

sql自動動態配置

自動填充

建立時間,修改時間!這些操作都是自動化完成的,我們不希望手動更新。

阿里巴巴開發手冊:所有的資料庫,gmt_creat,gmt-modified幾乎所有的表都要配置上,而且需要自動化。

方式一:資料庫級別(工作中不允許修改資料庫的,不建議使用)

  1. 在表中新增欄位,create_time,update_time(create_time不勾選根據當前時間更新)

  2. 再次測試插入方法,需要先把實體類同步!

    private Date createTime;
    private Date updateTime;
    
  3. 再次測試更新操作。

方式二:程式碼級別

  1. 刪除時間的預設值

  2. 實體類欄位屬性上需要增加註解

    //欄位插入填充內容
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
  3. 編寫處理器來處理註解

    package com.zr.handler;
    
    @Slf4j
    @Component  //加到IOC容器中
    public class MyMetaObjectHandler implements MetaObjectHandler {
        //插入時的填充策略
        @Override
        public void insertFill(MetaObject metaObject) {
            log.debug("start insert....");
            this.setFieldValByName("createTime",new Date(),metaObject);
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }
    
        //更新時的填充策略
        @Override
        public void updateFill(MetaObject metaObject) {
            log.debug("start update....");
            this.setFieldValByName("updateTime",new Date(),metaObject);
        }
    }
    
  4. 測試插入,觀察時間更新。

樂觀鎖

樂觀鎖:顧名思義非常樂觀,總是認為不會出現問題,無論幹什麼都不去上鎖,如果出現了問題,再次更新值測試。

悲觀鎖:顧名思義非常悲觀,總是認為會出現問題,無論幹什麼都加鎖,再去操作。

樂觀鎖實現方式:

  • 取出記錄時,獲取當前version
  • 更新時,帶上這個version
  • 執行更新時, set version = newVersion where version = oldVersion
  • 如果version不對,就更新失敗
樂觀鎖,先查詢,獲取版本號
---A
update user set name = "zhour",version=version+1
where id = 2 and version = 1

---B 執行緒先完成,這個時候versioon=2,A執行失敗
update user set name = "zhour",version=version+1
where id = 2 and version = 1

測試樂觀鎖MP外掛:

資料庫中增加version欄位

同步實體類

//樂觀鎖的註解
@Version
private Integer version;

註冊元件(主啟動類的掃描mapper放到這裡)MyBatisPlusConfig

package com.zr.config;

//掃描mapper資料夾
@MapperScan("com.zr.mapper")
@EnableTransactionManagement
@Configuration //代表是一個配置類
public class MyBatisPlusConfig {
    //註冊樂觀鎖外掛
    @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor(){
        return new OptimisticLockerInterceptor();
    }
}

測試:新增以下程式碼

//測試樂觀鎖成功
@Test
public void testOptimisticLocker(){
    //執行緒1
    //查詢使用者資訊
    User user = userMapper.selectById(222L);
    //修改使用者資訊
    user.setName("zhourrr");
    user.setEmail("813794474@qq.com");
    //執行更新操作
    userMapper.updateById(user);

}


//測試樂觀鎖失敗,多執行緒下
@Test
public void testOptimisticLocker2(){
    //執行緒1
    User user1 = userMapper.selectById(222L);
    user1.setName("zhourrr111");
    user1.setEmail("813794474@qq.com");

    //模擬另外一個執行緒執行了插隊操作
    User user2 = userMapper.selectById(222L);
    user2.setName("zhourrr222");
    user2.setEmail("813794474@qq.com");
    userMapper.updateById(user2);

    //自旋鎖來多次嘗試提交
    userMapper.updateById(user1); //如果沒有樂觀鎖就會覆蓋插隊執行緒的值
}

查詢操作

//測試批量查詢
@Test
public void testSelectById(){
    User user = userMapper.selectById(222L);
    System.out.println(user);
}

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

//按條件查詢 Map
@Test
public void testSelectByMap(){
    HashMap<String,Object> map = new HashMap();
    //自定義查詢
    map.put("name","週週zzzz");

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

分頁查詢

分頁在網站的使用非常多。

  1. 原始用limit分頁
  2. pageHelper 第三方外掛
  3. MP內建了分頁外掛

使用:

  1. 配置攔截器組即可(官網示例)

    MyBatisPlusConfig中新增

     //分頁外掛
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            return new PaginationInterceptor;
        }
    
  2. 直接使用page物件即可

    //測試分頁查詢
    @Test
    public void testPage(){
        //引數1:當前頁
        //引數2:頁面的大小
        Page<User> page = new Page<>(1,5);
        userMapper.selectPage(page,null);
        page.getRecords().forEach(System.out::println);
        System.out.println(page.getTotal());
    }
    

刪除操作

根據id刪除

//測試刪除
@Test
public void testDeleteById(){
    userMapper.deleteById(1342445919922073602L);
}
//通過id批量刪除
@Test
public void testDeleteBatchByIds(){
    userMapper.deleteBatchIds(Arrays.asList(1342445919922073602L,222));
}
//通過Map刪除
@Test
public void testDeleteByMap(){
    HashMap<String, Object> map = new HashMap<>();
    map.put("name","zhourrr222");
    userMapper.deleteByMap(map);
}

邏輯刪除

物理刪除:從資料庫中直接移除

邏輯刪除:在資料庫中沒有被移除,而是通過一個變數讓它失效

應用:管理員可以檢視被刪除的內容,防止資料的丟失,類似於回收站!

測試:

  1. 在資料庫中增加一個deleted欄位

  2. 實體類中增加屬性

    @TableLogic  //邏輯刪除註解
    private Integer deleted;
    
  3. 配置 MyBatisPlusConfig

    //邏輯刪除元件
    @Bean
    public ISqlInjector sqlInjector(){
        return new LogicSqlInjector();
    }
    

    application.properties中新增

    #配置邏輯刪除
    # 邏輯已刪除值(預設為 1)
    # 邏輯未刪除值(預設為 0)
    mybatis-plus.global-config.db-config.logic-delete-value=1
    mybatis-plus.global-config.db-config.logic-not-delete-value=0
    
  4. 測試

觀察資料庫中id為111的使用者deleted的值變為1.

再次查詢id為111的使用者,顯示為空(只會查詢deleted值為0的使用者)。

效能分析外掛

在開發中,我們會遇到一些慢sql。測試,durid....

MP也提供了效能分析外掛,如果超過這個時間就停止執行。

  1. 匯入外掛

    //sql執行效率外掛
    @Bean
    @Profile({"dev","test"})  //設定dev,test環境開啟
    public PerformanceInterceptor performanceInterceptor(){
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        performanceInterceptor.setMaxTime(100); //設定sql能夠執行的最大時間
        performanceInterceptor.setFormat(true); //是否格式化sql語句
        return performanceInterceptor;
    }
    

    在application.properties中配置環境為測試或者開發環境。

    #設定開發環境
    spring.profiles.active=dev
    
  2. 測試使用

    @Test
    void contextLoads() {
        //引數是一個 Wrapper,條件構造器,這裡先不用 null
        //查詢全部使用者
        List<User> users = userMapper.selectList(null);
        users.forEach(System.out::println);
    
    }
    

sql執行的時間和格式化的sql

執行的時間超過了設定的時間就會丟擲異常。

使用效能分析外掛可以幫助我們提高效率!

條件構造器

AbstractWrapper.

我們寫一些複雜的sql就可以使用它來替代。

新建一個測試類 WrapperTest。

以下的測試結合日誌的 SQL 語句來分析。

測試一:查詢name不為空的使用者,並且郵箱也不為空,且年齡大於12的使用者

package com.zr;

@SpringBootTest
public class WrapperTest {

    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads() {
        //查詢name不為空的使用者,並且郵箱也不為空,且年齡大於12的使用者
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .isNotNull("name")
                .isNotNull("email")
                .ge("age",12);
        userMapper.selectList(wrapper).forEach(System.out::println);
    }
}

測試二:查詢名字為週週zzzz的使用者

@Test
void test2(){
    //查詢名字為週週zzzz的使用者
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("name","週週zzzz");
    User user = userMapper.selectOne(wrapper); //查詢一個資料,查詢多個用list或者map
    System.out.println(user);
}

測試三:查詢年齡在10-20之間的使用者

@Test
void test3(){
    //查詢年齡在10-20之間的使用者
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.between("age",10,20);
    userMapper.selectCount(wrapper);  //查詢結果數
}

測試四:查詢名字中沒有字母e,且郵箱是以t開頭的(t%)

@Test
void test4(){
    //模糊查詢
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper
            .notLike("name","e")
            .likeRight("email","t");
    List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
    maps.forEach(System.out::println);
}

測試五:子查詢

@Test
void test5(){
    // id 在子查詢中查出來
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.inSql("id","select id from user where id < 3");
    List<Object> objects = userMapper.selectObjs(wrapper);
    objects.forEach(System.out::println);
}

測試六:通過 id 降序排序

@Test
void test6(){
    // 通過 id 降序排序
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.orderByDesc("id");
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

其它更多測試參考mybatis-plus官方文件。

程式碼自動生成器

AutoGenerator 是 MyBatis-Plus 的程式碼生成器,通過 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各個模組的程式碼,極大的提升了開發效率。

package com.zr;

public class ZrCode {
    public static void main(String[] args) {
        //程式碼自動生成
        AutoGenerator mpg = new AutoGenerator();

        //全域性配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath+"/src/main/java");
        gc.setAuthor("zhour");
        gc.setOpen(false);
        gc.setFileOverride(false); //是否覆蓋
        gc.setServiceName("%sService");  //去Service的 i 字首
        gc.setIdType(IdType.ID_WORKER);
        gc.setDateType(DateType.ONLY_DATE);
        gc.setSwagger2(true);

        mpg.setGlobalConfig(gc);

        //資料來源配置
        DataSourceConfig ds = new DataSourceConfig();
        ds.setUrl("jdbc:mysql://localhost:3306/mybaits_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT");
        ds.setDriverName("com.mysql.cj.jdbc.Driver");
        ds.setUsername("root");
        ds.setPassword("123456");
        ds.setDbType(DbType.MYSQL);

        mpg.setDataSource(ds);

        //包的配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("blog");
        pc.setParent("com.zrcode");
        pc.setEntity("entity");
        pc.setMapper("mapper");
        pc.setController("controller");
        pc.setService("service");

        mpg.setPackageInfo(pc);

        //策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setInclude("user");  //設定要對映的表名
        strategy.setNaming(NamingStrategy.underline_to_camel); //轉駝峰命名
        strategy.setColumnNaming(NamingStrategy.underline_to_camel); //轉駝峰命名
        strategy.setEntityLombokModel(true);
        strategy.setLogicDeleteFieldName("deleted");  //邏輯刪除

        //自動填充
        TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
        TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> list = new ArrayList<>();
        list.add(createTime);
        list.add(updateTime);
        strategy.setTableFillList(list);

        //樂觀鎖
        strategy.setVersionFieldName("version");
        strategy.setRestControllerStyle(true);
        strategy.setControllerMappingHyphenStyle(true);  //localhost:8080/hello_id_3,下劃線命名

        mpg.setStrategy(strategy);

        mpg.execute();
    }
}

本文專案結構目錄: