【MyBatisPlus】一文带你快速上手MyBatisPlus

文章目录

  • MyBatisPlus学习笔记
    • 前言
    • 1、MyBatisPlus概述
    • 2、快速体验
    • 3、CRUD接口
      • 3.1 Mapper层CRUD接口
        • 3.1.1 Insert
        • 3.1.2 Delete
        • 3.1.3 Update
        • 3.1.4 Select
      • 3.2 Service层CRUD接口
        • 3.2.1 Save
        • 3.2.2 Remove
        • 3.2.3 Update
        • 3.2.4 Get
    • 3.3 自定义SQL接口
    • 4、常用注解和配置
      • 4.1 @TableId
      • 4.2 @TableField
      • 4.3 @TableLogic
    • 5、条件构造器和常用接口
      • 5.1 QueryWrapper
      • 5.2 UpdateWrapper
      • 5.3 使用Condition动态组装条件
      • 5.4 LamdaQueryWrapper
      • 5.5 LamdaUpdateWrapper
    • 6、插件
      • 6.1 MyBatisPlus分页插件
      • 6.2 悲观锁与乐观锁
      • 6.3 MyBatisX插件
    • 7、通用枚举
    • 8、代码生成器
      • 8.1 快速生成
      • 8.2 交互式生成
    • 9、多数据源

MyBatisPlus学习笔记

【MyBatisPlus】一文带你快速上手MyBatisPlus_第1张图片

前言

  本文是参考MyBatisPlus官网对MyBatisPlus的一个学习笔记,主要是对MyBatisPlus的一个简单的入门学习,大致对MyBatisPlus有一个整体认知,熟悉使用MyBatisPlus提供的各种API(比如MyBatisPlus提供的增删改查接口),以及各种便利的特性和插件(比如自动生成代码、MyBatisPlus分页插件)。当然主要目的是想利用费曼学习方法将学到的知识进行一个输出,这样能够加深我对知识有一个更加深刻的认知,因为这些知识在官方是都能够查看到的,我用自己的认知、自己的排版对这些知识进行重复输出。

1、MyBatisPlus概述

  • MyBatisPlus是什么

    MyBatisPlus(简称MP)是MyBatis的增强版,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。和MyBatis一样是一款数据访问层的框架,可以大大简化对SQL的操作

    • 官方文档地址:MyBatis-Plus

    • Gitee开源地址:mybatis-plus: mybatis

    • GitHub开源地址:baomidou/mybatis-plus

    • 创始人:青苗

  • MyBatisPlus的t特点

    • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
    • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 支持的数据库

    • 国外:MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift
    • 国内:达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库
  • 框架结构

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第2张图片

2、快速体验

搭建环境
编写配置文件
编写实体类
编写Mapper
编写测试类
扫描Mapper
  • Step1:搭建环境

    1)建表(使用的是MySQL)

    建立一张 user 表

    DROP TABLE IF EXISTS user;
    
    CREATE TABLE user
    (
        id BIGINT(20) NOT NULL COMMENT '主键ID',
        name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
        age INT(11) NULL DEFAULT NULL COMMENT '年龄',
        email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
        PRIMARY KEY (id)
    );
    

    往表中添加数据:

    DELETE FROM user;
    
    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)导入依赖

            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starterartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
            dependency>
    
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.5.2version>
            dependency>
    

    3)编写配置文件

    application.yml

    server:
      port: 8080
    
    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC
        username: root
        password: 32345678
        
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志
    
  • Step2:编写实体类

    @Data
    public class User {
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    
  • Step3:编写Mapper

    package com.hhxy.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.hhxy.entity.User;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        /*
        说明: BaseMapper 
        BaseMapper是MP封装了通用CRUD的接口,Mapper只需要集成BaseMapper
        T为任意实体类,只要让Mapper集成BaseMapper并且关联对应的实体类,就能使用Mapper对象操作实体类对应的表了
        */
    }
    
  • Step4:扫描Mapper

    在SpringBoot的启动类上添加**@MapperScan("com.hhxy.mapper")**注解

    PS:如果我们在Mapper接口上添加了@Mapper注解,则这个注解可以省略

  • Step5:编写测试类

    @SpringBootTest
    public class SampleTest {
    
        @Autowired
        private UserMapper userMapper;
    
        @Test
        public void testSelect() {
            System.out.println(("----- selectAll method test ------"));
            List<User> userList = userMapper.selectList(null);
            Assertions.assertEquals(5, userList.size());
            userList.forEach(System.out::println);
        }
        
    }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第3张图片

知识拓展:关于MP中的SQL是如何确定要操作的表是谁

我们在使用MP时,我们自己编写的Mapper必须继承BaseMapper,通过这一步,我们MP底层会自动将T映射为SQL操作表,举个例子吧:当我们的T是User时,那么SQL操作的表就是user

那如果数据库中的表是tb_user,如何确保SQL操作的表是tb_user呢?

  • 方案一:最直接的方法肯定是修改entity的格式,比如可以将entity修改成tb_user、TbUser、tbUser、Tb_User、Tb_User。

  • 方案二:通过@TableName注解映射表名,比如@TableName("tb_user")

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第4张图片

  • 方案二:通过编写配置文件,给user添加一个前缀

    mybatis-plus:
    global-config:
     db-config:
       table-prefix: tb_
    

    需要注意的是,通过配置文件是全局有效的,也就是所有的实体类都会增加一个前缀tb_

3、CRUD接口

MP在MyBatis的基础上做了增强,底层封装了大量通用的SQL,主要有BaseMapperIService两个CRUD接口,其中IService的实现类是ServiceImpl,BaseMapper中的方法以insertdeleteupdateselect开始,IService中的方法以saveremoveget开始

  • insert、delete、update的返回值都是int类型,返回值为0说明无数据更新,此时SQL执行失败
  • save、remove的返回值都是boolean类型,返回值为false说明SQL执行失败

3.1 Mapper层CRUD接口

参数说明

类型 参数名 描述
T entity 实体对象
Wrapper wrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
Collection idList 主键 ID 列表(不能为 null 以及 empty)
Serializable id 主键 ID(可以是任何实现了序列化接口的数据类型)
Map columnMap 表字段 map 对象
IPage page 分页查询条件(可以为 RowBounds.DEFAULT)

备注:基本数据类型的包装类都实现了序列化接口,String也实现了序列化接口

3.1.1 Insert

  • int insert(T entity):插入一条记录,返回值为更新数据的条数
    @Test
    public void testInsert(){
        User user = new User("张三", 23, "[email protected]");
//        INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
        int result = userMapper.insert(user);
        System.out.println("insert = " + result); // result = 1
        // MP很贴心,它会将Insert后自动生成的id,重新赋值给user对象(不用向MyBatis还要进行配置才能获取生成id)
        System.out.println("MP自动生成的id为: "+user.getId());
    }

备注:不止是BaseMapper的Insert方法能够通过getId直接获取自动生成的Id,IService的save方法也能够通过getId直接获取自动生成的Id。总的来讲MP真的简化了MyBatis,让开发变得更简单

3.1.2 Delete

  • int deleteById(Serializable id):根据id删除一条记录,返回值为更新数据的条数

  • int deleteById(T entity):根据entity的id删除一条记录,返回值为更新数据的条数

    注意事项:entity必须具有getId()这个方法,否则会报错

    PS:其实可以通过@TableId配置自定义的主键,详情键第四节【常用注解和配置】

  • int deleteByMap(Map columnMap):以map的value作为条件进行删除

    注意事项:map的key必须要与表中的字段进行对应(不区分大小写)

  • int deleteBachIds(Cellection idList):批量删除

  • int delete(Wrapper queryWrapper):条件删除

int result = 0;

// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(1613825708092678146L);

User user = new User(1L,"张三", 23, "[email protected]");
// DELETE FROM user WHERE id=?;
result = userMapper.deleteById(user);

Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 23);
// DELETE FROM user WHERE name=? AND age=?;
result = userMapper.deleteByMap(map);

List<Long> idList = Arrays.asList(1L, 2L, 3L);
// DELETE FROM user WHERE id IN (?, ?, ?);
result = userMapper.deleteBatchIds(idList);


3.1.3 Update

  • int updateById(T entity):根据id进行修改

    entity必须具有getId()这个方法,否则会报错

  • int update(T entity, Wrapper updateWrapper):根据条件进行修改

int result = 0;

User user = new User(1L,"张三", 23, "[email protected]");
// UPDATE user SET name=?, age=?, email=? WHERE id=?;
result = userMapper.updateById(user);

3.1.4 Select

  • T selectById(Serializable id):根据id进行查询

  • List selectBatchIds(Collection idList):批量查询

  • List selectByMap(Map columnMap):根据map的value进行条件查询

    注意事项:map的key必须要与表中的字段进行对应(不区分大小写)

  • List selectList(Wrapper queryWrapper):条件查询

// SELECT id,name,age,email FROM user WHERE id=?;
User user = userMapper.selectById(1L);

List<Long> idList = Arrays.asList(1l, 2l, 3l);
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? );
List<User> users = userMapper.selectBatchIds(idList);

Map<String, Object> map = new HashMap<>();
map.put("name", "张三");
map.put("age", 23);
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?;
List<User> users = userMapper.selectByMap(map);

// SELECT id,name,age,email FROM user;
List<User> users1 = userMapper.selectList(null);

3.2 Service层CRUD接口

类型 参数名 描述
int batchSize 插入批次数量
T entity 实体对象
Wrapper updateWrapper 实体对象封装操作类 UpdateWrapper
Collection entityList 实体对象集合
Collection idList idList
Function mapper 转换函数

3.2.1 Save

  • boolean save(T entity):新增一条记录

  • boolean saveBatch(Collection entityList):批量添加

    温馨提示

    1. 使用saveBatch,最好在数据库连接的url中添加一个rewriteBatchedStatements=true参数,实现高性能的批量插入

    2. 使用saveBatch,底层使用了事务,执行多条新增只会提交一次事务;但是如果在for循环中使用,会提交多次事务(不建议在循环中使用saveBatch方法)

    3. saveBatch(Collection entityList)底层就是调用saveBatch(Colection entityList, int batchSize),只是前一个他设置了默认值batchSize=1000,也就是一个批次会插入1000条,超过一千条需要等待下一批次执行

      【MyBatisPlus】一文带你快速上手MyBatisPlus_第5张图片

      备注:saveBatch底层是通过JDBC的executeBatch实现的。批次只针对增删改操作,它是指将执行SQL的语句的请求先存起来,等到达到一定数量,就当作一批发送给MySQL服务器,让MySQL服务器一次将这些SQL执行完,这样做可以避免频繁和MySQL数据库交互,造成效率低下

    参考文章:

    • MyBatis-plus批量写入数据方法saveBatch速度很慢原因排查
    • Mybatis-Plus批量插入的简单自测
    • Mybatis Plus saveBatch批量插入如何高效
    • Java-Mysql之批处理_veejaLiu的博客-CSDN博客
    • 什么是批处理?
  • boolean saveBatch(Colection entityList, int batchSize):批量添加

    当batchSize=1时,都是一条一条执行单一的insert语句

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第6张图片

    当batchSize>=list.size()时,底层通过executeBatch方法将list中的数据拼接成一条SQL,最终执行性一条SQL

    image-20230115093849845

  • boolean saveOrUpdate(T entity)

  • boolean saveOrUpdate(T entity, Wrapper updateWrapper)

  • boolean saveOrUpdateBatch(Collection entityList)

  • boolean saveOrUpdateBatch(CollectionentityList, int batchSize)

// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
User user = new User("张三", 22, "[email protected]");
boolean result = userService.save(user);

List<User> users = new ArrayList<>();
for (int i = 0; i < 3; i++) {
    User user = new User("张三", 18, "[email protected]");
    users.add(user);
}
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? ),( ?, ?, ?, ? ),( ?, ?, ?, ? );
boolean result = userService.saveBatch(users);

// 执行三次: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? );
boolean result = userService.saveBatch(users, 1);


3.2.2 Remove

  • boolean removeById(Serializable id):根据id删除数据
  • boolean removeById(T entity):根据id删除数据
  • boolean removeById(Serializable id, boolean useFill)
  • boolean removeBatchByIds(Collection list):更具id进行批量删除
  • boolean removeBatchByIds(Collection list, int batchSize)
  • boolean removeBatchByIds(Collection list, boolean useFill)
  • boolean removeBatchByIds(Collection list, int batchSize, boolean useFill)

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

3.2.4 Get

// 根据 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);
// 无条件分页查询
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);

3.3 自定义SQL接口

这个和MyBatis中是高度重合的,我就不在这里继续赘述了,如果想了解可以参考这篇文章:初识MyBatisPlus

MP充分践行了它的理念:只做增强,不做修改

4、常用注解和配置

此外还有@TableName注解比较常用,这个已经在前面学习过了

4.1 @TableId

  • @TableId:用于映射主键

    MP默认将id作为注解,如果数据库中主键非id,会报错。比如我们数据库中的注解为uid,实体类的字段也为uid,此时需要在实体类的uid上添加一个@TableId注解,告诉MP uid是主键

    • value属性

      前面是数据库的主键为uid,实体类中的属性也是uid。但如果数据库中主键是uid,实体类中的主键是id呢?此时就需要使用@TableId注解的value属性了。具体使用方式:

      给实体类的uid属性上添加@TableId(value = "uid"),只有value一个属性,还可以省略为@TableId(“uid”)

    • type属性

      IdType 说明
      AUTO 使用id自增策略(前提是数据库也要开启id自增)
      NONE 不手动设置主键值,MP将默认给出一个 Long 类型的字符串作为主键
      INPUT 手动设置主键值(不设置就没有)
      ASSIGN_ID 使用雪花算法生成主键值
      ASSIGN_UUID 使用uuid生成主键值

      MP的id生成策略默认是雪花算法,如果我们不想使用雪花算法,需要使用@TableId注解的type属性进行配置

      给实体类的id属性上添加@TableId(type = idType.AUTO)

      注意:手动设置id的优先级要高于MP的id生成策略

      知识拓展

      • 拓展一:通过配置文件配置主键生成策略

        mybatis-plus:
          global-config:
            db-config:
              id-type: auto # assign_id、assign_uuid、input、none
        
      • 拓展二:雪花算法

        简介:雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的 主键的有序性

        特点:全局唯一性(生成的id与当前时间相关)、递增性、高可用性(能够确保任何时候都能生成正确的id)、高性能(特别适合高并发场景)

        核心思想

        长度共64bit(一个long型)。

        首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负 数是1,所以id一般是正数,最高位是0。 41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。 10bit作为机器的ID(5个bit是数据中心,5bit的机器ID,可以部署在1024个节点)。 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)

        为了应对数据规模增长导致的压力,我们需要对数据库进行扩展。常见的扩展方式有:业务分库、主从复制、数据库分表

        【MyBatisPlus】一文带你快速上手MyBatisPlus_第7张图片

        优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高

        1)数据库分表

        将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务 继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据, 如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进 行拆分。

        单表数据拆分有两种方式:垂直分表水平分表。示意图如下:

        【MyBatisPlus】一文带你快速上手MyBatisPlus_第8张图片

        垂直分表:拆分字段。垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。 例如,前面示意图中的 nickname 和 description 字段,假设我们是一个婚恋网站,用户在筛选其他用 户的时候,主要是用 age 和 sex 两个字段进行查询,而 nickname 和 description 两个字段主要用于展 示,一般不会在业务查询中用到。description 本身又比较长,因此我们可以将这两个字段独立到另外 一张表中,这样在查询 age 和 sex 时,就能带来一定的性能提升。

        水平分表:拆分记录。水平分表适合表行数特别大的表,有的公司要求单表行数超过 5000 万就必须进行分表,这个数字可以 作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过 1000 万就要分表了;而对于一些简单的表,即使存储数据超过 1 亿行,也可以不分表。 但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性 能瓶颈或者隐患。 水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理

        体验分表:以主键id为例

        1)方式一:通过分段的方式分表。

        选取适当的分段范围,比如可以按照 1000000 的范围大小进行分段,1 ~ 999999 放到表 1中, 1000000 ~ 1999999 放到表2中,以此类推。

        复杂点:分段太小会导致切分后子表数量过多,增加维护复杂度;分段太大可能会 导致单表依然存在性能问题,一般建议分段大小在 100 万至 2000 万之间,具体需要根据业务选取合适 的分段大小。

        优点:可以随着数据的增加平滑地扩充新的表。例如,现在的用户是 100 万,如果增加到 1000 万, 只需要增加新的表就可以了,原有的数据不需要动

        缺点:分布不均匀。假如按照 1000 万来进行分表,有可能某个分段实际存储的数据量只有 1 条,而 另外一个分段实际存储的数据量有 1000 万条

        2)方式二:通过取模的方式分表。假如我们一开始就规划了 10 个数据库表,可以简单地用 user_id % 10 的值来 表示数据所属的数据库表编号,ID 为 985 的用户放到编号为 5 的子表中,ID 为 10086 的用户放到编号 为 6 的子表中。

        复杂点:初始表数量的确定。表数量太多维护比较麻烦,表数量太少又可能导致单表性能存在问题

        优点:表分布比较均匀

        缺点:扩充新的表很麻烦,所有数据都要重分布

4.2 @TableField

前面我们遇到主键不一致,我们可以使用@TableId注解进行映射,如果我们的其它属性和表的字段不一致,我们则需要使用@TableField注解进行映射

  • 情况一:MP自动映射驼峰和下划线

    若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格。例如实体类属性userName,表中字段user_name ,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格(这个在MyBatis中需要手动配置,而MP中则默认配置了)

  • 情况二:使用@TableField注解映射字段

    若实体类中的属性和表中的字段不满足情况一,例如实体类属性name,表中字段username,此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段

  • 情况三:使用@TableField注解自动填充

    在项目开发时,对于一些字段(比如:create_time、update_time……),每次执行添加、修改操作时,都需要去填充,如果使用注入的方式会显得比较麻烦,所以我们需要设置字段自动填充,当进行该操作时,就自动为这些字段进行赋值

    字段自动填充主要使用到了@TableField注解的fill属性,该属性有以下取值:

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第9张图片

    • Step1:环境搭建

      ……

    • Step2:创建实体类,并在实体类需要填充的字段添加上@TableField

          @TableField(fill = FieldFill.INSERT_UPDATE) // 当进行更新(插入、删除、修改)操作时,就会进行自动填充
          private LocalDateTime updateTime;
      
          @TableField(fill = FieldFill.INSERT) // 当进行插入操作时,就会进行自动填充
          private LocalDateTime createTime;
      
    • Step3:编写元数据处理器,用于设置填充的数据

      @Component
      public class MyMetaObjectHandler implements MetaObjectHandler {
          /**
           * 插入时填充字段
           * @param metaObject
           */
          @Override
          public void insertFill(MetaObject metaObject) {
              if (metaObject.hasSetter("createTime")){
                  metaObject.setValue("createTime", LocalDateTime.now());
                  /* this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); */ //也可以使用这个,这是官方给出的自动填充方式
              }
              if (metaObject.hasSetter("updateTime")){
                  metaObject.setValue("updateTime", LocalDateTime.now());
              }
          }
      
          /**
           * 更新时填充字段(包括修改、删除)
           * @param metaObject
           */
          @Override
          public void updateFill(MetaObject metaObject) {
              if(metaObject.hasSetter("updateTime")){
                  metaObject.setValue("updateTime", LocalDateTime.now());
              }
          }
      }
      

      备注:如果实体类中只存在updateTime一个属性时,会报错,必须要同时具有updateTime和createTime两个属性(在实体类中添加@TableField注解进行自动填充,SpringBoot会自动调用MyMetaObjectHandler中所有的方法)。为了解决这个问题,我们可以添加一个if判断,判断该类是否具有该属性,然后再进行赋值操作。

      注意事项

      1. 填充的数据类型要和实体类的数据类型保持一致,否则填充结果为null

      2. 注意区别官方给出的三个填充的方法

        // 这个是通用的,插入和更新都可以使用 但是当字段存在值 的时候不进行填充
        this.fillStrategy(metaObject, "createTime", LocalDateTime.now());
        // 这个是insert的时候用的,插入的时候时候强制进行填充
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        // update的时候使用,更新的时候强制进行填充
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); 
        

        备注:以上方法都需要MyBatisPlus3.3.0版本才能进行使用,值得一提的是fillStrategy再3.3.0版本中有bug,需要使用更高的版本(PS:反正我现在使用的是MP3.5问题不大)

4.3 @TableLogic

  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录
  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据

如果我们想要被删除的数据能够恢复,就需要使用到逻辑删除

示例

  • Step1:创建一个逻辑删除字段 is_deleted,同时设置默认值为0(0表示未删除,1表示删除)

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第10张图片

  • Step2:实体类中也添加一个逻辑删除的属性isDeleted

  • Step3:给实体类的逻辑删除属性上添加一个@TableLogic注解

  • Step4:测试

    int result = userMapper.deleteById(1613825708092678146L);
    

    可以看到此时,删除的SQL并不是以前的DELETE FROM user WHERE id=?;了,而是换成了UPDATE tb_user SET is_deleted=1 WHERE id=? AND is_deleted=0;

    要恢复逻辑删除的数据也能简单,直接在数据库中将is_deleted的值改成0就好了,需要注意的是逻辑删除后的数据,查询语句是无法查到的,同时update语句也是无法进行修改的

5、条件构造器和常用接口

【MyBatisPlus】一文带你快速上手MyBatisPlus_第11张图片

  • Wrapper : 条件构造抽象类,最顶端父类
    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : 查询条件封装
      • UpdateWrapper : Update 条件封装
      • AbstractLambdaWrapper : 使用Lambda 语法
        • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper

5.1 QueryWrapper

  • QueryWrapper

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第12张图片

    示例一:使用QueryMapper进行查询

        @Test
        public void test1() {
            // 查询姓张的,年龄在20~30之间,并且email字段不为空的用户信息
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like("name", "张")
                    .between("age", 20, 30)
                    .isNotNull("email");
            /*
            SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND
            (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
             */
            List<User> users = userMapper.selectList(queryWrapper);
            users.forEach(System.out::println);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第13张图片

    进行指定字段查询:

        @Test
        public void test6(){
            // 查询指定字段(用户名、年龄、邮箱)
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.select("name", "age", "email");
    //        SELECT name,age,email FROM user
            List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
            maps.forEach(System.out::println);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第14张图片

    使用QueryMapper实现子查询:

        @Test
        public void test7(){
            // 查询id小于100的用户信息
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.inSql("id","select id from user where id < 100");
    //        SELECT id,name,age,email,is_deleted FROM user WHERE (id IN (select id from user where id < 100))
            List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
            maps.forEach(System.out::println);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第15张图片

    示例二:使用QueryMapper进行条件删除

        @Test
        public void test2(){
            // 查询用户信息,按照年龄降序排序,如果年龄相同则按照id升序排序
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.orderByDesc("age")
                    .orderByAsc("id");
    //        SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 ORDER BY age DESC,id ASC
            List<User> users = userMapper.selectList(queryWrapper);
            users.forEach(System.out::println);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第16张图片

    示例三:使用QueryMapper进行条件修改

        @Test
        public void test4(){
            // 修改(年龄大于20并且姓张)或邮箱为null的用户信息
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.gt("age", 20)
                    .like("name", "张")
                    .or() // QueryMapper的连接条件默认是 AND
                    .isNull("email");
            User user = new User("张三", 22, "[email protected]");
    //        UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND name LIKE ? OR email IS NULL)
            userMapper.update(user, queryWrapper);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第17张图片

        @Test
        public void test5(){
            // 修改年龄大于20并且(姓张或邮箱为null)的用户信息
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.gt("age", 20)
                    .and(qw->qw.like("name", "张")
                            .or()
                            .isNull("email"));
            User user = new User("张三", 22, "[email protected]");
    //        UPDATE user SET name=?, age=?, email=? WHERE (age > ? AND (name LIKE ? OR email IS NULL))
            userMapper.update(user, queryWrapper);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第18张图片

5.2 UpdateWrapper

示例一

    @Test
    public void test8(){
        // 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper.like("name", "张")
                .and(uw -> uw.gt("age", 20)
                        .or()
                        .isNull("email"));
        updateWrapper.set("name", "李四").set("age", 22);
//        UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
        int result = userMapper.update(null, updateWrapper);
        System.out.println("result = " + result);
    }

【MyBatisPlus】一文带你快速上手MyBatisPlus_第19张图片

某个字段自增或自减,MyBatiPlusm目前只有两种方式实现

  • 使用UpdateWrapper拼接

    // 使用UpdateWrapper
    UpdateWrapper wrapper = new UpdateWrapper();
    wrapper.eq("id",【实体类对象】.getBid());
    wrapper.setSql("'自增字段' = '自增字段' + 1"); // 注意SQL中没有 += 这个符号
    【service对象】.update(wrapper);
    // 直接调用update方法
    【service对象】.update().
        setSql("stock = stock -1")
        .eq("voucher_id", voucherId)
        .update();
    
  • 直接写原生sql到xml中

    略……

方式三:自定义一个自增自减字段的方法,参考文章:mybatis-plus 自定义UpdateWrapper(一)实现列自增

        // 库存充足,秒杀券库存减一
        // 方式一:使用Service对象的update方法
        /*boolean flag = seckillVoucherService.update().
                setSql("stock = stock -1")
                .eq("voucher_id", voucherId)
                .update();*/
        // 方式二:使用LambdaUpdateWrapper的setSql方法
        UpdateWrapper updateWrapper = new UpdateWrapper();
        updateWrapper.eq("voucher_id", voucherId);
        updateWrapper.setSql("stock = stock -1");
        boolean flag = seckillVoucherService.update(updateWrapper);

        // 方式三:使用自定义的LambdaUpdateWrapper
        /*CustomLambdaUpdateWrapper updateWrapper = new CustomLambdaUpdateWrapper<>();
        updateWrapper.descField(SeckillVoucher::getStock, 1)
                .eq(SeckillVoucher::getVoucherId, voucherId);
        boolean flag = seckillVoucherService.update(updateWrapper);*/
        // 方式四:在xml中编写SQL语句

备注:相关完整代码请参考博主Gitee仓库中的hmdp项目

5.3 使用Condition动态组装条件

前面我们学习了QueryWrapper的常用方法,在这些方法的基础上,我们可以都可以通过添加一个condition条件,进行判断 是否需要添加改条件,这个类似于MyBatis中的动态SQL的标签,只有满足condition条件,才能够将改条件组装到SQL上

    @Test
    public void test9(){
        // 模拟从前端接收参数
        String username = "";
        Integer ageBegin = 20;
        Integer ageEnd = 30;
        // 模糊查询姓某某的人,同时20
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(username), "name", username)
                .ge(ageBegin != null, "age", ageBegin)
                .le(ageEnd != null, "age", ageEnd);
        // SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?)
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

5.4 LamdaQueryWrapper

前面我们使用QueryWrapper和UpdateWrapper时,字段都是自己来编写的,如果你尝试过就一定知道,自己通过字符串的形式编写是没有一点提示的,这样很容易出现字段名写错,导致SQL执行失败的情况。而MP的开发者也是知道这一点的,于是就提供了LamdaQueryWrapper,这样我们可以通过lamda表达式的形式确定字段名,这在IDEA中是有提示的,基本上可以杜绝字段写错的问题。这个完全可以替代QueryWrapper,前提是你的JDK必须大于1.8,因为Lamda表达式是JDK1.8引入的

创建条件构造器
添加条件
执行SQL
    @Test
    public void test10(){
        // 模拟从前端接收参数
        String username = "";
        Integer ageBegin = 20;
        Integer ageEnd = 30;
        // 模糊查询姓某某的人,同时20
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
                .ge(ageBegin != null, User::getAge, ageBegin)
                .le(ageEnd != null, User::getAge, ageEnd);
        // SELECT id,name,age,email,is_deleted FROM user WHERE (age >= ? AND age <= ?)
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        users.forEach(System.out::println);
    }

5.5 LamdaUpdateWrapper

同样的这个也可以完全替代UpdateWrapper,推荐使用这个

创建条件构造器
添加条件
执行SQL
    @Test
    public void test11(){
        // 修改 用户名中含有张并且(年龄大于20或者邮箱null)的用户信息
        LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        lambdaUpdateWrapper.like(User::getName, "张")
                .and(uw -> uw.gt(User::getAge, 20)
                        .or()
                        .isNull(User::getEmail));
        lambdaUpdateWrapper.set(User::getName, "李四").set(User::getAge, 22);
//        UPDATE user SET name=?,age=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
        int result = userMapper.update(null, lambdaUpdateWrapper);
        System.out.println("result = " + result);
    }

6、插件

分页插件和乐观锁插件都是内置再MP中的,只需要配置以下就能使用了,而MyBatisX插件需要到插件商店中进行下载

6.1 MyBatisPlus分页插件

  • Step1:搭建环境

    请参考第二节【快速体验】

  • Step2:创建MP配置类,配置拦截器

    @Configuration
    public class MyBatisPlusConfig {
        
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            // 1、创建MP拦截器对象
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            // 2、将MP的分页插件添加到拦截器对象中
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    }
    

    备注:如果没有加@Configuration注解,则需要在启动类上添加@MapperScan注解扫描该类

  • Step3:测试

        @Test
        public void testPage(){
            // 创建分页构造器
            Page<User> page = new Page<>(2,3);
            // 执行SQL:分页查询所有条件(还可以使用QueryWrapper对象进行条件过滤)
            userMapper.selectPage(page, null);
            
            // 这里和前面获取生成id是一样的,MP将查询的数据重新赋值给了Page对象
            System.out.println("当前页码 = " + page.getCurrent());
            System.out.println("当前页展示的记录条数 = " + page.getSize());
            System.out.println("当前页展示的记录: ");
            page.getRecords().forEach(System.out::println);
            System.out.println("分页查询的总页数 = " + page.getPages());
            System.out.println("分页查询的总记录数 = " + page.getTotal());
            System.out.println("是否存在上一页: " + page.hasPrevious());
            System.out.println("是否存在下一页: " + page.hasNext());
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第20张图片

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第21张图片

知识拓展:XML自定义分页

如果在我们像自定义一个分页查询的SQL,并且需要使用MP内置的分页插件

UserMapper:

 /**
     * 通过年龄查询用户信息并进行分页
     * @param page 如果想使用MP内置的分页插件,则分页对象必须放在第一个
     * @param age
     * @return
     */
    Page<User> selectPageByAge(@Param("page") Page<User> page, @Param("age") Integer age);
    <select id="selectPageByAge" resultType="User">
        select id, name, age, email
        from user
        where age > #{age}
    select>

备注:这里配置了实体类的别名

【MyBatisPlus】一文带你快速上手MyBatisPlus_第22张图片

测试结果:

【MyBatisPlus】一文带你快速上手MyBatisPlus_第23张图片

6.2 悲观锁与乐观锁

在学习悲观锁和乐观锁之前,我们先模拟一个场景:

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1 万多。

像上面那样,不加锁,就会导致操作并发时出现严重故障,所以我们对于这种事务性操作是一定要进行枷锁校验的,这样才能保证数据的一致性,不然系统的使用者蒙受巨大损失

  • 乐观锁:对数据的处理持乐观态度,乐观的认为数据一般情况下不会发生冲突,只有提交数据更新时,才会对数据是否冲突进行检测。乐观锁的实现不依靠数据库提供的锁机制,需要我们自已实现,实现方式一般是记录数据版本,一种是通过版本号,一种是通过时间戳。
  • 悲观锁:对于数据的处理持悲观态度,总认为会发生并发冲突,获取和修改数据时,别人会修改数据。所以在整个数据处理过程中,需要将数据锁定。通常依靠数据库提供的锁机制实现,比如mysql的排他锁,select … for update来实现悲观锁。需要注意的是使用悲观锁时,需要关闭数据库自动提交功能,即:set autocommit = 0;

如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元。

示例一

模拟数据冲突

数据冲突包括:丢失修改、不可重复读、脏读

  • Step1:环境搭建

    1)建表

    2)插入数据

    3)导入依赖

    4)编写配置文件

  • Step2:编码

    1)编写实体类

    2)编写Mapper

  • Step3:测试

        @Test
        public void testDataConflict(){
            // 小李查看价格
            Product p1 = productMapper.selectById(1L);
            System.out.println("小李取出的价格:" + p1.getPrice());
    
            // 小王查看价格
            Product p2 = productMapper.selectById(1L);
            System.out.println("小王取出的价格:" + p2.getPrice());
    
            // 小李将价格加了50元,存入了数据库
            p1.setPrice(p1.getPrice() + 50);
            int result1 = productMapper.updateById(p1);
            System.out.println("小李修改结果:" + result1);
    
            // 小王将商品减了30元,存入了数据库
            p2.setPrice(p2.getPrice() - 30);
            int result2 = productMapper.updateById(p2);
            System.out.println("小王修改结果:" + result2);
    
            // 获取商品最终的结果
            Product p3 = productMapper.selectById(1L);
            System.out.println("最后的结果:" + p3.getPrice());
        }
    
    【MyBatisPlus】一文带你快速上手MyBatisPlus_第24张图片

示例二

使用悲观锁解决数据冲突

  • Step1:搭建环境

    略……

  • Step2:给表添加一个version字段。

  • Step3:在编写实体类时,使用@Version注解标记version属性。取出记录时,获取当前version;更新时,version + 1,如果where语句中的version版本不对,则更新失败

  • Step4:编写配置类

    @Configuration
    public class MyBatisPlusConfig {
        
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            // 添加乐观锁插件
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return interceptor;
        }
        
    }
    
  • Step5:测试

    测试代码和示例一相同,略……

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第25张图片

    小李进行操作时,此时小王还没进行更新操作,version字段不变;当小王操作时,因为前面小李进行了更新操作,所以version+1了,和小王获取的version不同,更新操作失败!

示例三

使用悲观锁解决数据冲突

  • Step1:搭建环境

    略……

  • Step2

6.3 MyBatisX插件

MyBatisX插件主要具有以下功能

  1. 通过Mapper接口快速在Mapper配置文件中生成statement
  2. 快速生成代码

【MyBatisPlus】一文带你快速上手MyBatisPlus_第26张图片

【MyBatisPlus】一文带你快速上手MyBatisPlus_第27张图片

【MyBatisPlus】一文带你快速上手MyBatisPlus_第28张图片

【MyBatisPlus】一文带你快速上手MyBatisPlus_第29张图片

7、通用枚举

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举 来实现

  • Step1:搭建环境

    1)建表(将sex的类型设置为int)

    2)插入数据

    3)导入依赖

    4)编写配置文件

  • Step2:编码

    1)编写枚举类

    @Getter // 需要提供get方法,让外界能够获取到枚举对象
    public enum SexEnum {
    
        MALE(1, "男"),
        FEMALE(2, "女");
    
        @EnumValue // 将注解所标识的值存储到数据库中(不添加这个注解,则添加的值是MALE或FEMALE)
        private Integer sex;
        private String sexName;
    
        SexEnum(Integer sex, String sexName) {
            this.sex = sex;
            this.sexName = sexName;
        }
    
    }
    

    备注:MP3.5.2版本以前(3.5.2开始就已经废弃了,不需要配置就能直接使用通用没觉了),还需要再配置文件中进行配置,才能成功使用通用枚举

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第30张图片

    2)编写实体类

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

    3)编写Mapper

  • Step3:测试

        @Test
        public void testEnum(){
            User user = new User("张三", 23, "[email protected]", SexEnum.MALE);
            int result = userMapper.insert(user);
            System.out.println("result = " + result);
        }
    

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第31张图片

8、代码生成器

在MyBatis中我们可以通过逆向工程快速生成代码,从而节省大量的时间,但是MyBatis的逆向工程配置起来较为麻烦,所以MP简化了配置,让代码生成变得更加简单,堪称码农神器。让我们不必过多地重复哪些CRUD操作,只专注于核心业务。

代码生成器模板结构:

    FastAutoGenerator.create("url", "username", "password")
        //2、全局配置
        .globalConfig(...)
        //3、包配置
        .packageConfig(...)
        //4、策略配置
        .strategyConfig(...)
        //5、模板引擎配置
        .templateEngine(...)
        //6、执行
        .execute();

8.1 快速生成

示例

  • Step1:搭建环境

    1)建库、建表

    2)创建SpringBoot项目

    3)引入依赖

            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
    
            
            <dependency>
                <groupId>com.github.xiaoymingroupId>
                <artifactId>knife4j-spring-boot-starterartifactId>
                <version>3.0.3version>
            dependency>
    
            <dependency>
                <groupId>mysqlgroupId>
                <artifactId>mysql-connector-javaartifactId>
            dependency>
    
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.5.2version>
            dependency>
    
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-generatorartifactId>
                <version>3.5.2version>
            dependency>
    
            <dependency>
                <groupId>org.freemarkergroupId>
                <artifactId>freemarkerartifactId>
                <version>2.3.31version>
            dependency>
    

    4)编写配置文件

    # 配置端口
    server:
      port: 8080
    
    # Spring相关配置
    spring:
      # 数据源相关配置
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC
        username: root
        password: 32345678
    
    # MyBatisPlus相关配置
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启标准日志
      type-aliases-package: com.hhxy.entity # 配置类型别名
    
  • Step2:编写生成器

    注意:一定要使用绝对路径

    public class FastAutoGeneratorTest {
        public static void main(String[] args) {
            // 1、配置数据源
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis-plus_study?serverTimezone=UTC", "root", "32345678")
                    // 2、全局配置
                    .globalConfig(builder -> {
                        builder.author("ghp") // 设置作者
                                .enableSwagger() // 开启 swagger 模式
                                .fileOverride() // 覆盖已生成文件
                                .outputDir("/src/main/java"); // 指定输出目录
                    })
                    // 3、包配置
                    .packageConfig(builder -> {
                        builder.parent("com.hhxy") // 设置父包名
                                .moduleName("") // 设置父包模块名
                                .pathInfo(Collections.singletonMap(OutputFile.xml, "/src/main/resources/mapper")); // 设置mapperXml生成路径
                    })
                    // 4、策略配置
                    .strategyConfig(builder -> {
                        builder.addInclude("user") // 设置需要生成的表名(数据库中必须存在该表)
                                .addTablePrefix("tb_", "t_", "tbl_"); // 设置过滤表前缀
                    })
                    // 5、模板引擎配置
                    .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                    // 6、执行
                    .execute();
        }
    }
    
    

    备注:MyBatisPlus5.2开始,fileOverride方法被弃用了

  • Step3:运行方法

    【MyBatisPlus】一文带你快速上手MyBatisPlus_第32张图片

【MyBatisPlus】一文带你快速上手MyBatisPlus_第33张图片

使用MyBatisX插件生成自定的SQL

我们需要通过insertdeleteupdateselect等关键次开头,IDEA会有提示

【MyBatisPlus】一文带你快速上手MyBatisPlus_第34张图片

【MyBatisPlus】一文带你快速上手MyBatisPlus_第35张图片

8.2 交互式生成

public class InteractiveAutoGeneratorTest {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.println("=====================数据库配置=======================");
        System.out.println("请输入 URL");
        String url = scan.next();
        System.out.println("请输入 username");
        String username = scan.next();
        System.out.println("请输入 password");
        String password = scan.next();

        FastAutoGenerator.create(url, username, password)
                // 全局配置
                .globalConfig((scanner, builder) -> builder.author(scanner.apply("=====================全局配置=======================\n请输入作者名称"))
                        .outputDir(System.getProperty("user.dir") + "/src/main/java")
                        .commentDate("yyyy-MM-dd hh:mm:ss")
                        .dateType(DateType.TIME_PACK)
                        .enableSwagger()
                        .fileOverride()
                        .enableSwagger()
                        .disableOpenDir()
                )
                // 包配置
                .packageConfig((scanner, builder) -> builder.parent(scanner.apply("=====================包配置=======================\n请输入包名?"))
                        .moduleName(scanner.apply("请输入父包模块名?"))
                        .entity("entity")
                        .service("service")
                        .serviceImpl("serviceImpl")
                        .mapper("mapper")
                        .xml("mapper")
                        .other("utils")
                        .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir")+"/src/main/resources/mapper"))
                )
                // 策略配置
                .strategyConfig((scanner, builder) -> {
                    builder.addInclude(getTables(scanner.apply("=====================策略配置=======================\n请输入表名,多个参数使用逗号分隔")))
                            .serviceBuilder()
                            .formatServiceFileName("%sService")
                            .formatServiceImplFileName("%sServiceImpl")
                            .entityBuilder()        //实体类策略配置
                            .enableLombok()         //开启 Lombok
                            .disableSerialVersionUID()
                            .logicDeleteColumnName("deleted")        //逻辑删除字段
                            .naming(NamingStrategy.underline_to_camel)
                            .columnNaming(NamingStrategy.underline_to_camel)
                            .addTableFills(new Column("create_time", FieldFill.INSERT), new Column("modify_time", FieldFill.INSERT_UPDATE))
                            .enableTableFieldAnnotation()       // 开启生成实体时生成字段注解
                            .controllerBuilder()
                            .formatFileName("%sController")
                            .enableRestStyle()
                            .mapperBuilder()
                            .superClass(BaseMapper.class)
                            .formatMapperFileName("%sMapper")
                            .enableMapperAnnotation()       //@mapper
                            .formatXmlFileName("%sMapper");
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
}

9、多数据源

一个项目中经常会使用到多个数据库,比如主从复制,主库来进行更新操作,从库用来进行查询操作,从而有效降低数据库库的负担,一定程度提高系统的效率。

关于主从复制相关配置可以参考:MySQL学习笔记

多数据源主要存在以下三种配置方式:

# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:

主从复制相关配置:

spring:
# 配置数据源信息
  datasource:
    dynamic:
    # 设置默认的数据源或者数据源组, 默认值即为master
#    primary: master
    # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
#    strict: false
    datasource:
      names:
        master,slave # 设置别名,可以随便取,但要与后面的前缀对应(可以配置多个从库)
      master:
        url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456
      slave:
        url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
        driver-class-name: com.mysql.cj.jdbc.Driver
        username: root
        password: 123456

【MyBatisPlus】一文带你快速上手MyBatisPlus_第36张图片

@Service
@DS("master") // 使用主库
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("slave") // 使用从库
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

你可能感兴趣的:(#,Java,#,B_Framework,mybatis,java,spring)