MyBatis-Plus

目录

一、MyBatis-Plus 简介

1、简介

2、特性

3、支持数据库

4、框架结构

5、代码及文档

二、入门案例

0、开发环境

1、创建数据库及表

2、创建 Spring Boot 工程

3、编写代码

3.1 配置 application.yml

3.2 启动类

3.3 添加实体

3.4 添加 mapper

3.5 测试

3.6 添加日志

三、基本 CRUD

1、 BaseMapper

2、插入

3、删除

3.1 通过 id 删除记录

3.2 通过 id 批量删除记录

3.3 通过 map 条件删除记录

4、修改

5、查询

5.1 根据 id 查询用户信息

5.2 根据多个 id 查询多个用户信息

5.3 通过 map 条件查询用户信息

5.4 查询所有数据

6、通用 Service

6.1 IService

6.2 创建 Service 接口和实现类

6.3 测试查询记录数

6.4 测试批量插入

四、常用注解

1、@TableName

1.1 问题

1.2 通过 @TableName 解决问题

1.3 通过全局配置解决问题

2、@TableId

2.1 问题

2.2 通过 @TableId 解决问题

2.3 @TableId 的 value 属性

2.4 @TableId 的 type 属性

3、@TableField

3.1 情况1

3.2 情况2

4、@TableLogic

4.1 逻辑删除

4.2 实现逻辑删除

4.3 全局配置

4.4 恢复

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

1、wapper 介绍

2、QueryWrapper

2.2.1 组装查询条件

2.2.2 组装排序条件

2.2.3 组装删除条件

2.2.4 条件的优先级

2.2.5 组装 select 子句

2.2.6 实现子查询

3、UpdateWrapper

4、condition

4.1 方法一

4.2 方法二

5、LambdaQueryWrapper

6、LambdaUpdateWrapper

六、插件

1、分页插件

1.1 添加配置类

1.2 测试

2、xml 自定义分页

2.1 UserMapper 中定义接口方法

2.2 UserMapper.xml 中编写 SQL

2.3 测试

3、乐观锁

3.1 数据库中增加商品表

3.2 添加数据

3.3 添加实体

3.4 添加 mapper

3.5 乐观锁实现流程

3.6 修改实体类

3.7 添加乐观锁插件配置

4. 自动填充

4.1 原理

4.2 实现

七、通用枚举

1. 数据库表添加字段 sex

2. 类增加 sex 属性

3. 创建通用枚举类型

4. 配置扫描通用枚举

5. 测试

八、代码生成器

1. 引入依赖

2. 快速生成

3. 测试一下

九、多数据源

0. 新建一个项目

1. 创建数据库及表

2. 引入依赖

3. 配置多数据源

4. 创建用户 service

5. 创建商品 service

6. 测试

十、MyBatisX 插件

1、快速上手

1.1 下载 MyBatisX 插件

1.2 XML 跳转

1.3 生成代码(需先在 idea 配置 Database 配置数据源)

1.3.1  配置数据源

1.3.2 IDEA 连接数据库 

 1.3.3 生成代码

1.4 快速生成 CRUD


一、MyBatis-Plus 简介


1、简介

MyBatis-Plus (简称 MP) 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

愿景

我们的愿望是成为 MyBatis 的最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。

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 操作智能分析阻断,也可自定义拦截规则,预防误操作

3、支持数据库

任何能使用 mybatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下

  • mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb

  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

4、框架结构

5、代码及文档

官方地址: http://mp.baomidou.com

代码发布地址:

Github: https://github.com/baomidou/mybatis-plus

Gitee: https://gitee.com/baomidou/mybatis-plus

文档发布地址: https://baomidou.com/pages/24112f

二、入门案例


0、开发环境

IDE:ieda 2021.3.3

JDK:JDK8+

构建工具:maven 3.3.9

MySQL 版本:MySQL 5.1.47

MyBatis 版本:MyBatis 3.5.7

Spring Boot:2.6.3

MyBatis-Plus:3.5.1

1、创建数据库及表

CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `mybatis_plus`;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL COMMENT '主键ID',
`name` VARCHAR(30) DEFAULT NULL COMMENT '姓名',
`age` INT(11) DEFAULT NULL COMMENT '年龄',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO USER (id, NAME, age, email) VALUES
(1, 'Jone', 18, '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');

2、创建 Spring Boot 工程

3.1 引入依赖

        <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>

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

        <!--lombok 用于简化实体类开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

3、编写代码

3.1 配置 application.yml

spring:
  # 配置数据源信息
  datasource:
    # 配置数据源类型
    type: com.zaxxer.hikari.HikariDataSource
    # 配置连接数据库信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
    username: root
    password: root

注意:

1、驱动类 driver-class-name

spring boot 2.0(内置jdbc5驱动),驱动类使用:

driver-class-name: com.mysql.cj.jdbc.Driver 否则运行测试用例的时候会有 WARN 信息

2、连接地址url MySQL5.7版本的url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false MySQL8.0版本的url: jdbc:mysql://localhost:3306/mybatis_plus? serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false 否则运行测试用例报告如下错误: java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more

3.2 启动类

在 Spring Boot 启动类中添加注解 @MapperScan 注解,扫描 mapper 包

@SpringBootApplication
@MapperScan("com.atguigu.mybatisplus.mapper")
public class MybatisplusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisplusApplication.class, args);
    }
}

3.3 添加实体

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

3.4 添加 mapper

public interface UserMapper extends BaseMapper<User> {
}

3.5 测试

@SpringBootTest
public class MybatisPlusTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList() {
        //selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
        userMapper.selectList(null).forEach(System.out::println);
    }
}

3.6 添加日志

在 application.yml 中配置日志输出

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

三、基本 CRUD

1、 BaseMapper

MyBatis-Plus 中的基本 CRUD 在内置的 BaseMapper 中都已得到了实现,我们可以直接使用,接口如下:

/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @author hubin
 * @since 2016-01-23
 */
public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

2、插入

    @Test
    public void testInsert(){
        User user = new User(null, "张三", 23, "zhangsan@atguigu.com");
        // INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
        int result = userMapper.insert(user);
        System.out.println("受影响行数: "+result);
        // 1551812348291842049
        System.out.println("id自动获取: "+user.getId());
    }

最终执行结果,所获取的 id 为 1551812348291842049

这是因为 MyBatis-Plus 在实现插入数据时,会默认基于雪花算法的策略生成 id

3、删除

3.1 通过 id 删除记录

    @Test
    public void testDeleteById() {
        // DELETE FROM user WHERE id=?
        int result = userMapper.deleteById(1551812348291842049L);
        System.out.println("受影响行数: " + result);
    }

3.2 通过 id 批量删除记录

    @Test
    public void testDeleteBatchIds() {
        List<Long> idList = Arrays.asList(1L, 2L, 3L);
        // DELETE FROM user WHERE id IN ( ? , ? , ? )
        int result = userMapper.deleteBatchIds(idList);
        System.out.println("受影响行数: " + result);
    }

3.3 通过 map 条件删除记录

    @Test
    public void testDeleteByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("age",23);
        map.put("name","张三");
        // DELETE FROM user WHERE name = ? AND age = ?
        int result = userMapper.deleteByMap(map);
        System.out.println("受影响行数: "+result);
    }

4、修改

    @Test
    public void testUpdateById(){
        User user = new User(4L, "admin", 22, null);
        // UPDATE user SET name=?, age=? WHERE id=?
        int result = userMapper.updateById(user);
        System.out.println("受影响行数: "+result);
    }

5、查询

5.1 根据 id 查询用户信息

    @Test
    public void testSelectById(){
        // SELECT id,name,age,email FROM user WHERE id=?
        User user = userMapper.selectById(4L);
        System.out.println(user);
    }

5.2 根据多个 id 查询多个用户信息

    @Test
    public void testSelectBatchIds(){
        List<Long> idList = Arrays.asList(4L, 5L);
        // SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
        List<User> list = userMapper.selectBatchIds(idList);
        list.forEach(System.out::println);
    }

5.3 通过 map 条件查询用户信息

    @Test
    public void testSelectByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("age",22);
        map.put("name","admin");
        // SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
        List<User> list = userMapper.selectByMap(map);
        list.forEach(System.out::println);
    }

5.4 查询所有数据

    @Test
    public void testSelectList() {
        // SELECT id,name,age,email FROM user
        List<User> list = userMapper.selectList(null);
        list.forEach(System.out::println);
    }

通过观察 BaseMapper 中的方法,大多方法都有 Wrapper 类型的形参,此为条件构造器,可针对 SQL 语句设置不同的条件,若没有条件,则可以为该形参赋值 null,即查询(删除 / 修改)所有数据

6、通用 Service

说明:

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

6.1 IService

MyBatis-Plus 中有一个接口 IService 和其实现类 ServiceImpl,封装了常见的业务层逻辑,详情请查看源码 IService 和 ServiceImpl

6.2 创建 Service 接口和实现类

// UserService 继承 IService 模板提供的基础功能
public interface UserService extends IService<User> {
}
// ServiceImpl 实现了 IService,提供了 IService中基础功能的实现
// 若 ServiceImpl 无法满足业务需求,则可以使用自定义的UserService定义方法,并在实现类中实现
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}

6.3 测试查询记录数

    @Autowired
    private UserService userService;

    @Test
    public void testGetCount(){
        int count = userService.count();
        System.out.println("总记录数: "+count);
    }

6.4 测试批量插入

    @Test
    public void testSaveBatch() {
        // SQL 长度有限制,海量数据插入单行SQL无法实行
        // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            User user = new User();
            user.setName("ybc" + i);
            user.setAge(20 + i);
            users.add(user);
        }
        // INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
        userService.saveBatch(users);
    }

四、常用注解

1、@TableName

经过以上的测试,在使用 MyBatis-Plus 实现基本的 CRUD 时,我们并没有指定要操作的表,只是在 Mapper 接口继承 BaseMapper 时,设置了泛型 User,而操作的表为 user 表

由此得出结论,MyBatis-Plus 在确定操作的表时,由 BaseMapper 的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致

1.1 问题

若实体类类型的类名和要操作的表的表名不一致,会出现什么问题?

我们将表 user 更名为 t_user,测试查询功能

程序抛出异常,Table 'mybatis_plus.user' doesn't exist,因为现在的表名为 t_user,而默认操作的表名和实体类型的类名一致,即 user 表

1.2 通过 @TableName 解决问题

在实体类类型上添加 @TableName("t_user"),标识实体类对应的表,即可成功执行 SQL 语句

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

1.3 通过全局配置解决问题

在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如 t_或 tbl_

此时,可以使用 MyBatis-Plus 提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就不需要在每个实体类上通过 @TableName 标识实体类所对应的表

keepGlobalPrefix 属性:是否保持使用全局的 tablePrefix 的值,使用全局配置时,需要设置此属性的值为 true

# 配置MyBatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置 MyBatis-Plus 操作表的默认前缀
      table-prefix: t_

2、@TableId

经过以上的测试,MyBatis-Plus 在实现 CRUD 时,会默认将 id 作为主键列,并在插入数据时,默认基于雪花算法的策略生成 id

2.1 问题

若实体类和表中表示主键的不是 id,而是其他字段,例如 uid,MyBatis-Plus 会自动识别 uid 为主键列吗?

我们将实体类的属性 id 改为 uid,将表中的字段 id 也改为 uid,测试添加功能

程序抛出异常,Field 'uid' doesn't have a default value,说明 MyBatis-Plus 没有将 uid 作为主键赋值

2.2 通过 @TableId 解决问题

在实体类中 uid 属性上通过 @TableId 将其标识为主键,即可成功执行 SQL 语句

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

2.3 @TableId 的 value 属性

若实体类中主键对应的属性为 id,而表中标识主键的字段为 uid,此时若只在属性 id 上添加注解 @TableId,则抛出移除 Unknown column 'id' in 'fieId list',即 MyBatis-Plus 仍然会将 id 作为表的主键操作,而表中表示主键的是字段 uid

此时需要通过 @TableId 注解的 value 属性,指定表中的主键字段,@TableId("uid") 或 @TableId(value = "uid")

2.4 @TableId 的 type 属性

type 属性用来定义主键策略

常用的主键策略:

描述
IdType.ASSIGN_ID(默 认)基于雪花算法的策略生成数据 id,与数据库 id 是否设置自增无关,维护 Long 数据
IdType.AUTO使用数据库的自增策略,注意,该类型请确保数据库设置了 id 自增,否则无效
IdType.ASSIGN_UUID分配 UUID,维护 String 数据

public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long uid;
    private String name;
    private Integer age;
    private String email;
}

配置全局主键策略

# 配置MyBatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-plus的主键策略
      id-type: auto

3、@TableField

经过以上的测试,我么可以发现,MyBatis-Plus 在执行 SQL 语句时,要保证实体类中的属性名和表中的字段名一致

如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?

3.1 情况1

若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格

例如实体类属性 userName,表中字段 user_name

此时 MyBatis-Plus 会自动将下划线命名风格转化为驼峰命名风格

相当于在 MyBatis 中配置

3.2 情况2

若实体类中的属性和表中的字段不满足情况1

例如实体类属性 name,表中字段 username

此时需要在实体类属性上使用 @TableField("username") 设置属性所对应的字段名

public class User {
    @TableId(type = IdType.ASSIGN_ID)
    private Long uid;
    @TableField("username")
    private String name;
    private Integer age;
    private String email;
}

4、@TableLogic

4.1 逻辑删除

  • 物理删除:真是删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为 "被删除状态",之后再数据库中仍旧能看到此条数据记录
  • 使用场景:可以进行数据恢复

4.2 实现逻辑删除

第一步:数据库中创建逻辑删除状态列,设置默认值为 0

第二步:实体类中添加逻辑删除属性

 

 第三步:测试

测试删除功能,真正执行的是修改

UPDATE t_user SET is_deleted=1 WHERE id=? AND is_deleted=0

测试查询功能,被逻辑删除的数据默认不会被查询

SELECT id AS uid,name,age,email,is_deleted FROM t_user WHERE is_deleted=0

4.3 全局配置

如果使用了全局配置,可以不使用注解 @TableLogic

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted # 局逻辑删除的实体字段名
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

4.4 恢复

在 Mapper 接口中定义方法恢复

    @Update("update t_user set is_delete = 0 where id = #{id}")
    public void recoveryById(@Param("id") Long id);

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

1、wapper 介绍

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

2、QueryWrapper

2.2.1 组装查询条件

    @Test
    public void test1(){
        // 查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name","a")
                .between("age",20,30)
                .isNotNull("email");
        // SELECT id AS uid,name,age,email,is_deleted FROM t_user 
        // WHERE is_deleted=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

2.2.2 组装排序条件

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

2.2.3 组装删除条件

    @Test
    public void test3() {
        // 删除email为空的用户
        // DELETE FROM t_user WHERE (email IS NULL)
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("email");
        // 条件构造器也可以构建删除语句的条件
        int result = userMapper.delete(queryWrapper);
        System.out.println("受影响的行数: " + result);
    }

2.2.4 条件的优先级

    @Test
    public void test4() {
        // 将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .like("name", "a")
                .gt("age", 20)
                .or()
                .isNotNull("email");

        User user = new User();
        user.setAge(18);
        user.setEmail("user@atguigu.com");
        // UPDATE t_user SET age=?, email=? WHERE (name LIKE ? AND age > ? OR email IS NOT NULL)
        int result = userMapper.update(user, queryWrapper);
        System.out.println("受影响的行数: " + result);
    }
    @Test
    public void test4() {
        // 将用户名中包含有a并且(年龄大于20或1邮箱为null)的用户修改
        // lambda表达式内的逻辑优先运算
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper
                .like("name", "a")
                .and(i -> i.gt("age", 20).or().isNull("email"));
        User user = new User();
        user.setAge(18);
        user.setEmail("user@atguigu.com");
        // UPDATE t_user SET age=?, email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
        int result = userMapper.update(user, queryWrapper);
        System.out.println("受影响的行数: " + result);
    }

2.2.5 组装 select 子句

    @Test
    public void test5() {
        // 查询用户信息的name和age字段
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name", "age");
        // selectMaps() 返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
        // SELECT name,age FROM t_user
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }

2.2.6 实现子查询

    @Test
    public void test6() {
        // 查询id小于等于3的用户信息
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("id", "select id from t_user where id <= 3");
        // SELECT id AS uid,name,age,email FROM t_user WHERE (id IN (select id from t_user where id <= 3))
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }

3、UpdateWrapper

    @Test
    public void test7() {
        // 将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
        // 组装set子句以及修改条件
        UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
        updateWrapper
                .set("age", 18)
                .set("email", "user@atguigu.com")
                .like("name", "a")
                .and(i -> i.gt("age", 20).or().isNull("email"));

        //这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
        // User user = new User();
        // user.setName("张三");
        // UPDATE t_user SET name=?, age=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
        // int result = userMapper.update(user, updateWrapper);

        // UPDATE t_user SET age=?,email=? WHERE (name LIKE ? AND (age > ? OR email IS NULL))
        int result = userMapper.update(null, updateWrapper);
        System.out.println("受影响的行数: " + result);
    }

4、condition

在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因此我们再组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响 SQL 执行的结果

4.1 方法一

    @Test
    public void test8() {
        // 定义查询条件,有可能为null(用户未输入或未选择)
        String name = null;
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        // StringUtils.isNotBlank() 判断某字符串是否不为空且长度不为0且不为空白符构成
        if (StringUtils.isNotBlank(name)) {
            queryWrapper.like("name", "a");
        }

        if (ageBegin != null) {
            queryWrapper.ge("age", ageBegin);
        }

        if (ageEnd != null) {
            queryWrapper.le("age", ageEnd);
        }

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

4.2 方法二

上面的实现方案没有问题,但是代码比较复杂,我们可以使用带 condition 参数的重载方法构建查询条件,简化代码的编写

    @Test
    public void test9() {
        // 定义查询条件,有可能为null(用户未输入或未选择)
        String name = null;
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(name), "name", "a")
                .ge(ageBegin != null, "age", ageBegin)
                .le(ageEnd != null, "age", ageEnd);

        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

5、LambdaQueryWrapper

    @Test
    public void test9() {
        // 定义查询条件,有可能为null(用户未输入)
        String name = "a";
        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);
    }

6、LambdaUpdateWrapper

    @Test
    public void test10() {
        // 组装set子句
        LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper
                .set(User::getAge, 18)
                .set(User::getEmail, "user@atguigu.com")
                .like(User::getName, "a")
                .and(i -> i.lt(User::getAge, 24).or().isNull(User::getEmail)); // lambda表达式内的逻辑优先运算
        User user = new User();
        int result = userMapper.update(user, updateWrapper);
        System.out.println("受影响的行数: " + result);
    }

六、插件

1、分页插件

MyBatis Plus 自带分页插件,只要简单的配置即可实现分页功能

1.1 添加配置类

@Configuration
@MapperScan("com.atguigu.mybatisplus.mapper") // 可以将族类中的注解移到此处
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new
                // 这里可以选择数据库类型
                PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

1.2 测试

    @Test
    public void testPage() {
        // 设置分页参数
        Page<User> page = new Page<>(1, 5);
        userMapper.selectPage(page, null);
        // 获取分页数据
        List<User> list = page.getRecords();
        list.forEach(System.out::println);
        System.out.println("当前页: " + page.getCurrent());
        System.out.println("每页显示的条数: " + page.getSize());
        System.out.println("总记录数: " + page.getTotal());
        System.out.println("总页数: " + page.getPages());
        System.out.println("是否有上一页: " + page.hasPrevious());
        System.out.println("是否有下一页: " + page.hasNext());
    }

2、xml 自定义分页

2.1 UserMapper 中定义接口方法

@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 根据年龄查询用户列表,分页显示
     *
     * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位
     * @param age  年龄
     * @return
     */
    Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
}

2.2 UserMapper.xml 中编写 SQL

<!--SQL片段,记录基础字段-->
<sql id="BaseColumns">id,username,age,email</sql>

<!--IPage<User> selectPageVo(Page<User> page, Integer age);-->
<select id="selectPageVo" resultType="User">
SELECT <include refid="BaseColumns"></include> FROM t_user WHERE age > #
{age}
</select>

2.3 测试

    @Test
    public void testSelectPageVo() {
        //设置分页参数
        Page<User> page = new Page<>(1, 5);
        userMapper.selectPageVo(page, 20);
        //获取分页数据
        List<User> list = page.getRecords();
        list.forEach(System.out::println);
        System.out.println("当前页:" + page.getCurrent());
        System.out.println("每页显示的条数:" + page.getSize());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("总页数:" + page.getPages());
        System.out.println("是否有上一页:" + page.hasPrevious());
        System.out.println("是否有下一页:" + page.hasNext());
    }

3、乐观锁

3.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)
);

3.2 添加数据

INSERT INTO t_product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

3.3 添加实体

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

3.4 添加 mapper

public interface ProductMapper extends BaseMapper<Product> {
}

3.5 乐观锁实现流程

数据库中添加 version 字段

取出记录时,获取当前 version

SELECT id,`name`,price,`version` FROM product WHERE id=1

更新时,version + 1,如果 where 语句中的 version 版本不对,则更新失败

SELECT id,`name`,price,`version` FROM product WHERE id=1

3.6 修改实体类

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

3.7 添加乐观锁插件配置

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        //添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        //添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }

4. 自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用 MyBatis Plus 的自动填充功能,完成这些字段的赋值功能;

4.1 原理

  • 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler,确定填充具体操作
  • 注解填充字段:@TableField(fill = ...) 确定字段填充的时机
    • FieldFill.INSERT:插入填充字段
    • FieldFill.UPDATE:更新填充字段
    • FieldFill.INSERT_UPDATE:插入和更新填充字段

4.2 实现

4.2.1 添加表字段

 4.2.2 添加类属性

    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(value = "update_time", fill = FieldFill.UPDATE)
    private Date updateTime;

4.2.3 编写处理类

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    // 插入填充
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
    }

    // 更新填充
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

4.2.4 测试

    @Test
    public void testInsert() {
        User user = new User();
        user.setName("testInsert");
        user.setAge(18);
        user.setEmail("atguigu@com");

        userMapper.insert(user);
    }

    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName("update");
        userMapper.updateById(user);
    }

七、通用枚举

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

1. 数据库表添加字段 sex

2. 类增加 sex 属性

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

3. 创建通用枚举类型

import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;

@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;
    }
}

4. 配置扫描通用枚举

# 配置MyBatis日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      # 配置MyBatis-Plus操作表的默认前缀
      table-prefix: t_
      # 配置MyBatis-plus的主键策略
      id-type: auto
  # 配置扫描通用枚举
  type-enums-package: com.atguigu.mybatisplus.enums

5. 测试

    @Test
    public void testSexEnum() {
        User user = new User();
        user.setName("Enum");
        user.setAge(20);
        //设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库
        user.setSex(SexEnum.MALE);
        //INSERT INTO t_user ( username, age, sex ) VALUES ( ?, ?, ? )
        //Parameters: Enum(String), 20(Integer), 1(Integer)
        userMapper.insert(user);
    }

八、代码生成器


1. 引入依赖

        <!--注意!!当前包未传递依赖 mp 包,需要自己引入-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>

2. 快速生成

FastAutoGenerator.create("url", "username", "password")
	.globalConfig(builder -> {
		builder.author("baomidou") // 设置作者
            .enableSwagger() // 开启 swagger 模式
			.fileOverride() // 覆盖已生成文件
			.outputDir("D://"); // 指定输出目录
	})
	.packageConfig(builder -> {
		builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
			.moduleName("system") // 设置父包模块名
            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
	})
	.strategyConfig(builder -> {
		builder.addInclude("t_simple") // 设置需要生成的表名
			.addTablePrefix("t_", "c_"); // 设置过滤表前缀
	})
	.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
	.execute();

3. 测试一下

    @Test
    public void testCodeGenerator() {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC", "root", "root")
                .globalConfig(builder -> {
                    builder.author("安|德森") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.atguigu.mybatisplus.samples.generator") // 设置父包名
                            .moduleName("system") // 设置父包模块名
                            .pathInfo(Collections.singletonMap(OutputFile.mapper, "D://")); // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("t_user") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }

九、多数据源

适用于多种场景:存粹对库、读写分离、一主多从、混合模式等

目前我们就来模拟一个存粹多库的场景,其他场景类似

场景说明:

我们创建两个库,分别为:mybatis_plus (以前的库不动) 与 mybatis_plus_1 (新建),将mybatis_plus 库的 product 表移动到 mybatis_plus_1 表,这样每个库一张表,通过一个测试

用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功

0. 新建一个项目

1. 创建数据库及表

创建数据库 mybatis_plus_1 和表 product

CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus_1`;
CREATE TABLE product
(
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
    price INT(11) DEFAULT 0 COMMENT '价格',
    version INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
    PRIMARY KEY (id)
);

添加测试数据

INSERT INTO product (id, NAME, price) VALUES (1, '外星人笔记本', 100);

删除 mybatis_plus 库 product 表

use mybatis_plus;
DROP TABLE IF EXISTS product;

2. 引入依赖

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

3. 配置多数据源

说明:注释掉之前的数据库连接,添加新配置

spring:
  # 配置数据源信息
  datasource:
    dynamic:
      # 设置默认的数据源或者数据源组,默认值即为master
      primary: master
    # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root

4. 创建用户 service

public interface UserService extends IService<User> {
}
@DS("master") //指定所操作的数据源
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

5. 创建商品 service

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

6. 测试

    @Autowired
    private UserService userService;

    @Autowired
    private ProductService productService;

    @Test
    public void testDynamicDataSource() {
        System.out.println(userService.getById(1L));
        System.out.println(productService.getById(1L));
    }

结果:

1、都能顺利获取对象,则测试成功

2、如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切换,是不是就能实现读写分离?

十、MyBatisX 插件

MyBatis-Plus 为我们提供了强大的 mapper 和 service 模板,能够大大的提高开发效率

但是在真正开发过程中,MyBatis-Plus 并不能为我们解决所有问题,例如一些复杂的 SQL,多表联查,我们就需要自己取编写代码和 SQL 语句,我们该如何快速的解决这个问题呢?这个时候可以使用 MyBatisX 插件

MyBatisX 一款基于 IDEA 的快速开发插件,为效率而生。

MyBatisX 插件用法:MybatisX快速开发插件 | MyBatis-Plus

1、快速上手

1.1 下载 MyBatisX 插件

 下载完成之后重启 IDEA

1.2 XML 跳转

1.3 生成代码(需先在 idea 配置 Database 配置数据源)

1.3.1  配置数据源

spring:
  # 配置数据源信息
  datasource:
    url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

1.3.2 IDEA 连接数据库 

 1.3.3 生成代码

 生成的内容

1.4 快速生成 CRUD

在在接口中书写方法名,alt + enter,选择第二个选项 

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值