MyBatis-Plus基礎

一、MyBatis-Plus

1.1 簡介

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

我們的願景是成爲 MyBatis 最好的搭檔,就像 魂鬥羅 中的 1P、2P,基友搭配,效率翻倍。

img

1.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 操作智能分析阻斷,也可自定義攔截規則,預防誤操作

1.3 支持數據庫

任何能使用 MyBatis 進行 CRUD, 並且支持標準 SQL 的數據庫,具體支持情況如下,如果不在下列表查看分頁部分教程 PR 您的支持。

  • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • 達夢數據庫,虛谷數據庫,人大金倉數據庫,南大通用(華庫)數據庫,南大通用數據庫,神通數據庫,瀚高數據庫

1.4 框架結構

framework

1.5 官方地址

官方網站:https://baomidou.com/

官方文檔:https://baomidou.com/pages/24112f/

二、入門案例

2.1 開發環境

  • IDE:IDEA 2019.3.5
  • JDK:JDK8+
  • 構建工具:Maven 3.5.4
  • MySQL:MySQL 8.0.24
  • Navicat:Navicat Premium 15
  • Spring Boot:2.6.7
  • MyBatis-Plus:3.5.1

2.2 建庫建表

  • 打開Navicat運行以下SQL腳本進行建庫建表

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

2.3 創建工程

  • 使用Spring Initializer快速初始化一個 Spring Boot 工程

    image-20220519140839640 image-20220519141335981 image-20220519141737405 image-20220519141849937
  • 引入MyBatis-Plus的依賴

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
  • 安裝Lombok插件

    image-20220519143257305

2.4 配置編碼

  • 配置application.yml文件

    #配置端口
    server:
      port: 80
    
    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: {username}
        password: {password}
    
    #MyBatis-Plus相關配置
    mybatis-plus:
      configuration:
        #配置日誌
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
  • 在 Spring Boot 啓動類中添加 @MapperScan 註解,掃描 Mapper 文件夾

    @SpringBootApplication
    @MapperScan("指定Mapper接口所在的包")
    public class MybatisPlusDemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(MybatisPlusDemoApplication.class, args);
    	}
    }
    
  • 編寫實體類 User.java(此處使用了 Lombok 簡化代碼)

    @Data
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    
  • 編寫 Mapper 包下的 UserMapper接口

    public interface UserMapper extends BaseMapper<User> {}
    

2.5 測試查詢

  • 編寫一個測試類MyBatisPlusTest.java

    @SpringBootTest
    public class MyBatisPlusTest {
        @Resource
        private UserMapper userMapper;
    
        /**
         * 測試查詢所有數據
         */
        @Test
        void testSelectList(){
            //通過條件構造器查詢一個list集合,若沒有條件,則可以設置null爲參數
            List<User> users = userMapper.selectList(null);
            users.forEach(System.out::println);
        }
    }
    
  • 控制檯打印查詢結果

    image-20220519150454211

三、增刪改查

3.1 BaseMapper<T>

說明:

  • Mapper 繼承該接口後,無需編寫 mapper.xml 文件,即可獲得CRUD功能
  • 通用 CRUD 封裝BaseMapper 接口,爲 Mybatis-Plus 啓動時自動解析實體表關係映射轉換爲 Mybatis 內部對象注入容器
  • 泛型 T 爲任意實體對象
  • 參數 Serializable 爲任意類型主鍵 Mybatis-Plus 不推薦使用複合主鍵約定每一張表都有自己的唯一 id 主鍵
  • 對象 Wrapper 爲條件構造器

MyBatis-Plus中的基本CRUD在內置的BaseMapper中都已得到了實現,因此我們繼承該接口以後可以直接使用。

本次演示的CRUD操作不包含參數帶有條件構造器的方法,關於條件構造器將單獨在一個章節進行演示。


BaseMapper中提供的CRUD方法:

  • 增加:Insert

    // 插入一條記錄
    int insert(T entity);
    
  • 刪除:Delete

    // 根據 entity 條件,刪除記錄
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
    // 刪除(根據ID 批量刪除)
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根據 ID 刪除
    int deleteById(Serializable id);
    // 根據 columnMap 條件,刪除記錄
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
  • 修改:Update

    // 根據 whereWrapper 條件,更新記錄
    int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
    // 根據 ID 修改
    int updateById(@Param(Constants.ENTITY) T entity);
    
  • 查詢:Selete

    // 根據 ID 查詢
    T selectById(Serializable id);
    // 根據 entity 條件,查詢一條記錄
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 查詢(根據ID 批量查詢)
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根據 entity 條件,查詢全部記錄
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 查詢(根據 columnMap 條件)
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    // 根據 Wrapper 條件,查詢全部記錄
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據 Wrapper 條件,查詢全部記錄。注意: 只返回第一個字段的值
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根據 entity 條件,查詢全部記錄(並翻頁)
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據 Wrapper 條件,查詢全部記錄(並翻頁)
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據 Wrapper 條件,查詢總記錄數
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    

3.2 調用Mapper層實現CRUD

3.2.1 插入


最終執行的結果,所獲取的id爲1527206783590903810

這是因爲MyBatis-Plus在實現插入數據時,會默認基於雪花算法的策略生成id

/**
  * 測試插入一條數據
  * MyBatis-Plus在實現插入數據時,會默認基於雪花算法的策略生成id
  */
@Test
public void testInsert(){
    User user = new User();
    user.setName("Vz");
    user.setAge(21);
    user.setEmail("[email protected]");
    int result = userMapper.insert(user);
    System.out.println(result > 0 ? "添加成功!" : "添加失敗!");
    System.out.println("受影響的行數爲:" + result);
    //1527206783590903810(當前 id 爲雪花算法自動生成的id)
    System.out.println("id自動獲取" + user.getId());
}

3.2.2 刪除


a、根據ID刪除數據

調用方法:int deleteById(Serializable id);

/**
  * 測試根據id刪除一條數據
  */
@Test
public void testDeleteById(){
    int result = userMapper.deleteById(1527206783590903810L);
    System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
    System.out.println("受影響的行數爲:" + result);
}

b、根據ID批量刪除數據

調用方法:int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

/**
  * 測試通過id批量刪除數據
  */
@Test
public void testDeleteBatchIds(){
    List<Long> ids = Arrays.asList(6L,7L,8L);
    int result = userMapper.deleteBatchIds(ids);
    System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
    System.out.println("受影響的行數爲:" + result);
}

c、根據Map條件刪除數據

調用方法:int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

/**
   * 測試根據Map集合中所設置的條件刪除數據
   */
@Test
public void testDeleteByMap(){
    //當前演示爲根據name和age刪除數據
    //執行SQL爲:DELETE FROM user WHERE name = ? AND age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("name","Vz");
    map.put("age",21);
    int result = userMapper.deleteByMap(map);
    System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
    System.out.println("受影響的行數爲:" + result);
}

2.3 修改

調用方法:int updateById(@Param(Constants.ENTITY) T entity);

/**
  * 測試根據id修改用戶信息
  */
@Test
public void testUpdateById(){
    //執行SQL爲: UPDATE user SET name=?, age=?, email=? WHERE id=?
    User user = new User();
    user.setId(6L);
    user.setName("VzUpdate");
    user.setAge(18);
    user.setEmail("[email protected]");
    int result = userMapper.updateById(user);
    System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
    System.out.println("受影響的行數爲:" + result);
}

2.4 查詢


a、根據ID查詢用戶信息

調用方法:T selectById(Serializable id);

/**
  * 測試根據id查詢用戶數據
  */
@Test
public void testSelectById(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

b、根據多個ID查詢多個用戶信息

調用方法:List selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

/**
  * 根據多個id查詢用戶數據
  */
@Test
public void testSelectBatchIds(){
    //執行SQL爲:SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
    List<Long> ids = Arrays.asList(1L,2L,3L);
    List<User> users = userMapper.selectBatchIds(ids);
    users.forEach(System.out::println);
}

c、根據Map條件查詢用戶信息

調用方法:List selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

/**
  * 根據Map所設置的條件查詢用戶
  */
@Test
public void testSelectByMap(){
    //執行SQL爲:SELECT id,name,age,email FROM user WHERE age = ?
    Map<String,Object> map = new HashMap<>();
    map.put("age",18);
    List<User> users = userMapper.selectByMap(map);
    users.forEach(System.out::println);
}

d、查詢所有用戶信息

調用方法:List selectList(@Param(Constants.WRAPPER) Wrapper queryWrapper);

/**
  * 測試查詢所有數據
  */
@Test
void testSelectList(){
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

3.3 通用Service

說明:

  • 通用 Service CRUD 封裝IService接口,進一步封裝 CRUD 採用 get 查詢單行 remove 刪除 list 查詢集合 page 分頁 前綴命名方式區分 Mapper 層避免混淆,
  • 泛型 T 爲任意實體對象
  • 建議如果存在自定義通用 Service 方法的可能,請創建自己的 IBaseService 繼承 Mybatis-Plus 提供的基類
  • 對象 Wrapper 爲 條件構造器

MyBatis-Plus中有一個接口 IService和其實現類 ServiceImpl,封裝了常見的業務層邏輯,詳情查看源碼IService和ServiceImpl

因此我們在使用的時候僅需在自己定義的Service接口中繼承IService接口,在自己的實現類中實現自己的Service並繼承ServiceImpl即可


IService中的CRUD方法

  • 增加:Save、SaveOrUpdate

    // 插入一條記錄(選擇字段,策略插入)
    boolean save(T entity);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList, int batchSize);
    
    // TableId 註解存在更新記錄,否插入一條記錄
    boolean saveOrUpdate(T entity);
    // 根據updateWrapper嘗試更新,否繼續執行saveOrUpdate(T)方法
    boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
    
  • 刪除:Remove

    // 根據 entity 條件,刪除記錄
    boolean remove(Wrapper<T> queryWrapper);
    // 根據 ID 刪除
    boolean removeById(Serializable id);
    // 根據 columnMap 條件,刪除記錄
    boolean removeByMap(Map<String, Object> columnMap);
    // 刪除(根據ID 批量刪除)
    boolean removeByIds(Collection<? extends Serializable> idList);
    
  • 修改:Update

    // 根據 UpdateWrapper 條件,更新記錄 需要設置sqlset
    boolean update(Wrapper<T> updateWrapper);
    // 根據 whereWrapper 條件,更新記錄
    boolean update(T updateEntity, Wrapper<T> whereWrapper);
    // 根據 ID 選擇修改
    boolean updateById(T entity);
    // 根據ID 批量更新
    boolean updateBatchById(Collection<T> entityList);
    // 根據ID 批量更新
    boolean updateBatchById(Collection<T> entityList, int batchSize);
    
  • 查詢:Get、List、Count

    // 根據 ID 查詢
    T getById(Serializable id);
    // 根據 Wrapper,查詢一條記錄。結果集,如果是多個會拋出異常,隨機取一條加上限制條件 wrapper.last("LIMIT 1")
    T getOne(Wrapper<T> queryWrapper);
    // 根據 Wrapper,查詢一條記錄
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);
    // 根據 Wrapper,查詢一條記錄
    Map<String, Object> getMap(Wrapper<T> queryWrapper);
    // 根據 Wrapper,查詢一條記錄
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
    
    // 查詢所有
    List<T> list();
    // 查詢列表
    List<T> list(Wrapper<T> queryWrapper);
    // 查詢(根據ID 批量查詢)
    Collection<T> listByIds(Collection<? extends Serializable> idList);
    // 查詢(根據 columnMap 條件)
    Collection<T> listByMap(Map<String, Object> columnMap);
    // 查詢所有列表
    List<Map<String, Object>> listMaps();
    // 查詢列表
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
    // 查詢全部記錄
    List<Object> listObjs();
    // 查詢全部記錄
    <V> List<V> listObjs(Function<? super Object, V> mapper);
    // 根據 Wrapper 條件,查詢全部記錄
    List<Object> listObjs(Wrapper<T> queryWrapper);
    // 根據 Wrapper 條件,查詢全部記錄
    <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    
    // 查詢總記錄數
    int count();
    // 根據 Wrapper 條件,查詢總記錄數
    int count(Wrapper<T> queryWrapper);
    
  • 分頁:Page

    // 根據 ID 查詢
    T getById(Serializable id);
    // 根據 Wrapper,查詢一條記錄。結果集,如果是多個會拋出異常,隨機取一條加上限制條件 wrapper.last("LIMIT 1")
    T getOne(Wrapper<T> queryWrapper);
    // 根據 Wrapper,查詢一條記錄
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);
    // 根據 Wrapper,查詢一條記錄
    Map<String, Object> getMap(Wrapper<T> queryWrapper);
    // 根據 Wrapper,查詢一條記錄
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    

3.4 調用Service層操作數據

我們在自己的Service接口中通過繼承MyBatis-Plus提供的IService接口,不僅可以獲得其提供的CRUD方法,而且還可以使用自身定義的方法。

  • 創建UserService並繼承IService

    /**
      * UserService繼承IService模板提供的基礎功能 
      */
    public interface UserService extends IService<User> {}
    
  • 創建UserService的實現類並繼承ServiceImpl

    /**
      * ServiceImpl實現了IService,提供了IService中基礎功能的實現 
      * 若ServiceImpl無法滿足業務需求,則可以使用自定的UserService定義方法,並在實現類中實現
      */
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
    
  • 測試查詢記錄數

    調用方法:int count();

    @Test
    public void testGetCount(){
        //查詢總記錄數
        //執行的SQL爲:SELECT COUNT( * ) FROM user
        long count = userService.count();
        System.out.println("總記錄數:" + count);
    }
    
  • 測試批量插入數據

    調用方法:boolean saveBatch(Collection entityList);

    @Test
    public void test(){
        List<User> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++) {
            User user = new User();
            user.setName("Vz"+i);
            user.setAge(20+i);
            list.add(user);
        }
        boolean b = userService.saveBatch(list);
        System.out.println(b ? "添加成功!" : "添加失敗!");
    }
    

四、常用註解

MyBatis-Plus提供的註解可以幫我們解決一些數據庫與實體之間相互映射的問題。

4.1 @TableName

經過以上的測試,在使用MyBatis-Plus實現基本的CRUD時,我們並沒有指定要操作的表,只是在Mapper接口繼承BaseMapper時,設置了泛型User,而操作的表爲user表,由此得出結論,MyBatis-Plus在確定操作的表時,由BaseMapper的泛型決定,即實體類型決定,且默認操作的表名和實體類型的類名一致。

4.1.1 引出問題


若實體類類型的類名和要操作的表的表名不一致,會出現什麼問題?

  • 我們將表user更名爲t_user,測試查詢功能

    image-20220520093844842

  • 程序拋出異常,Table 'mybatis_plus.user' doesn't exist,因爲現在的表名爲t_user,而默認操作的表名和實體類型的類名一致,即user

    image-20220520094126411

4.1.2 解決問題


a、使用註解解決問題

在實體類類型上添加@TableName("t_user"),標識實體類對應的表,即可成功執行SQL語句

@Data
@TableName("t_user")
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

b、使用全局配置解決問題

在開發的過程中,我們經常遇到以上的問題,即實體類所對應的表都有固定的前綴,例如 t_tbl_ 此時,可以使用MyBatis-Plus提供的全局配置,爲實體類所對應的表名設置默認的前綴,那麼就不需要在每個實體類上通過@TableName標識實體類對應的表

mybatis-plus:
  global-config:
    db-config:
      # 設置實體類所對應的表的統一前綴
      table-prefix: t_

4.2 @TableId

經過以上的測試,MyBatis-Plus在實現CRUD時,會默認將id作爲主鍵列,並在插入數據時,默認基於雪花算法的策略生成id

4.2.1 引出問題


若實體類和表中表示主鍵的不是id,而是其他字段,例如uid,MyBatis-Plus會自動識別uid爲主鍵列嗎?

  • 我們實體類中的屬性id改爲uid,將表中的字段id也改爲uid,測試添加功能

    image-20220520100939157

    image-20220520100715109

  • 程序拋出異常,Field 'uid' doesn't have a default value,說明MyBatis-Plus沒有將uid作爲主鍵賦值

    image-20220520101317761

4.2.2 解決問題


在實體類中uid屬性上通過@TableId將其標識爲主鍵,即可成功執行SQL語句

@Date
public class User {
    @TableId
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

4.2.3 @TableId的value屬性


若實體類中主鍵對應的屬性爲id,而表中表示主鍵的字段爲uid,此時若只在屬性id上添加註解@TableId,則拋出異常Unknown column 'id' in 'field list',即MyBatis-Plus仍然會將id作爲表的主鍵操作,而表中表示主鍵的是字段uid此時需要通過@TableId註解的value屬性,指定表中的主鍵字段,@TableId("uid")@TableId(value="uid")

image-20220520103030977

4.2.4 @TableId的type屬性


type屬性用來定義主鍵策略:默認雪花算法

常用的主鍵策略:

描述
IdType.ASSIGN_ID(默認) 基於雪花算法的策略生成數據id,與數據庫id是否設置自增無關
IdType.AUTO 使用數據庫的自增策略,注意,該類型請確保數據庫設置了id自增,
@TableId(value = "uid", type = IdType.AUTO)
...

配置全局主鍵策略:

#MyBatis-Plus相關配置
mybatis-plus:
  configuration:
    #配置日誌
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      #配置mp的主鍵策略爲自增
      id-type: auto
      # 設置實體類所對應的表的統一前綴
      table-prefix: t_

雪花算法

背景

需要選擇合適的方案去應對數據規模的增長,以應對逐漸增長的訪問壓力和數據量。

數據庫的擴展方式主要包括:業務分庫、主從複製,數據庫分表。

數據庫分表

將不同業務數據分散存儲到不同的數據庫服務器,能夠支撐百萬甚至千萬用戶規模的業務,但如果業務繼續發展,同一業務的單表數據也會達到單臺數據庫服務器的處理瓶頸。例如,淘寶的幾億用戶數據,如果全部存放在一臺數據庫服務器的一張表中,肯定是無法滿足性能要求的,此時就需要對單表數據進行拆分。

單表數據拆分有兩種方式:垂直分表和水平分表。示意圖如下:

image-20221226174209950

垂直分表

垂直分表適合將表中某些不常用且佔了大量空間的列拆分出去。

例如,前面示意圖中的 nickname 和 description 字段,假設我們是一個婚戀網站,用戶在篩選其他用戶的時候,主要是用 age 和 sex 兩個字段進行查詢,而 nickname 和 description 兩個字段主要用於展示,一般不會在業務查詢中用到。description 本身又比較長,因此我們可以將這兩個字段獨立到另外一張表中,這樣在查詢 age 和 sex 時,就能帶來一定的性能提升。

水平分表

水平分表適合錶行數特別大的表,有的公司要求單錶行數超過 5000 萬就必須進行分表,這個數字可以作爲參考,但並不是絕對標準,關鍵還是要看錶的訪問性能。對於一些比較複雜的表,可能超過 1000萬就要分表了;而對於一些簡單的表,即使存儲數據超過 1 億行,也可以不分表。

但不管怎樣,當看到表的數據量達到千萬級別時,作爲架構師就要警覺起來,因爲這很可能是架構的性能瓶頸或者隱患。

水平分表相比垂直分表,會引入更多的複雜性,例如要求全局唯一的數據id該如何處理

主鍵自增

①以最常見的用戶 ID 爲例,可以按照 1000000 的範圍大小進行分段,1 ~ 999999 放到表 1中,

1000000 ~ 1999999 放到表2中,以此類推。

②複雜點:分段大小的選取。分段太小會導致切分後子表數量過多,增加維護複雜度;分段太大可能會導致單表依然存在性能問題,一般建議分段大小在 100 萬至 2000 萬之間,具體需要根據業務選取合適的分段大小。

③優點:可以隨着數據的增加平滑地擴充新的表。例如,現在的用戶是 100 萬,如果增加到 1000 萬,只需要增加新的表就可以了,原有的數據不需要動。

④缺點:分佈不均勻。假如按照 1000 萬來進行分表,有可能某個分段實際存儲的數據量只有 1 條,而另外一個分段實際存儲的數據量有 1000 萬條。

取模

①同樣以用戶 ID 爲例,假如我們一開始就規劃了 10 個數據庫表,可以簡單地用 user_id % 10 的值來表示數據所屬的數據庫表編號,ID 爲 985 的用戶放到編號爲 5 的子表中,ID 爲 10086 的用戶放到編號爲 6 的子表中。

②複雜點:初始表數量的確定。表數量太多維護比較麻煩,表數量太少又可能導致單表性能存在問題。

③優點:表分佈比較均勻。

④缺點:擴充新的表很麻煩,所有數據都要重分佈。

雪花算法

https://www.bilibili.com/video/BV19k4y1B7aJ/

雪花算法是由Twitter公佈的分佈式主鍵生成算法,它能夠保證不同表的主鍵的不重複性,以及相同表的主鍵的有序性

①核心思想:

長度共64bit(一個long型)。

首先是一個符號位,1bit標識,由於long基本類型在Java中是帶符號的,最高位是符號位,正數是0,負數是1,所以id一般是正數,最高位是0。

41bit時間截(毫秒級),存儲的是時間截的差值(當前時間截 - 開始時間截),結果約等於69.73年。

10bit作爲機器的ID(5個bit是數據中心,5個bit的機器ID,可以部署在1024個節點)。

12bit作爲毫秒內的流水號(意味着每個節點在每毫秒可以產生 4096 個 ID)。

②優點:整體上按照時間自增排序,並且整個分佈式系統內不會產生ID碰撞,並且效率較高。

4.3 @TbaleField

經過以上的測試,我們可以發現,MyBatis-Plus在執行SQL語句時,要保證實體類中的屬性名和表中的字段名一致

如果實體類中的屬性名和字段名不一致的情況,會出現什麼問題呢?

4.3.1 情況一


若實體類中的屬性使用的是駝峯命名風格,而表中的字段使用的是下劃線命名風格

例如實體類屬性userName,表中字段user_name

此時MyBatis-Plus會自動將下劃線命名風格轉化爲駝峯命名風格

相當於在MyBatis中配置

4.3.2 情況二


若實體類中的屬性和表中的字段不滿足情況1

例如實體類屬性name,表中字段username

此時需要在實體類屬性上使用@TableField("username")設置屬性所對應的字段名

public class User {
    @TableId("uid")
    private Long id;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}

4.4 @TableLogic

4.4.1 邏輯刪除


物理刪除:真實刪除,將對應數據從數據庫中刪除,之後查詢不到此條被刪除的數據

邏輯刪除:假刪除,將對應數據中代表是否被刪除字段的狀態修改爲“被刪除狀態”,之後在數據庫中仍舊能看到此條數據記錄

使用場景:可以進行數據恢復

4.4.2 實現邏輯刪除


  • 數據庫中創建邏輯刪除狀態列,設置默認值爲0

    image-20220520134529809

  • 實體類中添加邏輯刪除屬性

    image-20220520134636112

  • 測試刪除功能,真正執行的是修改

    public void testDeleteById(){
        int result = userMapper.deleteById(1527472864163348482L);
        System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
        System.out.println("受影響的行數爲:" + result);
    }
    

    image-20220520135637388

  • 此時執行查詢方法,查詢的結果爲自動添加條件is_deleted=0

    image-20220520140036445

五、條件構造器

5.1 Wrapper介紹

image-20220521092812125

  • Wrapper : 條件構造抽象類,最頂端父類

    • AbstractWrapper : 用於查詢條件封裝,生成 sql 的 where 條件

      • QueryWrapper : 查詢條件封裝

      • UpdateWrapper : Update 條件封裝

      • AbstractLambdaWrapper : 使用Lambda 語法

        • LambdaQueryWrapper :用於Lambda語法使用的查詢Wrapper

        • LambdaUpdateWrapper : Lambda 更新封裝Wrapper

5.2 QueryWrapper

  • 組裝查詢條件

    執行SQL:SELECT uid AS 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)

    public void test01(){
        //查詢用戶名包含a,年齡在20到30之間,郵箱信息不爲null的用戶信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("username","a")
            .between("age",20,30)
            .isNotNull("email");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }
    
  • 組裝排序條件

    執行SQL:SELECT uid AS id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC

    public void test02(){
        //查詢用戶信息,按照年齡的降序排序,若年齡相同,則按照id升序排序
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("age")
            .orderByAsc("id");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }
    
  • 組裝刪除條件

    執行SQL:UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)

    public void test03(){
        //刪除郵箱地址爲null的用戶信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
        int result = userMapper.delete(queryWrapper);
        System.out.println(result > 0 ? "刪除成功!" : "刪除失敗!");
        System.out.println("受影響的行數爲:" + result);
    }
    
  • 條件的優先級

    執行SQL:UPDATE t_user SET user_name=?, email=? WHERE is_deleted=0 AND (age > ? AND user_name LIKE ? OR email IS NULL)

    public void test04(){
        //將(年齡大於20並且用戶名中包含有a)或郵箱爲null的用戶信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.gt("age",20)
            .like("username","a")
            .or()
            .isNull("email");
        User user = new User();
        user.setName("Oz");
        user.setEmail("[email protected]");
    
        int result = userMapper.update(user, updateWrapper);
        System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
        System.out.println("受影響的行數爲:" + result);
    }
    

    執行SQL:UPDATE t_user SET username=?, email=? WHERE is_deleted=0 AND (username LIKE ? AND (age > ? OR email IS NULL))

    public void test05(){
        //將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("username","a").and(i->i.gt("age",20).or().isNull("email"));
        User user = new User();
        user.setName("Vz7797");
        user.setEmail("[email protected]");
    
        int result = userMapper.update(user, updateWrapper);
        System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
        System.out.println("受影響的行數爲:" + result);
    }
    
  • 組裝select子句

    執行SQL:SELECT username,age,email FROM t_user WHERE is_deleted=0

    public void test06(){
        //查詢用戶的用戶名、年齡、郵箱信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("username","age","email");
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }
    
  • 實現子查詢

    執行SQL:SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 100))

    public void test07(){
        //查詢id小於等於100的用戶信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("uid", "select uid from t_user where uid <= 100");
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }
    

5.3 UpdateWrapper

UpdateWrapper不僅擁有QueryWrapper的組裝條件功能,還提供了set方法進行修改對應條件的數據庫信息

public void test08(){
    //將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改
    UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
    updateWrapper.like("username","a").and( i -> i.gt("age",20).or().isNull("email")).set("email","[email protected]");
    int result = userMapper.update(null, updateWrapper);
    System.out.println(result > 0 ? "修改成功!" : "修改失敗!");
    System.out.println("受影響的行數爲:" + result);
}

5.4 condition

在真正開發的過程中,組裝條件是常見的功能,而這些條件數據來源於用戶輸入,是可選的,因此我們在組裝這些條件時,必須先判斷用戶是否選擇了這些條件,若選擇則需要組裝該條件,若沒有選擇則一定不能組裝,以免影響SQL執行的結果

  • 思路一

    執行SQL:SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age <= ?)

     public void test09(){
         String username = "a";
         Integer ageBegin = null;
         Integer ageEnd = 30;
         QueryWrapper<User> queryWrapper = new QueryWrapper<>();
         if(StringUtils.isNotBlank(username)){
             //isNotBlank判斷某個字符創是否不爲空字符串、不爲null、不爲空白符
             queryWrapper.like("user_name", username);
         }
         if(ageBegin != null){
             queryWrapper.ge("age", ageBegin);
         }
         if(ageEnd != null){
             queryWrapper.le("age", ageEnd);
         }
         List<User> list = userMapper.selectList(queryWrapper);
         list.forEach(System.out::println);
     }
    
  • 思路二

    上面的實現方案沒有問題,但是代碼比較複雜,我們可以使用帶condition參數的重載方法構建查詢條件,簡化代碼的編寫

    public void test10(){
        String username = "a";
        Integer ageBegin = null;
        Integer ageEnd = 30;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), "user_name", username)
            .ge(ageBegin != null, "age", ageBegin)
            .le(ageEnd != null, "age", ageEnd);
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }
    

5.5 LambdaQueryWrapper

功能等同於QueryWrapper,提供了Lambda表達式的語法可以避免填錯列名。

public void test11(){
    String username = "a";
    Integer ageBegin = null;
    Integer ageEnd = 30;
    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> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

5.6 LambdaUpdateWrapper

功能等同於UpdateWrapper,提供了Lambda表達式的語法可以避免填錯列名。

public void test12(){
    //將用戶名中包含有a並且(年齡大於20或郵箱爲null)的用戶信息修改
    LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
    updateWrapper.like(User::getName, "a")
        .and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));
    updateWrapper.set(User::getName, "小黑").set(User::getEmail,"[email protected]");
    int result = userMapper.update(null, updateWrapper);
    System.out.println("result:"+result);
}

六、常用插件

6.1 分頁插件

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

  • 添加配置類MyBatisPlusConfig

    @Configuration
    //掃描mapper接口所在的包
    @MapperScan("com.atguigu.mybatisplus.mapper")
    public class MyBatisPlusConfig {
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            // 添加分頁插件
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            // paginationInterceptor.setLimit(你的最大單頁限制數量,默認 500 條,小於 0 如 -1 不受限制);
            return interceptor;
        }
    }
    
  • 編寫測試方法

    @Test
    public void testPage(){
        //new Page()中的兩個參數分別是當前頁碼,每頁顯示數量
        Page<User> page = userMapper.selectPage(new Page<>(1, 2), null);
        List<User> users = page.getRecords();
        users.forEach(System.out::println);
    }
    

6.2 自定義分頁

上面調用的是MyBatis-Plus提供的帶有分頁的方法,那麼我們自己定義的方法如何實現分頁呢?

  • UserMapper接口中定義一個方法

    /**
      * 根據年齡查詢用戶列表,分頁顯示 
      * @param page 分頁對象,xml中可以從裏面進行取值,傳遞參數 Page 即自動分頁,必須放在第一位 
      * @param age 年齡 
      * @return 
      */
    Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
    
  • UserMapper.xml中編寫SQL實現該方法

    <!--Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);-->
    <select id="selectPageVo" resultType="User">
        select id,username as name,age,email from t_user where age > #{age}
    </select>
    
  • 編寫測試方法

    @Test
    public void testPageVo(){
        Page<User> page = userMapper.selectPageVo(new Page<User>(1,2), 20);
        List<User> users = page.getRecords();
        users.forEach(System.out::println);
    }
    

6.3 樂觀鎖

樂觀鎖(Optimistic Lock),顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,所以不會上鎖。但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制

作用:當要更新一條記錄的時候,希望這條記錄沒有被別人更新

樂觀鎖的實現方式:

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

6.3.1 場景


  • 一件商品,成本價是80元,售價是100元。老闆先是通知小李,說你去把商品價格增加50元。小李正在玩遊戲,耽擱了一個小時。正好一個小時後,老闆覺得商品價格增加到150元,價格太高,可能會影響銷量。又通知小王,你把商品價格降低30元。
  • 此時,小李和小王同時操作商品後臺系統。小李操作的時候,系統先取出商品價格100元;小王也在操作,取出的商品價格也是100元。小李將價格加了50元,並將100+50=150元存入了數據庫;小王將商品減了30元,並將100-30=70元存入了數據庫。是的,如果沒有鎖,小李的操作就完全被小王的覆蓋了。
  • 現在商品價格是70元,比成本價低10元。幾分鐘後,這個商品很快出售了1千多件商品,老闆虧1萬多。

6.3.2 樂觀鎖與悲觀鎖


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

6.3.3 模擬修改衝突


  • 數據庫中增加商品表

    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);
    
  • 添加一個實體類Product

    @Data
    public class Product {
        private Long id;
        private String name;
        private Integer price;
        private Integer version;
    }
    
  • 添加一個Mapper接口ProductMapper

    public interface ProductMapper extends BaseMapper<Product> {}
    
  • 測試方法

    @Test
    public void testProduct01(){
        //1.小李獲取商品價格
        Product productLi = productMapper.selectById(1);
        System.out.println("小李獲取的商品價格爲:" + productLi.getPrice());
    
        //2.小王獲取商品價格
        Product productWang = productMapper.selectById(1);
        System.out.println("小李獲取的商品價格爲:" + productWang.getPrice());
    
        //3.小李修改商品價格+50
        productLi.setPrice(productLi.getPrice()+50);
        productMapper.updateById(productLi);
    
        //4.小王修改商品價格-30
        productWang.setPrice(productWang.getPrice()-30);
        productMapper.updateById(productWang);
    
        //5.老闆查詢商品價格
        Product productBoss = productMapper.selectById(1);
        System.out.println("老闆獲取的商品價格爲:" + productBoss.getPrice());
    }
    
  • 執行結果

    image-20220521225803162

6.3.4 樂觀鎖解決問題


  • 實體類version字段添加註解@Version

    @Data
    public class Product {
        private Long id;
        private String name;
        private Integer price;
        @Version
        private Integer version;
    }
    
  • 添加樂觀鎖插件配置

    @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 testProduct01(){
        //1.小李獲取商品價格
        Product productLi = productMapper.selectById(1);
        System.out.println("小李獲取的商品價格爲:" + productLi.getPrice());
    
        //2.小王獲取商品價格
        Product productWang = productMapper.selectById(1);
        System.out.println("小李獲取的商品價格爲:" + productWang.getPrice());
    
        //3.小李修改商品價格+50
        productLi.setPrice(productLi.getPrice()+50);
        productMapper.updateById(productLi);
    
        //4.小王修改商品價格-30
        productWang.setPrice(productWang.getPrice()-30);
        int result = productMapper.updateById(productWang);
        if(result == 0){
            //操作失敗,重試
            Product productNew = productMapper.selectById(1);
            productNew.setPrice(productNew.getPrice()-30);
            productMapper.updateById(productNew);
        }
    
        //5.老闆查詢商品價格
        Product productBoss = productMapper.selectById(1);
        System.out.println("老闆獲取的商品價格爲:" + productBoss.getPrice());
    }
    

    image-20220521230448577

七、通用枚舉

表中的有些字段值是固定的,例如性別(男或女),此時我們可以使用MyBatis-Plus的通用枚舉來實現

  • 數據庫表添加字段sex

    image-20220521231317777

  • 創建通用枚舉類型

    @Getter
    public enum SexEnum {
        MALE(1, "男"),
        FEMALE(2, "女");
    
        @EnumValue //將註解所標識的屬性的值存儲到數據庫中
        private int sex;
        private String sexName;
    
        SexEnum(Integer sex, String sexName) {
            this.sex = sex;
            this.sexName = sexName;
        }
    }
    
  • User實體類中添加屬性sex

    public class User {
        private Long id;
        @TableField("username")
        private String name;
        private Integer age;
        private String email;
    
        @TableLogic
        private int isDeleted;  //邏輯刪除
    
        private SexEnum sex;
    }
    
  • 配置掃描通用枚舉

    #MyBatis-Plus相關配置
    mybatis-plus:
      #指定mapper文件所在的地址
      mapper-locations: classpath:mapper/*.xml
      configuration:
        #配置日誌
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        banner: off
        db-config:
          #配置mp的主鍵策略爲自增
          id-type: auto
          # 設置實體類所對應的表的統一前綴
          table-prefix: t_
      #配置類型別名所對應的包
      type-aliases-package: com.atguigu.mybatisplus.pojo
      # 掃描通用枚舉的包
      type-enums-package: com.atguigu.mybatisplus.enums
    
  • 執行測試方法

    @Test
    public void test(){
        User user = new User();
        user.setName("admin");
        user.setAge(33);
        user.setSex(SexEnum.MALE);
        int result = userMapper.insert(user);
        System.out.println("result:"+result);
    }
    

八、代碼生成器

1、引入依賴

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

2、快速生成

public class FastAutoGeneratorTest {

    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("atguigu") // 設置作者
                            //.enableSwagger() // 開啓 swagger 模式
                            .fileOverride() // 覆蓋已生成文件
                            .outputDir("D://mybatis_plus"); // 指定輸出目錄
                })
                .packageConfig(builder -> {
                    builder.parent("com.atguigu") // 設置父包名
                            .moduleName("mybatisplus") // 設置父包模塊名
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus")); // 設置mapperXml生成路徑
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user") // 設置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 設置過濾表前綴
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板
                .execute();
    }

}

九、多數據源

適用於多種場景:純粹多庫、 讀寫分離、 一主多從、 混合模式等

場景說明:

我們創建兩個庫,分別爲:mybatis_plus(以前的庫不動)與mybatis_plus_1(新建),將mybatis_plus庫的product表移動到mybatis_plus_1庫,這樣每個庫一張表,通過一個測試用例分別獲取用戶數據與商品數據,如果獲取到說明多庫模擬成功

9.1 創建數據庫及表

  • 創建數據庫mybatis_plus_1和表`product

    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;
    

9.2 新建工程引入依賴

自行新建一個Spring Boot工程並選擇MySQL驅動及Lombok依賴

引入MyBaits-Plus的依賴及多數據源的依賴

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

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

9.3 編寫配置文件

spring:
  # 配置數據源信息
  datasource:
    dynamic:
      # 設置默認的數據源或者數據源組,默認值即爲master
      primary: master
      # 嚴格匹配數據源,默認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: 132537
        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: 132537

9.4 創建實體類

  • 新建一個User實體類(如果數據庫表名有t_前綴記得配置)

    @Data
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    
  • 新建一個實體類Product

    @Data
    public class Product {
        private Long id;
        private String name;
        private Integer price;
        private Integer version;
    }
    

9.5 創建Mapper及Service

  • 新建接口UserMapper

    public interface UserMapper extends BaseMapper<User> {}
    
  • 新建接口ProductMapper

    public interface ProductMapper extends BaseMapper<Product> {}
    
  • 新建Service接口UserService指定操作的數據源

    @DS("master") //指定操作的數據源,master爲user表
    public interface UserService extends IService<User> {}
    
  • 新建Service接口ProductService指定操作的數據源

    @DS("slave_1")
    public interface ProductService extends IService<Product> {}
    
  • 自行建立Service的實現類

    ...
    

9.6 編寫測試方法

記得在啓動類中添加註解@MapperScan()

class TestDatasourceApplicationTests {
	@Resource
	UserService userService;

	@Resource
	ProductService productService;

	@Test
	void contextLoads() {
		User user = userService.getById(1L);
		Product product = productService.getById(1L);
		System.out.println("User = " + user);
		System.out.println("Product = " + product);
	}

}

image-20220522113049945

10、MyBatisX插件

MyBatis-Plus爲我們提供了強大的mapper和service模板,能夠大大的提高開發效率。

但是在真正開發過程中,MyBatis-Plus並不能爲我們解決所有問題,例如一些複雜的SQL,多表聯查,我們就需要自己去編寫代碼和SQL語句,我們該如何快速的解決這個問題呢,這個時候可以使用MyBatisX插件。

MyBatisX一款基於 IDEA 的快速開發插件,爲效率而生。

10.1 安裝MyBatisX插件

打開IDEA,File-> Setteings->Plugins->MyBatisX,搜索欄搜索MyBatisX然後安裝。

image-20220522115718361

10.2 快速生成代碼

  • 新建一個Spring Boot項目引入依賴(創建工程時記得勾選lombok及mysql驅動)

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.0</version>
    </dependency>
    
  • 配置數據源信息

    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: 132537
    
  • 在IDEA中與數據庫建立鏈接

    image-20220522120758740

  • 填寫數據庫信息並保存

    image-20220522121434468

  • 找到我們需要生成的表點擊右鍵

    image-20220522121613909

  • 填寫完信息以後下一步

    image-20220522122127649

  • 繼續填寫信息

    image-20220522122525598

  • 大功告成(真特麼好用yyds)

    image-20220522122612334

10.2 快速生成CRUD

MyBaitsX可以根據我們在Mapper接口中輸入的方法名快速幫我們生成對應的sql語句

image-20220522123143852

image-20220522123202310

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章