MyBatisPlus 入门学习笔记(版本:3.4.3)

news/2024/7/7 7:57:00

文章目录

    • 学习辅助资料
  • MyBatisPlus概述
    • 1. MyBatisPlus是什么
    • 2. 特性
  • 快速开始
    • 1. 创建数据库 `mybatis_plus`
    • 2. 导入相关依赖
    • 3. 数据源配置
    • 3. 快速开始
      • 3.1 User实体类编写
      • 3.2 mapper编写
      • 3.3 启动类设置
      • 3.4 测试
  • 配置日志
  • Mapper层自带的的CRUD方法
    • 1. Insert插入操作
      • 1.1 产生奇妙ID的原因
      • [ @TableId](https://github.com/baomidou/mybatis-plus/blob/3.0/mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/TableId.java)
        • #](https://mybatis.plus/guide/annotation.html#idtype)[IdType
      • 1.2 主键自增方案
      • 1.3 拓展:分布式主键ID生成方案
        • 雪花算法
    • 2. Update更新操作
    • 3. 自动填充
      • 3.1 数据库级别填充(不推荐)
      • 3.2 代码级别填充(推荐)
    • 4. 乐观锁
      • 4.1 乐观锁机制
        • 4.1.1 配置插件
        • 4.1.2 在实体类的字段上加上`@Version`注解
        • 4.1.3 测试乐观锁
    • 5. Select查询操作
    • 6. 分页查询
      • 6.1 在配置类中添加PaginationInnerInterceptor
      • 6.2进行测试
    • 7. delete删除操作
      • 7.1 通过ID删除单个user
      • 7.2 使用collection删除多个user
      • 7.3 使用map按条件删除特定user
    • 8. 逻辑删除
      • 逻辑删除设置方法
  • 性能分析插件
  • 条件构造器Wrapper(重要)
    • 1. 查询多个符合条件的用户并返回List
    • 2. 查询一个数据
    • 3. 查询满足范围的用户
    • 4. 模糊查询
    • 5. 子查询
    • 6. 排序
  • 用IService和ServiceImpl快速构建Service层(重要)
    • 1. 构造UserService接口
    • 2. 构造UserService实现类UserServiceImpl
    • 3. 书写Controller层进行测试
  • 代码生成器
    • 步骤
      • 1. 添加依赖
      • 2. 创建测试用的数据库
      • 3. 书写代码生成器的运行代码
  • 总结

学习辅助资料

  • 视频链接 https://www.bilibili.com/video/BV17E411N7KN?from=search&seid=11697890445335742189
  • MyBatisPlus官方文档 https://mybatis.plus/guide/

只过了一年,很多教学视频和笔记里的有些方法和配置已经过时。多看官方文档,跟着官方文档敲代码,同时灵活应变才是应对快速迭代的框架的最佳方案。

MyBatisPlus概述

1. MyBatisPlus是什么

MyBatisPlus可以节省大量工作时间,所有的CRUD代码可以通过自动化完成。

2. 特性

来自官网:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

快速开始

官方文档地址:https://mybatis.plus/guide/quick-start.html#%E5%88%9D%E5%A7%8B%E5%8C%96%E5%B7%A5%E7%A8%8B

1. 创建数据库 mybatis_plus

使用的user表以及对应的字段如下:

idnameageemail
1Jone18test1@baomidou.com
2Jack20test2@baomidou.com
3Tom28test3@baomidou.com
4Sandy21test4@baomidou.com
5Billie24test5@baomidou.com
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)
);
-- 真实开发中,version(乐观锁)、deleted(逻辑删除)、gmt_create、gmt_modified
DELETE FROM user;

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

编写项目,初始化项目!

用IDE快速创建一个springboot项目后,进行以下步骤。

2. 导入相关依赖

pom.xml中引入 spring-boot-starterspring-boot-starter-testmybatis-plus-boot-starter和数据库连接依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-test</artifactId>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>mybatis-plus-boot-starter</artifactId>
  <version>3.4.3</version>
</dependency>

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</dependency>

使用mybatis-plus可以节省大量的代码,尽量不要同时导入mybatis和mybatis-plus,以免版本冲突。因为MyBatisPlus的starter会自动导入MyBatis。MySQL依赖的版本根据自己的MySQL版本进行修改。

3. 数据源配置

老生常谈了。本文的配置全部在application.yml中进行配置,因为配置的更有结构性。在application.properties也可。

# DataSource Config
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
    # 如果使用 MySQL8 驱动不同,url增加时区的配置 serverTimezone=GMT%2B8

3. 快速开始

传统方式: pojo-dao(连接MyBatis,配置mapper.xml文件)-service-controller

使用MyBatisPlus后,不用书写mapper便可直接进行CRUD的操作。

3.1 User实体类编写

建立了User类,并使用lombok插件进行快速建立方法和构造器。使用lombok前需要引入相关依赖。lombok入门资料:lombok实战指南。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /**
    * 主键ID
    */
    private Long id;
    /**
    * 姓名
    */
    private String name;
    /**
    * 年龄
    */
    private Integer age;
    /**
    * 邮箱
    */
    private String email;
}

3.2 mapper编写

直接继承MyBatisPlus内的BaseMapper即可,里面自带了大量的CRUD接口。

// 在对应的Mapper上面实现基本的接口 BaseMapper
@Repository // 持久层
public interface UserMapper extends BaseMapper<User> {
    // 所有的CRUD操作已经编写完成
    // 不需要像以前的配置一大堆文件
}

3.3 启动类设置

// 扫描mapper文件夹,如果mapper上使用@mapper注解,则可以不用写此注解
@MapperScan("com.sjh.mybatis_plus.mapper")
@SpringBootApplication
public class MybatisPlusApplication {

    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }

}

另外提一嘴,使用了@mapper注解进行注解Mapper后,不需要写@Repository也能被Spring IOC容器进行管理。

3.4 测试

直接使用从BaseMapper继承过来的基础CRUD方法,selectList可以获取数据库里的对象列表。

但是需要写入一个参数,参数类型为Wrapper,为条件构造抽象类。当值为null时,没有添加额外条件,即查询所有的用户。Wrapper在文章末尾有详细说明。

代码如下:

@Test
void selectList() {
  List<User> users = userMapper.selectList(null);
  users.forEach(System.out::println);
}

测试结果:

震惊,没有写任何的sql!MyBatisPlus帮忙写好了SQL语句和方法。

配置日志

所有的sql是不可见的,为了了解执行的sql,因此需要搭配日志。

日志在配置文件里进行配置,配置如下:

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

重新运行测试方法,运行结果如下,SQL语句,数据源等详细信息都有展示

配置完日志后,自己观察自动生成的SQL,对后面的学习有很大的帮助。

Mapper层自带的的CRUD方法

最详细的CRUD方法在官方文档 https://mybatis.plus/guide/crud-interface.html

1. Insert插入操作

书写User插入测试代码,如下所示:

@Test
void insertTest() {
  User user = new User();
  user.setName("sjh");
  user.setAge(1);
  user.setEmail("sjh@163.com");

  int insert = userMapper.insert(user); // 自动生成了id
  System.out.println("insert = " + insert); // 受影响的行数
  System.out.println("user = " + user);
}

运行结果如下,可以发现,没有设置id的时候,insert方法自动生成了id

原因:使用插入方法插入的id的默认值为全局的唯一ID

1.1 产生奇妙ID的原因

在主键上使用不同的注解,将会产生不同的ID。如果没有添加注解且主键为Number子类,默认自动生成雪花算法计算得到的ID。因此在主键属性上添加注解至关重要。添加的注解名称为@TableId,添加内容如下。

@TableId

  • 描述:主键注解
属性类型必须指定默认值描述
valueString“”主键字段名
typeEnumIdType.NONE主键类型

#IdType

描述
AUTO数据库ID自增
NONE无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUTinsert前自行set主键值
ASSIGN_ID分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认default方法)
ID_WORKER分布式全局唯一ID 长整型类型(please use ASSIGN_ID)
UUID32位UUID字符串(please use ASSIGN_UUID)
ID_WORKER_STR分布式全局唯一ID 字符串类型(please use ASSIGN_ID)

默认生成的方式为NONE,当插入对象ID为空且类型为Number,使用雪花算法自动补充。

@Getter
public enum IdType {
    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string),
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4);

    private final int key;

    IdType(int key) {
        this.key = key;
    }
}

1.2 主键自增方案

  1. 实体类字段加上@TableId注解
@TableId(type = IdType.AUTO)
private Long id;
  1. 数据库设置为主键自增

    删除原本字段后,修改自动递增数为正常值,原本的自动递增数如下图所示。将自动递增修改为正常值(设置还原为了6)。

  1. 运行测试方法,可得到恢复正常自增ID

到此,主键自增方案设置成功。

1.3 拓展:分布式主键ID生成方案

分布式系统唯一ID生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake。雪花算法支持的TPS可以达到419万左右(2^22*1000)。

2. Update更新操作

更新测试代码如下:

@Test
void updateTest() {
  User user = new User();
  user.setId(6L);
  user.setName("John");

  // 传入一个对象进行更新
  int i = userMapper.updateById(user);
  System.out.println("i = " + i);
}

运行结果:

**注意:**虽然方法是updateById,但是传入的参数是一个对象

3. 自动填充

创建时间修改时间等操作一般都是自动化完成的,并不希望手动更新。

阿里巴巴开发手册里说明,几乎所有的数据库表都得有字段:gmt_create和gmt_modified。并且需要自动化,即数据的创建时间和最后更新时间都得由程序来记录而不是人工设置。

3.1 数据库级别填充(不推荐)

  1. 在表中新增字段 create_timeupdate_time

    在Navicat中设置如下,类型和默认值都需要设置,将create_time和update_time的默认值设置为时间戳更新。

  2. 再次测试插入方法,同时实体类同步

    private Date createTime;
    private Date updateTime;
    
  3. 重新运行更新方法,在数据表中即可看到创建时间和更新时间生成

**不推荐原因:**数据库并没有资格随便更改。

3.2 代码级别填充(推荐)

  1. 删除数据库的默认值

  2. 在实体类字段属性上需要添加注释@TableField

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    
  3. 自定义实现类MyMetaObjectHandler

    @Slf4j
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        // 插入时的填充策略
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("start insert fill ....");
            this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        }
    
        // 更新时的填充策略
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("start update fill ....");
            this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
        }
    }
    

    **注意:**MySQL不支持LocalDateTime转换,跟着开发文档里的LocalDateTime来没有效果。使用Date对象是最保险方法。

  4. 如果需要从蛇形命名映射为驼峰式命名,配置里进行配置。(默认开启,不配置也行)

    mybatis-plus:
      configuration:
        map-underscore-to-camel-case: true
    

设置完后,创建时间和更新时间都可以自动生成,而不必修改数据库表设计。

4. 乐观锁

在面试过程中,经常被问到乐观锁和悲观锁。乐观锁和悲观锁在多线程中十分重要。

乐观锁:顾名思义十分乐观,它总是认为不会出现问题,无论干什么都不会上锁。如果出现问题就再次更新值测试。(版本号version)

悲观锁:顾名思义十分悲观,它总是认为总会出现问题,无论干什么都会上锁后再去操作。

4.1 乐观锁机制

当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:

  • 取出记录时,获取当前version

  • 更新时,带上这个version

  • 执行更新时, set version = newVersion where version = oldVersion

  • 如果version不对,就更新失败

    例:先查询获得版本号并更新版本号

    -- A
    update user set name = "sjh", version = version + 1
    where id = 2 and version = 1
                    
    -- B 如果该线程抢先完成,这个时候version = 2,会导致A修改失败
    update user set name = "sjh", version = version + 1
    where id = 2 and version = 1
    

前置操作:

数据库添加version字段,默认值为1**(记得数据库修改时,相应的实体类也得进行修改!!)**

乐观锁配置需要两步

4.1.1 配置插件

创建config包,编写配置类。插件配置后记得写@Bean,将插件交给IOC容器进行托管。

@Configuration
@MapperScan("com.sjh.mybatis_plus.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

4.1.2 在实体类的字段上加上@Version注解

@Version
private Integer version;

说明:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!

4.1.3 测试乐观锁

乐观锁正常运行情况

测试代码:

@Test
void optimisticLockerTest() {
  // 1. 查询用户信息
  User user = userMapper.selectById(1L);
  // 2. 修改用户信息
  user.setName("test01");
  user.setEmail("test01@163.com");
  // 3. 执行更新操作
  userMapper.updateById(user);
}

测试结果可得,数据成功被更改的同时,version自增1

查看日志,可以看到使用的SQL语句如下:

乐观锁运行失败情况

测试代码:

@Test
void optimisticLockerTest2() {
  // 线程1
  User user = userMapper.selectById(1L);
  user.setName("test02");
  user.setEmail("test02@163.com");

  // 模拟线程2插队
  User user2 = userMapper.selectById(1L);
  user2.setName("test0222222");
  user2.setEmail("test0222222@163.com");
  userMapper.updateById(user2);

  // 如果没有乐观锁就会覆盖插队线程的值
  userMapper.updateById(user);
}

测试结果可得,user因为version不匹配,无法更新。

5. Select查询操作

// 测试查询
@Test
void selectById() {
  User user = userMapper.selectById(1L);
  System.out.println("user = " + user);
}

// 测试批量查询
@Test
void selectByBatchId() {
  List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
  users.forEach(System.out::println);
}

// 按条件查询之使用map操作
@Test
void selectByMap() {
  HashMap<String, Object> stringObjectHashMap = new HashMap<>();
  // 自定义查询条件
  stringObjectHashMap.put("name", "test0222222");
  List<User> users = userMapper.selectByMap(stringObjectHashMap);
  users.forEach(System.out::println);
}

6. 分页查询

常用分页方法:

  1. 原始的limit进行分页
  2. pageHelper等第三方插件
  3. MyBatisPlus也有相应的配置插件

下文使用MyBatisPlus的分页插件进行分页查询。

具体步骤:

6.1 在配置类中添加PaginationInnerInterceptor

@Configuration
@MapperScan("com.sjh.mybatis_plus.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

注意:有些网上的方法是将MybatisPlusInterceptorOptimisticLockerInnerInterceptor分开单独放在IOC容器中,增加可读性。实际测试表明,在这个版本的MyBatisPlus下并不能正常运行。

6.2进行测试

测试代码:

@Test
void pageTest() {
  // 参数1:当前页;参数2:页面大小
  Page<User> userPage = new Page<>(2, 5);
  // 参数1:page对象;参数2:wrapper
  userMapper.selectPage(userPage, null);

  userPage.getRecords().forEach(System.out::println);
}

运行的SQL语句和结果。

7. delete删除操作

7.1 通过ID删除单个user

@Test
void deleteById() {
  int i = userMapper.deleteById(3L);
  System.out.println("i = " + i);
}

直接删除一个id对应的User,并且返回受删除SQL影响的行数。

7.2 使用collection删除多个user

@Test
void deleteBatchIds() {
  int i = userMapper.deleteBatchIds(Arrays.asList(4, 5, 7));
  System.out.println("i = " + i);
}

运行结果:

7.3 使用map按条件删除特定user

@Test
void deleteByMap() {
  HashMap<String, Object> stringObjectHashMap = new HashMap<>();
  stringObjectHashMap.put("name", "sfds");
  int i = userMapper.deleteByMap(stringObjectHashMap);
  System.out.println("i = " + i);
}

8. 逻辑删除

官方文档:https://mybatis.plus/guide/logic-delete.html

物理删除:从数据库中直接移除

逻辑移除:在数据库汇总没有被移除,而是通过一个变量来让它失效。deleted = 0 -> deleted = 1

管理员可以查看被删除的记录,防止数据的丢失,类似于回收站。

逻辑删除设置方法

  1. 在数据库中添加一个deleted字段,默认值为0

  2. 在配置文件中进行配置逻辑删除,但是新版本的MyBatisPlus默认配置了,所以这个步骤不用配置也可以。

    mybatis-plus:
      global-config:
        db-config:
          logic-delete-field: flag  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
          logic-delete-value: 1 # 逻辑已删除值(默认为 1)
          logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
    
  3. 实体类中增加deleted字段,并增加注解@TableLogic

    @TableLogic
    private Integer deleted;
    
  4. 测试类进行测试

    测试代码

    @Test
    void deleteById() {
      int i = userMapper.deleteById(1L);
      System.out.println("i = " + i);
    }
    

    测试结果如下,逻辑删除本质是update操作

    并且可以观察得到,指定id删除后的user的deleted字段变为了1,标记成了被删除。

性能分析插件

在平时的开发中,会遇到一些慢SQL。慢SQL极大程度影响了运行速度。

MyBatisPlus拥有SQL分析与打印的拓展,依赖 p6spy 组件,完美的输出打印 SQL 及执行时长 3.1.0 以上版本。

步骤:

  1. 引入p6spy依赖

    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
    
  2. application.yml 配置,修改driverClass和URL

    # DataSource Config
    spring:
      datasource:
        driver-class-name: com.p6spy.engine.spy.P6SpyDriver
        url: jdbc:p6spy:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8
     
    
  3. 在resources文件夹中添加spy.properties,配置内容如下

    #3.2.1以上使用
    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    #3.2.1以下使用或者不配置
    #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
    # 自定义日志打印
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    #日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    # 使用日志系统记录 sql
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    # 设置 p6spy driver 代理
    deregisterdrivers=true
    # 取消JDBC URL前缀
    useprefix=true
    # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
    excludecategories=info,debug,result,commit,resultset
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    # 实际驱动可多个
    #driverlist=org.h2.Driver
    # 是否开启慢SQL记录
    outagedetection=true
    # 慢SQL记录标准 2 秒
    outagedetectioninterval=2
    
  4. 测试

    使用selectById方法进行测试,可以看到console里面新增了SQL的运行时间记录。如果超过了慢SQL记录标准,运行时将会报错。

条件构造器Wrapper(重要)

官方文档:https://mybatis.plus/guide/wrapper.html#abstractwrapper

当书写复杂SQL时,可以使用wrapper进行书写并运行。

测试数据表如下:

1. 查询多个符合条件的用户并返回List

测试代码

// 查询name不为空的用户并且邮箱不为空的用户,年龄大于12岁
@Test
void selectList() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper
    .isNotNull("name")
    .isNotNull("email")
    .ge("age", 12);
  List<User> users = userMapper.selectList(userQueryWrapper);
  users.forEach(System.out::println);
}

console日志打印如下

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name IS NOT NULL AND email IS NOT NULL AND age >= ?)
==> Parameters: 12(Integer)
 Consume Time:8 ms 2021-07-08 16:10:47
 Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name IS NOT NULL AND email IS NOT NULL AND age >= 12)

<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 2, test0222222, 20, test0222222@163.com, 3, 0, null, 2021-07-08 10:09:29.0
<==        Row: 10, Jack, 18, jack@qq.com, 1, 0, 2021-07-08 16:05:07.0, null
<==        Row: 11, Bobby, 14, bobby@aliyun.com, 1, 0, 2021-07-08 16:05:57.0, null
<==      Total: 3

可以看到,需要符合条件的user才能被查询出来。被逻辑删除的数据尽管符合条件,但也不会被查询出来。

2. 查询一个数据

// 查询用户名为Maria的用户
@Test
void test02() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper.eq("name", "Maria");
  System.out.println(userMapper.selectOne(userQueryWrapper));
}

运行结果日志

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name = ?)
==> Parameters: Maria(String)
 Consume Time:5 ms 2021-07-08 17:16:04
 Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name = 'Maria')

<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 12, Maria, 22, null, 1, 0, 2021-07-08 16:09:16.0, null
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5583098b]
User(id=12, name=Maria, age=22, email=null, version=1, deleted=0, createTime=Thu Jul 08 16:09:16 CST 2021, updateTime=null)

3. 查询满足范围的用户

// 查询年龄在20-30岁之间的用户数量
@Test
void test03() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper.between("age", 20, 30);
  Integer count = userMapper.selectCount(userQueryWrapper);
  System.out.println("count = " + count);
}

运行结果日志

==>  Preparing: SELECT COUNT( * ) FROM user WHERE deleted=0 AND (age BETWEEN ? AND ?)
==> Parameters: 20(Integer), 30(Integer)
 Consume Time:5 ms 2021-07-08 17:13:42
 Execute SQL:SELECT COUNT( * ) FROM user WHERE deleted=0 AND (age BETWEEN 20 AND 30)

<==    Columns: COUNT( * )
<==        Row: 2
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4e83a98]
count = 2

4. 模糊查询

// 查询名字里不含"e",邮箱后缀为"@qq.com"的用户
@Test
void test04() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper
    .notLike("name", "e")
    .likeLeft("email", "@qq.com");
  List<Map<String, Object>> maps = userMapper.selectMaps(userQueryWrapper);
  maps.forEach(System.out::println);
}

注意,likeLeft表示的是左边模糊右边确认。看下面的源码便可明白。

运行结果日志:

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name NOT LIKE ? AND email LIKE ?)
==> Parameters: %e%(String), %@qq.com(String)
 Consume Time:6 ms 2021-07-08 17:36:23
 Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (name NOT LIKE '%e%' AND email LIKE '%@qq.com')

<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 10, Jack, 18, jack@qq.com, 1, 0, 2021-07-08 16:05:07.0, null
<==      Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@56f730b2]
{deleted=0, create_time=2021-07-08 16:05:07.0, name=Jack, id=10, version=1, age=18, email=jack@qq.com}

5. 子查询

@Test
void test05() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper.inSql("id", "select id from user where id < 11");
  List<User> users = userMapper.selectList(userQueryWrapper);
  users.forEach(System.out::println);
}

运行结果日志:

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (id IN (select id from user where id < 11))
==> Parameters: 
 Consume Time:4 ms 2021-07-08 17:46:35
 Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (id IN (select id from user where id < 11))

<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 2, test0222222, 20, test0222222@163.com, 3, 0, null, 2021-07-08 10:09:29.0
<==        Row: 6, John222, 1, sjh@163.com, 1, 0, 2021-07-07 21:35:04.0, 2021-07-08 10:24:26.0
<==        Row: 10, Jack, 18, jack@qq.com, 1, 0, 2021-07-08 16:05:07.0, null
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7ce85af2]
User(id=2, name=test0222222, age=20, email=test0222222@163.com, version=3, deleted=0, createTime=null, updateTime=Thu Jul 08 10:09:29 CST 2021)
User(id=6, name=John222, age=1, email=sjh@163.com, version=1, deleted=0, createTime=Wed Jul 07 21:35:04 CST 2021, updateTime=Thu Jul 08 10:24:26 CST 2021)
User(id=10, name=Jack, age=18, email=jack@qq.com, version=1, deleted=0, createTime=Thu Jul 08 16:05:07 CST 2021, updateTime=null)

6. 排序

@Test
void test06() {
  QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
  userQueryWrapper.orderByDesc("id");
  List<User> users = userMapper.selectList(userQueryWrapper);
  users.forEach(System.out::println);
}

运行结果日志:

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 ORDER BY id DESC
==> Parameters: 
 Consume Time:6 ms 2021-07-09 00:28:11
 Execute SQL:SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 ORDER BY id DESC

<==    Columns: id, name, age, email, version, deleted, create_time, update_time
<==        Row: 12, Maria, 22, null, 1, 0, 2021-07-08 16:09:16.0, null
<==        Row: 11, Bobby, 14, bobby@aliyun.com, 1, 0, 2021-07-08 16:05:57.0, null
<==        Row: 10, Jack, 18, jack@qq.com, 1, 0, 2021-07-08 16:05:07.0, null
<==        Row: 6, John222, 1, sjh@163.com, 1, 0, 2021-07-07 21:35:04.0, 2021-07-08 10:24:26.0
<==        Row: 2, test0222222, 20, test0222222@163.com, 3, 0, null, 2021-07-08 10:09:29.0
<==      Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2a39aa2b]
User(id=12, name=Maria, age=22, email=null, version=1, deleted=0, createTime=Thu Jul 08 16:09:16 CST 2021, updateTime=null)
User(id=11, name=Bobby, age=14, email=bobby@aliyun.com, version=1, deleted=0, createTime=Thu Jul 08 16:05:57 CST 2021, updateTime=null)
User(id=10, name=Jack, age=18, email=jack@qq.com, version=1, deleted=0, createTime=Thu Jul 08 16:05:07 CST 2021, updateTime=null)
User(id=6, name=John222, age=1, email=sjh@163.com, version=1, deleted=0, createTime=Wed Jul 07 21:35:04 CST 2021, updateTime=Thu Jul 08 10:24:26 CST 2021)
User(id=2, name=test0222222, age=20, email=test0222222@163.com, version=3, deleted=0, createTime=null, updateTime=Thu Jul 08 10:09:29 CST 2021)

用IService和ServiceImpl快速构建Service层(重要)

上面只讲述了mapper层面的方法不用书写,但是按照现在流行软件架构,还有Service层和Controller层的代码需要书写。

Controller层的代码肯定是得自己根据业务需求来书写了,不用想。

而Service层,MyBatisPlus封装了一系列的方法,不用自己手写。下文将会实战几种常用的Service层方法。完整的Service CRUD接口请查看官方文档 https://mybatis.plus/guide/crud-interface.html。

下面为Service CRUD接口的说明:

说明:

  • 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,
  • 泛型 T 为任意实体对象
  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类
  • 对象 Wrapper 为 条件构造器

Wrapper在这里又出现了,先不管,文章的后面会详细讲述Wrapper的使用方法。

上文已经构造好了UserMapper,下面继续构造Service层和Controller层,通过网页来查看Service层的效果。

1. 构造UserService接口

创建service包后创建UserService接口,然后该接口继承IService接口,结束。简单吧,就是这么简单,基础CRUD的方法都被封装到了IService中。

public interface UserService extends IService<User> {
}

需要说明的是,IService必须指明泛型,泛型为要操作的实体类。

2. 构造UserService实现类UserServiceImpl

UserServiceImpl需要实现UserService接口,这是常规操作。但是稍微想想都知道,完成这步后,还需要自己实现接口里的方法,这对于高效率(爱偷懒)的程序员是不友好的。

MyBatisPlus当然想到了这个问题,所以设计了IService的实现类ServiceImplUserServiceImpl继承了ServiceImpl后,便继承了大量的基础CRUD方法,就不需要自己写常规的Service层的CRUD代码了。

UserServiceImpl代码如下:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

一样的,ServiceImpl也需要输入泛型。第一个参数为对应的Mapper层接口,第二个参数为要操作的实体类。

代码书写后,查看UserServiceImpl的结构,可以看到来自ServiceImpl的CRUD方法都被顺利继承。

3. 书写Controller层进行测试

其实Service层方法直接在测试类进行测试即可,但是写Controller的原因在于,从浏览器直接一条龙到数据库比较有感觉,也顺便当做练练手。

  1. 创建controller包后,创建UserController类,详细代码如下,默认看这个资料的学过SpringMVC。

    UserController代码如下;

    @Controller
    public class UserController {
    
        @Autowired
        UserService userService;
    
        @ResponseBody
        @GetMapping("/allUser")
        public List<User> allUser() {
            return userService.list();
        }
    
        @PostMapping("/addUser")
        public String addUser(User newUser) {
            userService.save(newUser);
            return "redirect:allUser";
        }
    }
    

    额外说明:

    @ResponseBody标注返回的不是路径,而是直接返回json数据(说的直白一点,我比较懒,不想写网页)。

    测试期望:

    1. 通过访问路径/allUser,期望获取数据库里user的所有数据
    2. 通过表格进行post到/addUser路径后,成功存储一个新的用户后,重定向到/allUser路径,获取所有用户,看新用户有没有被成功添加。
  2. 用HTML写一个简易的表格,通过post方法测试用户添加方法。如果懒惰的同学可以直接用postman测试。

    HTML代码如下。命名为index.html,同时放到resources文件夹内,当做主页(Springboot基础知识)。

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="/addUser" method="post">
            姓名:<input type="text" name="name"><br>
            年龄:<input type="text" name="age"><br>
            邮箱:<input type="email" name="email"><br>
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
  3. 测试!

    启动springboot项目后,访问http://localhost:8080/allUser,成功获得数据库里的所有用户数据。第一个期望成功实现。

    访问http://localhost:8080/ 到表格添加页面,添加以下数据并提交

    可以看到表格提交后重定向到了allUser页面。用户狗剩儿被成功添加,数据库里也可以看到狗剩儿被添加。第二个期望达成。

代码生成器

能够不用重复性地写简单的mapper层和service层代码已经是非常优秀了,然而更懒的程序员肯定是不会止步于此的。很明显的原因在于,实体类,mapper层和service层的简单代码甚至是类的创建本身就是重复性工作,按理来说有更简单的方法。

确实,MyBatisPlus有对应的代码生成器,只要有数据库,便可以一键生成相应的所有基础代码。

步骤

1. 添加依赖

添加代码生成器依赖和模板引擎依赖。

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

2. 创建测试用的数据库

除了之前的user表外,我另外创建了一个数据库,名称为shop,具体字段如下。

建表语句:

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
  `version` int(10) DEFAULT '1' COMMENT '乐观锁字段',
  `deleted` int(1) DEFAULT '0' COMMENT '逻辑删除',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

3. 书写代码生成器的运行代码

创建一个generator包,书写代码自动生成器代码,具体代码如下。有非常详细的注释,稍微修改后便可直接使用。

public class CodeGenerator {
    public static void main(String[] args) {
        // 创建一个代码自动生成器对象
        AutoGenerator autoGenerator = new AutoGenerator();

        // 1. 全局配置
        // 注意:导入的包为 com.baomidou.mybatisplus.generator.config.GlobalConfig
        GlobalConfig globalConfig = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        // 文件输出目录
        globalConfig.setOutputDir(projectPath + "/src/main/java");
        // 设置作者名
        globalConfig.setAuthor("John");
        // 是否打开输出目录(默认为true)
        globalConfig.setOpen(false);
        // 是否覆盖已有文件
        globalConfig.setFileOverride(true);
        // 去除生成的Service接口默认的"I"前缀
        globalConfig.setServiceName("%sService");
        // 指定生成的主键ID类型,这里一样的,设置为常规的主键自增
        globalConfig.setIdType(IdType.AUTO);
        // 全局配置注入代码生成器
        autoGenerator.setGlobalConfig(globalConfig);

        // 2. 数据源配置
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        // 设置连接的数据库
        dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&useSSL=false&characterEncoding=utf8");
        dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
        dataSourceConfig.setUsername("root");
        dataSourceConfig.setPassword("123456");
        autoGenerator.setDataSource(dataSourceConfig);

        // 3. 包的配置
        PackageConfig packageConfig = new PackageConfig();
        // 设置模块名称
        packageConfig.setModuleName("generated");
        // 设置父包名
        packageConfig.setParent("com.sjh");
        autoGenerator.setPackageInfo(packageConfig);

        // 4. 策略配置
        StrategyConfig strategy = new StrategyConfig();
        // 需要包含的表明(支持正则表达式)
        strategy.setInclude("user", "shop");
        // 设置数据库表和字段映射到实体的命名策略,这里设置为蛇形命名转化为驼峰式
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 不设置为lombok模型(默认为false,如果需要,改为true)
        strategy.setEntityLombokModel(false);

        // 设置逻辑删除属性
        strategy.setLogicDeleteFieldName("deleted");
        // 自动填充策略设置
        TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
        TableFill gmtModified = new TableFill("gmt_modified", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(gmtCreate);
        tableFills.add(gmtModified);
        strategy.setTableFillList(tableFills);
        // 乐观锁设置
        strategy.setVersionFieldName("version");
        autoGenerator.setStrategy(strategy);

        // 运行自动生成器
        autoGenerator.execute();
    }
}

可以看到,有四大基本配置:全局配置、数据源配置、包配置和策略配置。此外,还有一个模板引擎配置,在此处没有体现。引擎配置可以自己制定模板引擎、生成路径和某些代码是否需要生成,这些并没有必要进行修改。如果需要更改的话查看官方文档和源码即可。

必须要修改的地方有:

  • globalConfig.setAuthor("作者")设置作者名称;
  • 数据源必须要自行设置,此处不做详细说明;
  • 包的配置,将要生成的代码的根文件夹和此文件夹的父文件夹根据自己的需要进行设置;
  • 策略设置是最灵活的地方,strategy.setInclude("表名称")用来指明要生成的代码的数据表来源。命名策略、自动填充字段设置、逻辑删除属性设置、乐观锁字段设置等,根据实际情况自行修改。

运行代码后,所有的基础代码都生成了,并且有很好的注释和代码规范,以后自己写都懒得写了。

总结

以上的流程走完后,MyBatisPlus的使用基本没有问题。可以说,MyBatisPlus是一个可以大幅提高开发效率的工具,让开发者脱离重复的简单的CRUD代码书写,虽然有一定的局限性,但是学会了准没错。


http://www.niftyadmin.cn/n/2436247.html

相关文章

利用反射机制获取属性的值遇到的坑

类&#xff1a; public Class Test { public string name; public string value; } Test tnew Test(); t.name"abc"; t.value"123"; string str(string)t.GetType().GetProperty("name").GetValue(t,null); 找了3个小时&#xff0c;都找不出问题…

IDEA常用快捷键和Live Templates for Mac

1. IDEA常用快捷键 CmdShiftEnter&#xff1a;将输入的if&#xff0c;for&#xff0c;函数等等补上{}或者&#xff1b;使代码语句完整ShiftEnter&#xff1a;在当前行的下方开始新行OptEnter: 正则表达式验证CmdOptEnter&#xff1a;在当前行的上方插入新行OptEnter: 代码快速…

Qt 查找功能

版权声明该文章原创于Qter开源社区&#xff08;www.qter.org&#xff09;&#xff0c;作者yafeilinux&#xff0c;转载请注明出处&#xff01;导语这一篇我们来加上查找菜单的功能。因为要涉及Qt Creator的很多实用功能&#xff0c;所以单独用一篇文章来介绍。以前都用设计器设…

Git入门笔记

文章目录0. Git原理简述1. 设置用户签名 git config2. 初始化本地库 git init3. 查看本地库状态 git status4. 本地文件添加到暂存区 git add5. 暂存区的文件提交到本地库 git commit6. 查看历史版本 git reflog7. 修改文件后提交到本地库8. 版本穿梭9. Git分支9.1 查看分支9.2…

创建帧动画

Photoshop制作会跳动的文字动画效果和流动效果 --之心 新建一个大小适当文档&#xff0c;选择椭圆工具&#xff0c;按住Shift拉出一个正圆&#xff0c;然后锁定图层。选择渐变工具&#xff0c;将前景色与背景色分别设置为白色和任意深色。如下图中直线方向从上至下拖曳。得到小…

macOS IDEA等jetbrain全家桶,Clear Read-Only Status解决方法

问题 在编写代码的时候&#xff0c;代码因为只读而不让修改&#xff0c;并且跳出clear read-only status窗口。 主要原因在于&#xff0c;现在的macOS的权限设置导致编辑器没有权限修改代码。 解决办法 放开文件的写权限即可。 终端cd到项目的根目录后执行以下代码&#x…

imx6 关闭调试串口

需要关闭imx6调试串口&#xff0c;用作普通的串口使用。 参考链接 http://blog.csdn.net/neiloid/article/details/7585876 http://www.cnblogs.com/helloworldtoyou/p/5437867.html 更改kernel中配置&#xff0c; make menuconfig Symbol: SERIAL_IMX_CONSOLE [y] …

TypeScript语法错误:Argument of type ‘string‘ is not assignable to parameter of type ‘Element‘. 解决方法

问题描述 DOM插入元素节点报错。 TypeScript语法错误&#xff1a; TS2345: Argument of type ‘string’ is not assignable to parameter of type ‘Element’. 类型"string"的参数不能赋给类型"Element"的参数。 报错内容以及对应代码&#xff1a; 解决…