MyBatis-Plus學習筆記

1. 簡介

官網(wǎng):https://baomidou.com/pages/24112f/

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

對比mybatis,MP減少了sql的書寫,之前的mybatis是需要將sql語句寫在xml文件中,涉及的操作比較繁瑣,而MP基本不用書寫簡單SQL,開發(fā)起來比較方便,而且MP擁有的特性也很受用。

2. 特性

  • 無侵入

    只做增強不做改變,引入它不會對現(xiàn)有工程產(chǎn)生影響,如絲般順滑

  • 損耗小

    啟動即會自動注入基本CRUD,性能基本無損耗,直接面向?qū)ο蟛僮?/p>

  • 強大的CRUD操作

    內(nèi)置通用Mapper,通用Service,僅僅通過少量配置即可實現(xiàn)單表大部分CRUD操作,更有強大的條件構造器,滿足各類使用需求

  • 支持Lambda形式調(diào)用

    通過Lambda表達式,方便的編寫各類查詢條件,無需擔心字段寫錯

  • 支持主鍵自動生成

    支持多達4種主鍵策略(內(nèi)含分布式唯一ID生成器- Sequence),可自由配置,完美解決主鍵問題

  • 支持ActiveRecord模式

    支持ActiveRecord形式調(diào)用,實體類只需繼承Model類即可進行強大的CRUD操作

  • 支持自定義全局通用操作

    支持全局通用方法注入

  • 內(nèi)置代碼生成器

    采用代碼或者Maven插件可快速生成Mapper,Model,Service,Controller層代碼,支持模板引擎,更有超多自定義配置

  • 內(nèi)置分頁插件

    基于Mybatis物理分頁,開發(fā)者無需關心具體操作,配置好插件后,寫分頁等同于普通List查詢

  • 分頁插件支持多種數(shù)據(jù)庫

    支持MySQL,Oracle,DB2,H2,HSQL,SQLite,Postgre,SQLServer等多種數(shù)據(jù)庫

  • 內(nèi)置性能分析插件

    可輸出SQL語句以及其執(zhí)行時間,建議開發(fā)測試時啟用該功能,能快速揪出慢查詢

  • 內(nèi)置全局攔截插件

    提供全表delete,update操作只能分析阻斷,也可以自定義攔截規(guī)則,預防誤操作

3. 快速開始

  1. 首先創(chuàng)建數(shù)據(jù)表,假設有一張用戶表user

    CREATE TABLE `user` (
     `id` bigint(20) NOT NULL COMMENT '主鍵ID',
        `name` varchar(32) DEFAULT NULL COMMENT '姓名',
        `age` int(11) DEFAULT NULL COMMENT '年齡',
        `email` varchar(32) DEFAULT NULL COMMENT '郵箱',
        PRIMARY KEY(`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    這里id字段的類型為bigint,是因為mybatis默認的主鍵是雪花算法計算出來的,比較長超出范圍,需要用bigint

  2. 插入一些預置的數(shù)據(jù)

    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');
    
  3. 創(chuàng)建Springboot工程,引入依賴

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.2</version>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.13</version>
    </dependency>
    
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
    
  4. 編寫配置(數(shù)據(jù)庫使用MySQL,數(shù)據(jù)源使用Druid)

    spring:
      # 配置數(shù)據(jù)源信息
      datasource:
        # 配置數(shù)據(jù)源類型
        type: com.alibaba.druid.pool.DruidDataSource
        # 配置連接數(shù)據(jù)庫信息
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    

    這里有個要注意的:

    1. 驅(qū)動類(driver-class-name)的值

      • Springboot 2.0或使用的數(shù)據(jù)庫是5.X版本,driver-class-name的值為:com.mysql.jdbc.Driver
      • Springboot 2.1及以上或數(shù)據(jù)庫是8.x版本,driver-class-name的值為:com.mysql.cj.jdbc.Driver
    2. 連接地址(url)的值

      • mysql5.x版本的url

        jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf8&useSSL=false

      • mysql8.x版本的url

        jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false

      mysql8多了一個時區(qū)的設置

  5. 添加實體類User

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

    這里的字段類型,盡量用包裝類。

    在插入數(shù)據(jù)時,由于ID是自動生成的,在設置數(shù)據(jù)時,如果用了基本數(shù)據(jù)類型long,就得設置為0,就會變成了插入數(shù)據(jù)的ID了,而包裝類,可以設置為null。(個人理解)

  6. 添加對應UserMapper

    public interface UserMapper extends BaseMapper<User> {
    }
    

    這里UserMapper繼承了BaseMapper,這個BaseMapper是MybatisPlus提供的模板Mapper,其中包含了基本的CRUD方法,泛型就是要操作的實體類型。

    Mapper 繼承該接口后,無需編寫 mapper.xml 文件,即可獲得CRUD功能

  7. 啟動類加上MapperScan

    @SpringBootApplication
    @MapperScan("com.zzy.mybatisplus_test.mapper")
    public class MybatisplusTestApplication {
    
     public static void main(String[] args) {
         SpringApplication.run(MybatisplusTestApplication.class, args);
     }
    
    }
    
  8. 編寫測試方法

    @Autowired
    private UserMapper userMapper;
    
    @Test
    public void testSelectList() {
     userMapper.selectList(null).forEach(System.out::println);
    }
    

    UserMapper中的selectList方法需要一個參數(shù),是MP內(nèi)置的條件封裝器,這里填寫null,就是指不需要條件,選擇所有

  9. 測試結果

    控制臺會輸出前面插入的數(shù)據(jù)

    User(id=1, name=Jone, age=18, email=test1@baomidou.com)
    User(id=2, name=Jack, age=20, email=test2@baomidou.com)
    User(id=3, name=Tom, age=28, email=test3@baomidou.com)
    User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
    User(id=5, name=Billie, age=24, email=test5@baomidou.com)
    

4. MP的CRUD操作

在操作之前,我們可以配置MP的日志輸出,能在控制臺顯示MP執(zhí)行的SQL語句,方便Debug與學習。

# 配置Mybatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
4.1 使用BaseMapper

在MP中,基本的CRUD在內(nèi)置的BaseMapper中都已經(jīng)得到了實現(xiàn)了,我們可以直接使用。

  1. Insert

    /**
     * 插入一條記錄
     *
     * @param entity 實體對象
     */
    int insert(T entity);
    
    @Test
    public void testInsert() {
        User user = new User(null, "zhangsan", 23, "zhangsan@qq.com");
        int result = userMapper.insert(user);
        System.out.println("result: " + result);
        System.out.println("用戶id為:" + user.getId());
    }
    

    輸出數(shù)據(jù):

    ==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
    ==> Parameters: 1546431457684246529(Long), zhangsan(String), 23(Integer), zhangsan@qq.com(String)
    <== Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2fd9fb34]
    result: 1
    用戶id為:1546431457684246529

    用戶id是MP默認使用雪花算法生成的。

  2. Delete

    // 根據(jù) entity 條件,刪除記錄
    // wrapper: 實體對象封裝操作類,可以為null
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
    // 刪除(根據(jù)ID 批量刪除)
    // idList:主鍵ID列表,不能為null以及empty
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根據(jù) ID 刪除
    int deleteById(Serializable id);
    // 根據(jù) columnMap 條件,刪除記錄
    // columnMap: 表字段map對象
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
    // 通過Id刪除記錄
    @Test
    public void testDeleteById() {
        int result = userMapper.deleteById(1546431457684246529L);
        System.out.println("result = " + result);
    }
    
    // 輸出結果
    ==>  Preparing: DELETE FROM user WHERE id=?
    ==> Parameters: 1546431457684246529(Long)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@687389a6]
    result = 1
    
    // 根據(jù)ID批量刪除
    @Test
    public void testDeleteByBatchId() {
        List<Long> list = Arrays.asList(1L, 2L, 3L);
        int result = userMapper.deleteBatchIds(list);
        System.out.println("result = " + result);
    }
    
    // 輸出結果
    ==>  Preparing: DELETE FROM user WHERE id IN ( ? , ? , ? )
    ==> Parameters: 1(Long), 2(Long), 3(Long)
    <==    Updates: 3
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4ffced4e]
    result = 3
    
    // 通過map條件刪除
    @Test
    public void testDeleteByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("age", 24);
        map.put("name", "Billie");
        int result = userMapper.deleteByMap(map);
        System.out.println("result = " + result);
    }
    
    // 輸出結果
    ==>  Preparing: DELETE FROM user WHERE name = ? AND age = ?
    ==> Parameters: Billie(String), 24(Integer)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6094de13]
    result = 1
    
  3. Update

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

    前面演示了刪除的方法,把數(shù)據(jù)幾乎都刪光了,這里重新將原始數(shù)據(jù)添加到數(shù)據(jù)庫,再測試。

    // 通過Id修改
    @Test
    public void testUpdateById() {
        User user = new User(3L, "zhangsan", 20, null);
        int result = userMapper.updateById(user);
        System.out.println("result = " + result);
    }
    
    // 輸出結果
    ==>  Preparing: UPDATE user SET name=?, age=? WHERE id=?
    ==> Parameters: zhangsan(String), 20(Integer), 3(Long)
    <==    Updates: 1
    Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@bbb6f0]
    result = 1
    // 注意,email設置為null,在執(zhí)行時并沒有去修改email的值的。
    
  4. Select

    // 根據(jù) ID 查詢
    T selectById(Serializable id);
    // 根據(jù) entity 條件,查詢一條記錄
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 查詢(根據(jù)ID 批量查詢)
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
    // 根據(jù) entity 條件,查詢?nèi)坑涗?List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 查詢(根據(jù) columnMap 條件)
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?。注意?只返回第一個字段的值
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根據(jù) entity 條件,查詢?nèi)坑涗洠ú⒎摚?IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據(jù) Wrapper 條件,查詢?nèi)坑涗洠ú⒎摚?IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    // 根據(jù) Wrapper 條件,查詢總記錄數(shù)
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    // 根據(jù)Id查詢
    @Test
    public void testSelectById() {
        User user = userMapper.selectById(2L);
        System.out.println("user = " + user);
    }
    
    // 輸出結果
    ==>  Preparing: SELECT id,name,age,email FROM user WHERE id=?
    ==> Parameters: 2(Long)
    <==    Columns: id, name, age, email
    <==        Row: 2, Jack, 20, test2@baomidou.com
    <==      Total: 1
    user = User(id=2, name=Jack, age=20, email=test2@baomidou.com)
    
    // 根據(jù)多個Id批量查詢
    @Test
    public void testSelectByBatchId() {
        List<Long> list = Arrays.asList(4L, 5L);
        List<User> users = userMapper.selectBatchIds(list);
        System.out.println("users = " + users);
    }
    // 輸出結果
    ==>  Preparing: SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
    ==> Parameters: 4(Long), 5(Long)
    <==    Columns: id, name, age, email
    <==        Row: 4, Sandy, 21, test4@baomidou.com
    <==        Row: 5, Billie, 24, test5@baomidou.com
    <==      Total: 2
    users = [User(id=4, name=Sandy, age=21, email=test4@baomidou.com), User(id=5, name=Billie, age=24, email=test5@baomidou.com)]
    
    // 通過map條件查詢
    @Test
    public void testSelectByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("age", 21);
        List<User> users = userMapper.selectByMap(map);
        System.out.println(users);
    }
    // 輸出結果
    ==>  Preparing: SELECT id,name,age,email FROM user WHERE age = ?
    ==> Parameters: 21(Integer)
    <==    Columns: id, name, age, email
    <==        Row: 4, Sandy, 21, test4@baomidou.com
    <==      Total: 1
    [User(id=4, name=Sandy, age=21, email=test4@baomidou.com)]
    
4.2 使用通用Service

說明:

  • 通用Service CRUD封裝了IService接口,進一步封裝CRUD采用get 查詢單行,remove 刪除,list 查詢集合,page 分頁前綴命名的方式區(qū)分Mapper層,避免混淆
  • 泛型T為任意實體對象
  • 建議如果存在自定義通用Service方法的可能,請創(chuàng)建自己的IBaseService繼承MP提供的基類
  1. IService

    MP中有一個接口IService和其實現(xiàn)類ServiceImpl,封裝了常見的業(yè)務層邏輯(具體翻看源碼)

    Save

    // 插入一條記錄(選擇字段,策略插入)
    boolean save(T entity);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList);
    // 插入(批量)
    boolean saveBatch(Collection<T> entityList, int batchSize);
    

    SaveOrUpdate

    // TableId 注解存在更新記錄,否插入一條記錄
    boolean saveOrUpdate(T entity);
    // 根據(jù)updateWrapper嘗試更新,否繼續(xù)執(zhí)行saveOrUpdate(T)方法
    boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList);
    // 批量修改插入
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
    

    Remove

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

    Update

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

    Get

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

    List

    // 查詢所有
    List<T> list();
    // 查詢列表
    List<T> list(Wrapper<T> queryWrapper);
    // 查詢(根據(jù)ID 批量查詢)
    Collection<T> listByIds(Collection<? extends Serializable> idList);
    // 查詢(根據(jù) columnMap 條件)
    Collection<T> listByMap(Map<String, Object> columnMap);
    // 查詢所有列表
    List<Map<String, Object>> listMaps();
    // 查詢列表
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
    // 查詢?nèi)坑涗?List<Object> listObjs();
    // 查詢?nèi)坑涗?<V> List<V> listObjs(Function<? super Object, V> mapper);
    // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?List<Object> listObjs(Wrapper<T> queryWrapper);
    // 根據(jù) Wrapper 條件,查詢?nèi)坑涗?<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
    

    Page

    // 無條件分頁查詢
    IPage<T> page(IPage<T> page);
    // 條件分頁查詢
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
    // 無條件分頁查詢
    IPage<Map<String, Object>> pageMaps(IPage<T> page);
    // 條件分頁查詢
    IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
    
  2. 創(chuàng)建Service接口和實現(xiàn)類

    public interface UserService extends IService<User> {
    }
    
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    }
    
  3. 測試查詢記錄數(shù)

    @Test
    public void testGetCount() {
        long count = userService.count();
        System.out.println("count = " + count);
    }
    
  4. 測試批量插入

    @Test
    public void testSaveBatch() {
        // SQL 長度有限制,海量數(shù)據(jù)插入單條SQL無法實行
        // 因此MP將批量插入放在了通用Service中實現(xiàn),而不是通用Mapper
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 5; i++ ){
            User user = new User();
            user.setName("abc" + 1);
            user.setAge(20);
            users.add(user);
        }
        //  INSERT INTO t_user1 ( name, age ) VALUES ( ?, ? )
        userService.saveBatch(users);
    }
    
4.3 條件構造器
在BaseMapper類中的方法,大多數(shù)方法中都有帶`Wrapper`類型的形參,這個`Wrapper`就是條件構造器,能夠針對SQL語句設置不同的條件。**如果沒有條件,則可以將該形參賦值為null,即為查詢(刪除/修改)所有數(shù)據(jù)**

Wrapper的關系樹:
wrapper.jpg
  • Wrapper:條件構造抽閑類,最頂端父類
    • AbstractWrapper:用于查詢條件封裝,生成sql的where條件
      • QueryWrapper:查詢條件封裝
      • UpdateWrapper: 更新條件封裝
      • AbstractLambdaWrapper:使用Lambda語法的條件封裝
        • LambdaQueryWrapper:用于Lambda語法使用的查詢Wrapper
        • LambdaUpdateWrapper:用于Lambda語法使用的更新Wrapper
  1. QueryWrapper

    • 組裝查詢條件

      @Test
      public void test01() {
          // 查詢用戶名包含a,年齡在20-30之間,并且郵箱不為null的用戶信息
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的sql:SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
          wrapper.like("name", "a")
              .between("age", 20, 30)
              .isNotNull("email");
          List<User> users = userMapper.selectList(wrapper);
          users.forEach(System.out::println);
      }
      // 輸出結果
      User(id=2, name=Jack, age=20, email=test2@baomidou.com)
      User(id=3, name=zhangsan, age=20, email=test3@baomidou.com)
      User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
      
    • 組裝排序條件

      @Test
      public void test02() {
          // 按年齡降序查詢用戶,如果年齡相同則按id升序排列
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的sql:SELECT id,name,age,email FROM user ORDER BY age DESC,id ASC
          wrapper.orderByDesc("age")
              .orderByAsc("id");
          List<User> users = userMapper.selectList(wrapper);
          users.forEach(System.out::println);
      }
      // 輸出結果
      User(id=5, name=Billie, age=24, email=test5@baomidou.com)
      User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
      User(id=2, name=Jack, age=20, email=test2@baomidou.com)
      User(id=3, name=zhangsan, age=20, email=test3@baomidou.com)
      User(id=1, name=Jone, age=18, email=test1@baomidou.com)
      
    • 組裝刪除條件

      @Test
      public void test03() {
          // 刪除email為空的用戶
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的sql:DELETE FROM user WHERE (email IS NULL)
          wrapper.isNull("email");
          int result = userMapper.delete(wrapper);
          System.out.println("result = " + result);
      }
      // 輸出結果
      result = 0,因為沒有用戶email是null的
      
    • 條件的優(yōu)先級

      @Test
      public void test04() {
          // 將(年齡大于20并且用戶名中包含a)或者郵箱為null的用戶信息修改
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的sql:UPDATE user SET age=?, email=? WHERE (age >= ? AND name LIKE ? OR email IS NULL)
          wrapper.ge("age", 20)
              .like("name", "a")
              .or()
              .isNull("email");
          User user = new User();
          user.setAge(33);
          user.setEmail("user@qq.com");
          int update = userMapper.update(user, wrapper);
          System.out.println("update = " + update);
      }
      // 輸出結果
      update = 3
      
      @Test
      public void test05() {
          // 將用戶名中包含a并且(年齡大于20或者郵箱為null)的用戶信息修改
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的SQL:UPDATE user SET age=?, email=? WHERE (name LIKE ? AND (age >= ? OR email IS NULL))
          wrapper.like("name", "a")
              .and(i -> i.ge("age", 20).or().isNull("email"));
          User user = new User();
          user.setAge(10);
          user.setEmail("abc@qq.com");
          int update = userMapper.update(user, wrapper);
          System.out.println("update = " + update);
      }
      

      主動調(diào)用or表示接著的下一個方法不是用and連接(默認是使用and連接的)

    • 組裝select子句

      @Test
      public void test06() {
          // 查詢用戶信息的name,age字段
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構造的sql:SELECT name,age FROM user
          wrapper.select("name", "age");
          List<User> users = userMapper.selectList(wrapper);
          users.forEach(System.out::println);
      }
      // 輸出結果
      User(id=null, name=Jone, age=18, email=null)
      User(id=null, name=Jack, age=10, email=null)
      User(id=null, name=zhangsan, age=10, email=null)
      User(id=null, name=Sandy, age=10, email=null)
      User(id=null, name=Billie, age=24, email=null)
      
    • 實現(xiàn)子查詢

      @Test
      public void test07() {
          // 查詢id小于等于3的用戶信息
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構建的sql:SELECT id,name,age,email FROM user WHERE (id IN (select id from user where id <= 3))
          wrapper.inSql("id", "select id from user where id <= 3");
          List<User> list = userMapper.selectList(wrapper);
          list.forEach(System.out::println);
      }
      // 輸出結果
      User(id=1, name=Jone, age=18, email=test1@baomidou.com)
      User(id=2, name=Jack, age=10, email=abc@qq.com)
      User(id=3, name=zhangsan, age=10, email=abc@qq.com)
      

      這里只是測試效果,實際應該不會這樣select id

  2. LambdaQueryWrapper

    上面使用QueryWrapper中,我們查詢時使用的是數(shù)據(jù)庫列名字符串,容易出現(xiàn)寫錯或與類的屬性名不一致的導致寫錯,MP提供了LambdaQueryWrapper,可以通過獲取Lambda數(shù)據(jù)庫列名。

    @Test
    public void test08() {
        // 查詢用戶名包含a,年齡在10-20之間,并且郵箱不為null的用戶信息
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
        wrapper.like(User::getName, "a")
            .between(User::getAge, 10, 20)
            .isNotNull(User::getEmail);
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    

    使用LambdaQueryWrapper后,不用直接寫列名字符串形式,可以通過類獲取

  3. UpdateWrapper

    前面的測試例子中也有用QueryWrapper實現(xiàn)update的,使用UpdateWrapper可以使用set方法直接設置,不需要創(chuàng)建User對象,如果創(chuàng)建了User對象,并且設置屬性,傳遞給了update方法,那么設置列會加入到sql中。

    @Test
    public void test09() {
        // 將(年齡大于20或郵箱為null)并且用戶名中包含有a的用戶信息修改
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        //  UPDATE user SET age=?,email=? WHERE (name LIKE ? AND (age >= ? OR email IS NULL))
        updateWrapper.set("age", 18)
            .set("email", "user@qq.com")
            .like("name", "a")
            .and(i -> i.ge("age", 20).or().isNull("email"));
        int update = userMapper.update(null, updateWrapper);
        System.out.println("update = " + update);
    }
    
  4. LambdaUpdateWrapper

    將上面的測試例子用Lambda的方式來編寫

    @Test
    public void test09() {
        // 將(年齡大于20或郵箱為null)并且用戶名中包含有a的用戶信息修改
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(User::getAge, 38)
            .set(User::getEmail, "user@qq.com")
            .like(User::getName, "a")
            .and(i -> i.ge(User::getAge, 10).or().isNull(User::getEmail));
    
        User user = new User();
        user.setName("Billiea");
        int update = userMapper.update(user, updateWrapper);
        System.out.println("update = " + update);
    }
    
  5. 總結

    1. QueryWrapper、QueryChainWrapper只能指定需要的數(shù)據(jù)庫列名
    2. LambdaQueryWrapperLambdaQueryChainWrapper可以通過Lambda獲取數(shù)據(jù)庫列名
    3. QueryWrapper 、LambdaQueryWrapper不能使用鏈式查詢的方式,必須借助BaseMapper來執(zhí)行
    4. QueryChainWrapper、LambdaQueryChainWrapper可以使用鏈式查詢的方式,如list(),one()
4.4 condition
在真正開發(fā)過程中,組裝條件時,有些數(shù)據(jù)是來源于用戶輸入的,是可選的,因此在組裝條件之前是需要先判斷條件是否成立的,成立才組裝條件到SQL執(zhí)行。

MP在條件構造器的方法中提供了帶condition參數(shù)的方法

未使用帶condition參數(shù)的方法時的操作:

@Test
public void test10() {
    // 定義查詢條件,有可能為null,假設輸入數(shù)據(jù)如下
    String name = null;
    Integer ageBegin = 10;
    Integer ageEnd = 24;

    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    if (StringUtils.isNotBlank(name)) {
        queryWrapper.like(User::getName, "a");
    }
    if (ageBegin != null) {
        queryWrapper.ge(User::getAge, ageBegin);
    }
    if (ageEnd != null) {
        queryWrapper.le(User::getAge, ageEnd);
    }
    // SELECT id,name,age,email FROM user WHERE (age >= ? AND age <= ?)
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

使用帶condition參數(shù)的方法:

@Test
public void test11() {
    // 定義查詢條件,有可能為null,假設輸入數(shù)據(jù)如下
    String name = null;
    Integer ageBegin = 10;
    Integer ageEnd = 24;

    LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(name), User::getName, name)
        .ge(ageBegin != null, User::getAge, ageBegin)
        .le(ageEnd != null, User::getAge, ageEnd);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

5. 常用注解

  1. @TableName

    在以上的測試中,我們使用MP進行CRUD時,并沒有指定要操作的表,只是在UserMapper接口繼承BaseMapper時,設置了泛型為User,但實際操作的表是User表。

    因此可以得出結論,MP在確定操作的表時,是由BaseMapper的泛型決定的,即實體類型決定,而且默認操作的表名與實體類的類名一致。

    @TableName的作用是標識實體類對應的表,作用在實體類上。

    可以做如下測試:將數(shù)據(jù)庫的表user改名為t_user,運行前面的測試程序,報錯如下

    org.springframework.jdbc.BadSqlGrammarException: 
    ### Error querying database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist
    ### The error may exist in com/zzy/mybatisplus_test/mapper/UserMapper.java (best guess)
    ### The error may involve defaultParameterMap
    ### The error occurred while setting parameters
    ### SQL: SELECT  id,name,age,email  FROM user     WHERE (age >= ? AND age <= ?)
    ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist
    ; bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'mybatisplus.user' doesn't exist
    

    解決方法:因為當前實體類為User,數(shù)據(jù)庫表為t_user,二者名字不匹配,因此報錯,可以在User類上添加注解@TableName("t_user")標識User類與表t_user匹配

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

    上面我們只是改了一個表,如果所有的實體類對應的表都有固定的前綴,比如t_,我們總不能手動一個個的添加@TableName注解吧,那得累死

    為了解決這種情況,我們可以通過MP全局配置,為實體類所對應的表名設置默認的前綴:

    # 配置Mybatis日志
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          # 配置MP操作表的默認前綴
          table-prefix: t_
    
  2. @TableId

    經(jīng)過以上測試,MP在實現(xiàn)CRUD時,會默認將id作為主鍵列,并在插入數(shù)據(jù)時,默認基于雪花算法的策略生成id

    問題:如果實體類和表中表示主鍵的不是id,而是其他字段,比如uid,那MP還會自動識別uid為主鍵列嗎?

    測試:將實體類中屬性id改為uid,表中字段id也改為uid,測試添加功能

    ### SQL: INSERT INTO t_user1  ( name, age, email )  VALUES  ( ?, ?, ? )
    ### Cause: java.sql.SQLException: Field 'uid' doesn't have a default value
    ; Field 'uid' doesn't have a default value; nested exception is java.sql.SQLException: Field 'uid' doesn't have a default value
    

    解決方法:通過@TableId將uid屬性標識為主鍵

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

    若實體類中主鍵對應的屬性為id,而表中表示主鍵的字段為uid,此時如果只在屬性上添加注解@TableId,則會拋出異常Unknown column 'id' in 'field list',也就是MP仍然會將id作為表的主鍵操作,而表中表示主鍵的是字段uid。

    此時就需要通過@TableId的value屬性了,指定表中主鍵字段:@TableId("uid")@TableId(value="uid")

    @TableId的type屬性

    常用的主鍵策略:

    • IdType.ASSIGN_ID(默認)

      基于雪花算法的策略生成數(shù)據(jù)id,與數(shù)據(jù)庫id是否設置自增無關

    • IdType.AUTO

      使用數(shù)據(jù)庫的自增策略,注意,該類型請確保數(shù)據(jù)庫設置了id自增,否則無效

    配置全局主鍵策略:

    # 配置Mybatis日志
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          # 配置MP操作表的默認前綴
          table-prefix: t_
          # 配置MP的主鍵策略
          id-type: auto
    
  3. @TableField

    經(jīng)過以上的測試,我們可以發(fā)現(xiàn),MP在執(zhí)行SQL語句時,要保證實體類中的屬性名和表中的字段名一致

    如果實體類中的屬性名和字段名不一致,會產(chǎn)生什么問題?

    • 如果實體類中屬性使用的是駝峰命名風格,而表中字段使用的是下劃線命名風格,比如實體類屬性為userName,表中字段為user_name.

      這種情況MP會自動將下劃線命名的風格轉(zhuǎn)化為駝峰命名風格

    • 如果實體類中的屬性和表中的字段不滿足第一個情況,比如實體類屬性是name,表中字段為username

      這種情況需要在實體類上添加注解@TableField("username")設置屬性對應的字段名

  4. @TableLogic

    邏輯刪除:假刪除,就是新增一個字段用來表示刪除狀態(tài),在數(shù)據(jù)庫中仍舊可以看到這條記錄,不是真正的刪除,但是對于MP來說像是刪除了的,無法使用CRUD再操作這條數(shù)據(jù)。

    實現(xiàn)邏輯刪除:

    • 數(shù)據(jù)庫中創(chuàng)建邏輯刪除狀態(tài)列,設置默認值為0
    • 實體類中添加邏輯刪除屬性,添加注解@TableLogic

6.插件

  1. 分頁插件

    MP自帶分頁插件,只需要簡單配置就可以實現(xiàn)分頁功能了。

    • 添加配置類

      @Configuration
      @MapperScan("com.zzy.mybatisplus_test.mapper") // 可以將主類中Mapper的注解移到這里
      public class MybatisPlusConfig {
      
          @Bean
          public MybatisPlusInterceptor mybatisPlusInterceptor() {
              MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
              // 數(shù)據(jù)類型為DbType.MYSQL
              mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
      
              return mybatisPlusInterceptor;
          }
      }
      
    • 測試

      @Test
      public void testPage() {
          // 設置分頁參數(shù),通過分頁參數(shù)來確定數(shù)據(jù)
          Page<User> page = new Page<User>(1, 3);
          userMapper.selectPage(page, null);
          // 獲取分頁數(shù)據(jù)
          List<User> list = page.getRecords();
          list.forEach(System.out::println);
          System.out.println("當前頁: " + page.getCurrent());
          System.out.println("每頁顯示的條數(shù): " + page.getSize());
          System.out.println("總記錄數(shù): " + page.getTotal());
          System.out.println("總頁數(shù): " + page.getPages());
          System.out.println("是否有上一頁:" + page.hasPrevious());
          System.out.println("是否有下一頁: " + page.hasNext());
      }
      

    使用XML自定義分頁

    • UserMapper中定義接口方法

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

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.zzy.mybatisplus_test.mapper.UserMapper">
          <sql id="BaseColumns">uid, name, age, email</sql>
      
          <select id="selectPageVo" resultType="User">
              select <include refid="BaseColumns"/> from t_user1 where age > #{age}
          </select>
      </mapper>
      
    • 測試

      @Test
      public void testSelectPageVo() {
          // 設置分頁參數(shù)
          Page<User> page = new Page<>(1, 3);
          userMapper.selectPageVo(page, 20);
          // 獲取分頁數(shù)據(jù)
          List<User> list = page.getRecords();
          list.forEach(System.out::println);
          System.out.println("當前頁: " + page.getCurrent());
          System.out.println("每頁顯示的條數(shù): " + page.getSize());
          System.out.println("總記錄數(shù): " + page.getTotal());
          System.out.println("總頁數(shù): " + page.getPages());
          System.out.println("是否有上一頁:" + page.hasPrevious());
          System.out.println("是否有下一頁: " + page.hasNext());
      }
      
  2. 樂觀鎖插件

    • 悲觀鎖與樂觀鎖的理解可以看看這個文章:https://my.oschina.net/hanchao/blog/3057429

    • 樂觀鎖實現(xiàn)方式:(當要更新一條記錄的時候,希望這條記錄沒有被別人更新)

      • 取出記錄時,獲取當前version

      • 更新時,帶上這個version

      • 執(zhí)行更新時,set version = newVersion where version = oldVersion

      • 如果version不對,就更新失敗

    • 模擬沖突場景

      有一件商品成本80元,售價100元,老板先讓小李將商品價格提高50元,小李沒有及時操作,一小時后,老板覺得商品售價150元太高,就又讓小王去將價格降低30元。

      老板的想法是100 -> 150 -> 120

      但是這時候小李和小王同時操作系統(tǒng),取出的商品價格都是100元,然后小李加了50元,將150存到了數(shù)據(jù)庫;小王減掉30元,將70存入數(shù)據(jù)庫.

      如果沒有鎖,小李的操作就會被小王覆蓋了,最終可能價格變成了70元,導致老板虧錢

      新增一張商品表

      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)
      );
      
      # 添加數(shù)據(jù)
      INSERT INTO t_product (id, NAME, price) VALUES (1, '筆記本', 100);
      

      測試(未使用樂觀鎖):

      @Test
      public void testConCurrentUpdate() {
          // 1. 小李
          Product p1 = productMapper.selectById(1L);
          System.out.println("小李取出的價格:" + p1.getPrice());
      
          // 2. 小王
          Product p2 = productMapper.selectById(1L);
          System.out.println("小王取出的價格: " + p2.getPrice());
      
          // 3. 小李將價格增加50元,存入了數(shù)據(jù)庫
          p1.setPrice(p1.getPrice() + 50);
          int ret1 = productMapper.updateById(p1);
          System.out.println("小李修改結果:" + ret1);
      
          // 4. 小王將價格減了30元,存入了數(shù)據(jù)庫
          p2.setPrice(p2.getPrice() - 30);
          int ret2 = productMapper.updateById(p2);
          System.out.println("小王修改結果:" + ret2);
      
          // 最后的結果
          Product p3 = productMapper.selectById(1L);
          System.out.println("最后的價格是:" + p3.getPrice());
      }
      // 最終輸出的價格變?yōu)?0
      
    • 樂觀鎖配置:

      1. 配置插件

        @Configuration
        @MapperScan("com.zzy.mybatisplus_test.mapper")
        public class MybatisPlusConfig {
        
            @Bean
            public MybatisPlusInterceptor mybatisPlusInterceptor() {
                MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
                // 數(shù)據(jù)類型為DbType.MYSQL
                mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
                // 樂觀鎖插件
                mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        
                return mybatisPlusInterceptor;
            }
        }
        
      2. 實體類字段加上@Version注解

        模擬沖突:

        @Data
        @NoArgsConstructor
        @AllArgsConstructor
        public class Product {
            private Integer id;
            private String name;
            private Integer price;
            @Version
            private Integer version;
        }
        
    • 測試(使用樂觀鎖后的測試)

      @Test
      public void testConcurrentVesionUpdate() {
          // 小李取數(shù)據(jù)
          Product p1 = productMapper.selectById(1L);
      
          // 小王取數(shù)據(jù)
          Product p2 = productMapper.selectById(1L);
      
          // 小李修改 + 50
          p1.setPrice(p1.getPrice() + 50);
          int result = productMapper.updateById(p1);
          System.out.println("小李修改的結果:" + result);
      
          // 小王修改 - 30
          p2.setPrice(p2.getPrice() - 30);
          int ret = productMapper.updateById(p2);
          System.out.println("小王修改的結果:" + ret);
          if (ret == 0) {
              // 失敗重試,重新獲取version并更新
              p2 = productMapper.selectById(1L);
              p2.setPrice(p2.getPrice() - 30);
              ret = productMapper.updateById(p2);
          }
          System.out.println("小王修改重試的結果:" + ret);
      
          // 老板看價格
          Product p3 = productMapper.selectById(1L);
          System.out.println("老板看價格:" + p3.getPrice());
      }
      

7.通用枚舉

表中有些字段值是固定的,比如性別,只有男或女,此時我們可以用MP的通用枚舉來實現(xiàn)
  1. 數(shù)據(jù)庫表增加一個字段sex

    enum.png
  2. 創(chuàng)建通用枚舉類型

    @Getter
    public enum SexEnum {
        MALE(1, "男"),
        FEMALE(2, "女");
    
        @EnumValue
        private Integer sex;
        private String sexName;
    
        SexEnum(Integer sex, String sexName) {
            this.sex = sex;
            this.sexName = sexName;
        }
    }
    
    
  3. 配置掃描通用枚舉

    # 配置Mybatis日志
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      global-config:
        db-config:
          # 配置MP操作表的默認前綴
          table-prefix: t_
          # 配置MP的主鍵策略
          id-type: auto
      type-aliases-package: com.zzy.mybatisplus_test.pojo
      # 配置掃描通用枚舉
      type-enums-package: com.zzy.mybatisplus_test.enums
    
  4. 測試

    @Test
    public void testSexEnum() {
        User user = new User();
        user.setName("Enum");
        user.setAge(20);
        // 設置性別信息為枚舉項,會將@EnumValue注解所標注的屬性值存儲到數(shù)據(jù)庫
        user.setSex(SexEnum.FEMALE);
        userMapper.insert(user);
    }
    

8.多數(shù)據(jù)源

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

接下來模擬一個純粹多庫的場景:

創(chuàng)建兩個庫,分別為:mybatisplus(上面的庫不動)與mybatisplus_1(新建),將mybatis_plus庫的t_product表移動到mybatisplus_1庫,這樣每個庫一張表,通過一個測試用例分別獲取用戶數(shù)據(jù)與商品數(shù)據(jù),如果獲取到說明多庫模擬成功。

  1. 創(chuàng)建數(shù)據(jù)庫及表

    CREATE DATABASE `mybatisplus_1`;
    use `mybatisplus_1`;
    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);
    
    # 刪除mybatisplus庫的t_product表
    use mybatisplus;
    DROP TABLE IF EXISTS t_product;
    
  2. 引入依賴

    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
  3. 配置多數(shù)據(jù)源

    將之前的數(shù)據(jù)庫連接配置注釋掉,添加新的配置

    spring:
      # 配置數(shù)據(jù)源信息
      datasource:
        dynamic:
          # 設置默認的數(shù)據(jù)源或者數(shù)據(jù)源組,默認值即為master
          primary: master
          # 嚴格匹配數(shù)據(jù)源,默認false.true未匹配到指定數(shù)據(jù)源時拋異常,false使用默認數(shù)據(jù)源
          strict: false
          datasource:
            master:
              url: jdbc:mysql://localhost:3306/mybatisplus?characterEncoding=utf-8&useSSL=false
              driver-class-name: com.mysql.jdbc.Driver
              username: root
              password: root
            slave_1:
              url: jdbc:mysql://localhost:3306/mybatisplus_1?characterEncoding=utf-8&useSSL=false
              driver-class-name: com.mysql.jdbc.Driver
              username: root
              password: root
    
  4. 創(chuàng)建用戶Service

    在Service上添加注解@DS,指定數(shù)據(jù)源

    public interface UserService extends IService<User> {
    }
    
    @Service
    @DS("master")
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    }
    
  5. 創(chuàng)建商品Service

    public interface ProductService extends IService<Product> {
    }
    
    @DS("slave_1")
    @Service
    public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
    }
    
  6. 測試

     @Test
     public void testDynamicDataSource() {
    //       User user = userService.getById(1L);
         User user = userMapper.selectById(2L);
         System.out.println(user);
         System.out.println(productService.getById(1L));
     }
    

    注意:

    這里在測試時候遇到了一點bug,使用Mybatis-plus 3.5.2版本的時候,從數(shù)據(jù)庫查詢得到的枚舉不會轉(zhuǎn)換,一直報錯:

    org.springframework.jdbc.UncategorizedSQLException: Error attempting to get column 'sex' from result set. Cause: java.sql.SQLException: Error
    ; uncategorized SQLException; SQL state [null]; error code [0]; Error; nested exception is java.sql.SQLException: Error

    一直查找問題,最后查看了這個issue:https://github.com/baomidou/mybatis-plus/issues/4338,使用3.4.3,3.5.1,3.5.3版本都報錯,只有將mybatis-plus版本退回3.4.2才可以正常運行。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • MyBatis Plus 本文檔基于 MyBatis-Plus 官方文檔編寫,詳情請參見 MyBatis-Plus...
    青丶空閱讀 2,370評論 3 17
  • MyBatis-Plus視頻教程網(wǎng)站https://www.imooc.com/learn/1130以下是學習過程...
    日常麻花閱讀 812評論 0 0
  • MyBatis-Plus MyBatis-Plus是一個MyBatis的增強工具,在MyBatis的基礎上只做增強...
    禹王穆閱讀 965評論 0 0
  • Mybatis-Plus簡介 什么是Mybatis-Plus MyBatis-Plus(簡稱MP)是一個MyBat...
    深擁_66e2閱讀 545評論 0 0
  • MybatisPlus 特性 無侵入:只做增強不做改變,引入它不會對現(xiàn)有工程產(chǎn)生影響,如絲般順滑 損耗?。簡蛹磿?..
    njitzyd閱讀 676評論 0 0

友情鏈接更多精彩內(nèi)容