MyBatis-Plus學(xué)習(xí)筆記

1. 簡介

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

簡稱為MP承边,是一個Mybatis的增強(qiáng)工具阐污,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)浪谴,提高效率而生归露。

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

2. 特性

  • 無侵入

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

  • 損耗小

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

  • 強(qiáng)大的CRUD操作

    內(nèi)置通用Mapper,通用Service留储,僅僅通過少量配置即可實(shí)現(xiàn)單表大部分CRUD操作队丝,更有強(qiáng)大的條件構(gòu)造器,滿足各類使用需求

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

    通過Lambda表達(dá)式欲鹏,方便的編寫各類查詢條件,無需擔(dān)心字段寫錯

  • 支持主鍵自動生成

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

  • 支持ActiveRecord模式

    支持ActiveRecord形式調(diào)用,實(shí)體類只需繼承Model類即可進(jìn)行強(qiáng)大的CRUD操作

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

    支持全局通用方法注入

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

    采用代碼或者M(jìn)aven插件可快速生成Mapper胧弛,Model尤误,Service,Controller層代碼结缚,支持模板引擎损晤,更有超多自定義配置

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

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

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

    支持MySQL,Oracle茵宪,DB2最冰,H2,HSQL稀火,SQLite暖哨,Postgre,SQLServer等多種數(shù)據(jù)庫

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

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

  • 內(nèi)置全局?jǐn)r截插件

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

3. 快速開始

  1. 首先創(chuàng)建數(shù)據(jù)表团甲,假設(shè)有一張用戶表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,是因?yàn)閙ybatis默認(rèn)的主鍵是雪花算法計(jì)算出來的往枣,比較長超出范圍伐庭,需要用bigint

  2. 插入一些預(yù)置的數(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ū)的設(shè)置

  5. 添加實(shí)體類User

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

    這里的字段類型雕沉,盡量用包裝類集乔。

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

  6. 添加對應(yīng)UserMapper

    public interface UserMapper extends BaseMapper<User> {
    }
    

    這里UserMapper繼承了BaseMapper哩罪,這個BaseMapper是MybatisPlus提供的模板Mapper,其中包含了基本的CRUD方法巡验,泛型就是要操作的實(shí)體類型际插。

    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. 測試結(jié)果

    控制臺會輸出前面插入的數(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與學(xué)習(xí)。

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

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

  1. Insert

    /**
     * 插入一條記錄
     *
     * @param entity 實(shí)體對象
     */
    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默認(rèn)使用雪花算法生成的。

  2. Delete

    // 根據(jù) entity 條件猿挚,刪除記錄
    // wrapper: 實(shí)體對象封裝操作類咐旧,可以為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);
    }
    
    // 輸出結(jié)果
    ==>  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);
    }
    
    // 輸出結(jié)果
    ==>  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);
    }
    
    // 輸出結(jié)果
    ==>  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);
    }
    
    // 輸出結(jié)果
    ==>  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設(shè)置為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)坑涗浕襞埂W⒁猓?只返回第一個字段的值
    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);
    }
    
    // 輸出結(jié)果
    ==>  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);
    }
    // 輸出結(jié)果
    ==>  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);
    }
    // 輸出結(jié)果
    ==>  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接口,進(jìn)一步封裝CRUD采用get 查詢單行,remove 刪除,list 查詢集合,page 分頁前綴命名的方式區(qū)分Mapper層穿挨,避免混淆
  • 泛型T為任意實(shí)體對象
  • 建議如果存在自定義通用Service方法的可能月弛,請創(chuàng)建自己的IBaseService繼承MP提供的基類
  1. IService

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

    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 條件,更新記錄 需要設(shè)置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但壮,查詢一條記錄。結(jié)果集常侣,如果是多個會拋出異常蜡饵,隨機(jī)取一條加上限制條件 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接口和實(shí)現(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無法實(shí)行
        // 因此MP將批量插入放在了通用Service中實(shí)現(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 條件構(gòu)造器
在BaseMapper類中的方法,大多數(shù)方法中都有帶`Wrapper`類型的形參筷登,這個`Wrapper`就是條件構(gòu)造器剃根,能夠針對SQL語句設(shè)置不同的條件。**如果沒有條件前方,則可以將該形參賦值為null狈醉,即為查詢(刪除/修改)所有數(shù)據(jù)**

Wrapper的關(guān)系樹:
wrapper.jpg
  • Wrapper:條件構(gòu)造抽閑類,最頂端父類
    • 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<>();
          // 構(gòu)造的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);
      }
      // 輸出結(jié)果
      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<>();
          // 構(gòu)造的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);
      }
      // 輸出結(jié)果
      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<>();
          // 構(gòu)造的sql:DELETE FROM user WHERE (email IS NULL)
          wrapper.isNull("email");
          int result = userMapper.delete(wrapper);
          System.out.println("result = " + result);
      }
      // 輸出結(jié)果
      result = 0莺匠,因?yàn)闆]有用戶email是null的
      
    • 條件的優(yōu)先級

      @Test
      public void test04() {
          // 將(年齡大于20并且用戶名中包含a)或者郵箱為null的用戶信息修改
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構(gòu)造的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);
      }
      // 輸出結(jié)果
      update = 3
      
      @Test
      public void test05() {
          // 將用戶名中包含a并且(年齡大于20或者郵箱為null)的用戶信息修改
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構(gòu)造的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連接(默認(rèn)是使用and連接的)

    • 組裝select子句

      @Test
      public void test06() {
          // 查詢用戶信息的name金吗,age字段
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構(gòu)造的sql:SELECT name,age FROM user
          wrapper.select("name", "age");
          List<User> users = userMapper.selectList(wrapper);
          users.forEach(System.out::println);
      }
      // 輸出結(jié)果
      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)
      
    • 實(shí)現(xiàn)子查詢

      @Test
      public void test07() {
          // 查詢id小于等于3的用戶信息
          QueryWrapper<User> wrapper = new QueryWrapper<>();
          // 構(gòu)建的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);
      }
      // 輸出結(jié)果
      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)
      

      這里只是測試效果,實(shí)際應(yīng)該不會這樣select id

  2. LambdaQueryWrapper

    上面使用QueryWrapper中趣竣,我們查詢時使用的是數(shù)據(jù)庫列名字符串摇庙,容易出現(xiàn)寫錯或與類的屬性名不一致的導(dǎo)致寫錯,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

    前面的測試?yán)又幸灿杏肣ueryWrapper實(shí)現(xiàn)update的,使用UpdateWrapper可以使用set方法直接設(shè)置户秤,不需要創(chuàng)建User對象,如果創(chuàng)建了User對象码秉,并且設(shè)置屬性,傳遞給了update方法鸡号,那么設(shè)置列會加入到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

    將上面的測試?yán)佑肔ambda的方式來編寫

    @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. 總結(jié)

    1. QueryWrapperQueryChainWrapper只能指定需要的數(shù)據(jù)庫列名
    2. LambdaQueryWrapper鲸伴、LambdaQueryChainWrapper可以通過Lambda獲取數(shù)據(jù)庫列名
    3. QueryWrapper 府蔗、LambdaQueryWrapper不能使用鏈?zhǔn)讲樵兊姆绞剑仨毥柚鶥aseMapper來執(zhí)行
    4. QueryChainWrapper汞窗、LambdaQueryChainWrapper可以使用鏈?zhǔn)讲樵兊姆绞叫粘啵鏻ist(),one()
4.4 condition
在真正開發(fā)過程中,組裝條件時仲吏,有些數(shù)據(jù)是來源于用戶輸入的不铆,是可選的蝌焚,因此在組裝條件之前是需要先判斷條件是否成立的,成立才組裝條件到SQL執(zhí)行狂男。

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

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

@Test
public void test10() {
    // 定義查詢條件综看,有可能為null,假設(shè)輸入數(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è)輸入數(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進(jìn)行CRUD時红碑,并沒有指定要操作的表,只是在UserMapper接口繼承BaseMapper時泡垃,設(shè)置了泛型為User析珊,但實(shí)際操作的表是User表。

    因此可以得出結(jié)論蔑穴,MP在確定操作的表時忠寻,是由BaseMapper的泛型決定的,即實(shí)體類型決定存和,而且默認(rèn)操作的表名與實(shí)體類的類名一致奕剃。

    @TableName的作用是標(biāo)識實(shí)體類對應(yīng)的表,作用在實(shí)體類上捐腿。

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

    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
    

    解決方法:因?yàn)楫?dāng)前實(shí)體類為User茄袖,數(shù)據(jù)庫表為t_user,二者名字不匹配操软,因此報錯,可以在User類上添加注解@TableName("t_user")標(biāo)識User類與表t_user匹配

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

    上面我們只是改了一個表宪祥,如果所有的實(shí)體類對應(yīng)的表都有固定的前綴聂薪,比如t_,我們總不能手動一個個的添加@TableName注解吧蝗羊,那得累死

    為了解決這種情況藏澳,我們可以通過MP全局配置,為實(shí)體類所對應(yīng)的表名設(shè)置默認(rèn)的前綴:

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

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

    問題:如果實(shí)體類和表中表示主鍵的不是id凉驻,而是其他字段腻要,比如uid复罐,那MP還會自動識別uid為主鍵列嗎?

    測試:將實(shí)體類中屬性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屬性標(biāo)識為主鍵

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

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

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

    @TableId的type屬性

    常用的主鍵策略:

    • IdType.ASSIGN_ID(默認(rèn))

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

    • IdType.AUTO

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

    配置全局主鍵策略:

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

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

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

    • 如果實(shí)體類中屬性使用的是駝峰命名風(fēng)格,而表中字段使用的是下劃線命名風(fēng)格缩赛,比如實(shí)體類屬性為userName,表中字段為user_name.

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

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

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

  4. @TableLogic

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

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

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

6.插件

  1. 分頁插件

    MP自帶分頁插件,只需要簡單配置就可以實(shí)現(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ù)來確定數(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("當(dāng)前頁: " + 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中可以從里面進(jìn)行取值卓练,傳遞參數(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è)置分頁參數(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("當(dāng)前頁: " + 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

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

      • 取出記錄時襟企,獲取當(dāng)前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元蚪黑,導(dǎo)致老板虧錢

      新增一張商品表

      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("小李修改結(jié)果:" + ret1);
      
          // 4. 小王將價格減了30元,存入了數(shù)據(jù)庫
          p2.setPrice(p2.getPrice() - 30);
          int ret2 = productMapper.updateById(p2);
          System.out.println("小王修改結(jié)果:" + ret2);
      
          // 最后的結(jié)果
          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. 實(shí)體類字段加上@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("小李修改的結(jié)果:" + result);
      
          // 小王修改 - 30
          p2.setPrice(p2.getPrice() - 30);
          int ret = productMapper.updateById(p2);
          System.out.println("小王修改的結(jié)果:" + ret);
          if (ret == 0) {
              // 失敗重試忌穿,重新獲取version并更新
              p2 = productMapper.selectById(1L);
              p2.setPrice(p2.getPrice() - 30);
              ret = productMapper.updateById(p2);
          }
          System.out.println("小王修改重試的結(jié)果:" + ret);
      
          // 老板看價格
          Product p3 = productMapper.selectById(1L);
          System.out.println("老板看價格:" + p3.getPrice());
      }
      

7.通用枚舉

表中有些字段值是固定的抒寂,比如性別,只有男或女掠剑,此時我們可以用MP的通用枚舉來實(shí)現(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操作表的默認(rèn)前綴
          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);
        // 設(shè)置性別信息為枚舉項(xiàng)屈芜,會將@EnumValue注解所標(biāo)注的屬性值存儲到數(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è)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為master
          primary: master
          # 嚴(yán)格匹配數(shù)據(jù)源,默認(rèn)false.true未匹配到指定數(shù)據(jù)源時拋異常,false使用默認(rèn)數(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));
     }
    

    注意:

    這里在測試時候遇到了一點(diǎn)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才可以正常運(yùn)行印荔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市详羡,隨后出現(xiàn)的幾起案子妓美,更是在濱河造成了極大的恐慌军洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異甘有,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)亏镰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門潭陪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蟹漓,你說我怎么就攤上這事炕横。” “怎么了葡粒?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵份殿,是天一觀的道長。 經(jīng)常有香客問我嗽交,道長卿嘲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任夫壁,我火速辦了婚禮拾枣,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘盒让。我一直安慰自己梅肤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布邑茄。 她就那樣靜靜地躺著凭语,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撩扒。 梳的紋絲不亂的頭發(fā)上似扔,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機(jī)與錄音搓谆,去河邊找鬼炒辉。 笑死,一個胖子當(dāng)著我的面吹牛泉手,可吹牛的內(nèi)容都是我干的黔寇。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼斩萌,長吁一口氣:“原來是場噩夢啊……” “哼缝裤!你這毒婦竟也來了屏轰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤憋飞,失蹤者是張志新(化名)和其女友劉穎霎苗,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體榛做,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡唁盏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了检眯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片厘擂。...
    茶點(diǎn)故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锰瘸,靈堂內(nèi)的尸體忽然破棺而出刽严,到底是詐尸還是另有隱情,我是刑警寧澤避凝,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布港庄,位于F島的核電站,受9級特大地震影響恕曲,放射性物質(zhì)發(fā)生泄漏鹏氧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一佩谣、第九天 我趴在偏房一處隱蔽的房頂上張望把还。 院中可真熱鬧,春花似錦茸俭、人聲如沸吊履。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽艇炎。三九已至,卻和暖如春腾窝,著一層夾襖步出監(jiān)牢的瞬間缀踪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工虹脯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留驴娃,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓循集,卻偏偏與公主長得像唇敞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • MyBatis Plus 本文檔基于 MyBatis-Plus 官方文檔編寫疆柔,詳情請參見 MyBatis-Plus...
    青丶空閱讀 2,080評論 3 17
  • MyBatis-Plus視頻教程網(wǎng)站https://www.imooc.com/learn/1130以下是學(xué)習(xí)過程...
    日常麻花閱讀 738評論 0 0
  • MyBatis-Plus MyBatis-Plus是一個MyBatis的增強(qiáng)工具咒精,在MyBatis的基礎(chǔ)上只做增強(qiáng)...
    禹王穆閱讀 681評論 0 0
  • Mybatis-Plus簡介 什么是Mybatis-Plus MyBatis-Plus(簡稱MP)是一個MyBat...
    深擁_66e2閱讀 451評論 0 0
  • MybatisPlus 特性 無侵入:只做增強(qiáng)不做改變,引入它不會對現(xiàn)有工程產(chǎn)生影響旷档,如絲般順滑 損耗心P稹:啟動即會...
    njitzyd閱讀 580評論 0 0