console.log(🐂🍺);|

我係死肥宅

园龄:4年11个月粉丝:19关注:3

MyBatis Plus

Mybatis-Plus

1 简介

1.1 Mybatis-Plus 概述

img

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

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

特性:

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

1.2 代码及文档发布地址

官方地址:https://baomidou.com/pages/24112f/

代码地址:

  1. https://github.com/baomidou/mybatis-plus
  2. https://gitee.com/baomidou/mybatis-plus

2 集成MP

2.1 创建测试表

现有一张 User 表,其表结构如下:

id name age email
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

其对应的数据库 Schema 脚本如下:

DROP TABLE IF EXISTS user;

CREATE TABLE `user` (
  `id` bigint NOT NULL COMMENT '主键ID',
  `name` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
  `age` int DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin

其对应的数据库 Data 脚本如下:

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

2.2 创建 JavaBean

package com.atguigu.mp.mp01.bean;

import lombok.Data;

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

2.3 依赖配置

依赖配置

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

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </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.5.0</version>
    </dependency>
</dependencies>

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

数据源配置

# DataSource Config
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mp?useSSL=false&useUnicode=true&characterEncoding=utf-8
    username: root
    password: 12345678

3 入门HelloWorld

3.1 入门CRUD

  1. 提出问题:

假设我们已存在一张 tbl_employee 表,且已有对应的实体类 Employee ,实现 tbl_employee 表的 CRUD 操作我们需要做什么呢?

  1. 实现方式
  • 基于MyBatis
    需要编写 EmployeeMapper 接口,并手动编写 CRUD 方法。提供 EmployeeMapper.xml 映射文件,并手动编写每个方法对应的 SQL语句

  • 基于 MP

    只需要创建 EmployeeMapper 接口,并继承 BaseMapper 接口。这就是使用 MP 需要完成的所有操作,甚至不需要创建 SQL映射文件。

编写 Mapper 类 UserMapper.java

package com.atguigu.mp.mp01.mapper;

import com.atguigu.mp.mp01.beans.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

public interface UserMapper extends BaseMapper<User> {

}

启动类设置

package com.atguigu.mp.mp01;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.atguigu.mp.mp01.mapper")
public class Mp01Application {

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

}

配置日志

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

3.2 插入操作

package com.atguigu.mp.mp01;

import com.atguigu.mp.mp01.bean.User;
import com.atguigu.mp.mp01.mapper.UserMapper;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

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

    @Test
    public void insert() {
        userMapper.insert(new User(null, "MP", 22, "mp@atguigu.com"));
    }
}

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

2022-01-06 10:44:46.563  INFO 5484 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1684265526 wrapping com.mysql.cj.jdbc.ConnectionImpl@4a4b288a] will not be managed by Spring
==>  Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1478920447641960450(Long), MP(String), 22(Integer), mp@atguigu.com(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1426370c]

3.2.1 主键问题

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

属性 类型 必须指定 默认值 描述
value String "" 主键字段名
type Enum IdType.NONE 指定主键类型

相关的枚举:

描述
AUTO 数据库 ID 自增
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert 前自行 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)
UUID 32 位 UUID 字符串(please use ASSIGN_UUID)
ID_WORKER_STR 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID)

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

/*
 * Copyright (c) 2011-2022, baomidou (jobob@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.baomidou.mybatisplus.annotation;

import lombok.Getter;

/**
 * 生成ID类型枚举类
 *
 * @author hubin
 * @since 2015-11-10
 */
@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;
    }
}

3.2.2 设置主键自增

数据库层面

image

代码层面

在字段上增加注解

@TableId(value = "id", type = IdType.AUTO)
private Long id;

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

JDBC Connection [HikariProxyConnection@1507181879 wrapping com.mysql.cj.jdbc.ConnectionImpl@5464b97c] will not be managed by Spring
==>  Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: MP(String), 22(Integer), mp@atguigu.com(String)
<==    Updates: 1
id name age email
6 MP 22 mp@atguigu.com

3.2.3 数据库全局配置

在application.yml中配置,具体配置:基本配置

3.2.4 相关注解

@TableName
  • 描述:表名注解,标识实体类对应的表
  • 使用位置:实体类
@TableId
  • 描述:主键注解
  • 使用位置:实体类主键字段
@TableField
  • 描述:字段注解(非主键)
属性 类型 必须指定 默认值 描述
value String "" 数据库字段名
exist boolean true 是否为数据库表字段

具体内容可查看官方文档,这里注意一下exist字段,如果设置成false表示数据库里面没有这个字段。

例如在 User 类中新增一个字段salary,如果不设置该字段会报如下错误:

Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'salary' in 'field list'

可以设置如下:

@TableField(exist = false)
private Double salary;

3.2.5 插入数据后获取主键值

MyBatis-Plus在插入后能够自动获取主键值。示例代码如下:

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

    @Test
    public void insert() {
        User user = new User(null, "MP", null, "mp@atguigu.com", 20000d);
        userMapper.insert(user);
        System.out.println(user.getId());
    }
}

控制台打印:

JDBC Connection [HikariProxyConnection@911998047 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a7e799e] will not be managed by Spring
==>  Preparing: INSERT INTO user ( name, email ) VALUES ( ?, ? )
==> Parameters: MP(String), mp@atguigu.com(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@506aa618]
10

3.2.6 插入所有字段

可以在配置文件中修改

insertStrategy

  • 类型:com.baomidou.mybatisplus.annotation.FieldStrategy
  • 默认值:NOT_NULL

字段验证策略之 insert,在 insert 的时候的字段验证策略

/**
 * 字段策略枚举类
 * <p>
 * 如果字段是基本数据类型则最终效果等同于 {@link #IGNORED}
 *
 * @author hubin
 * @since 2016-09-09
 */
public enum FieldStrategy {
    /**
     * 忽略判断
     */
    IGNORED,
    /**
     * 非NULL判断
     */
    NOT_NULL,
    /**
     * 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
     */
    NOT_EMPTY,
    /**
     * 默认的,一般只用于注解里
     * <p>1. 在全局里代表 NOT_NULL</p>
     * <p>2. 在注解里代表 跟随全局</p>
     */
    DEFAULT,
    /**
     * 不加入 SQL
     */
    NEVER
}

如果设置为IGNORE,即使字段为空也进行插入。

3.3 更新操作

@Test
public void update() {
    User user = new User(7L, "MP2", null, "mp2@atguigu.com");
    userMapper.updateById(user);
}
JDBC Connection [HikariProxyConnection@674247007 wrapping com.mysql.cj.jdbc.ConnectionImpl@da22aa] will not be managed by Spring
==>  Preparing: UPDATE user SET name=?, email=? WHERE id=?
==> Parameters: MP2(String), mp2@atguigu.com(String), 7(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4afd65fd]

3.4 查询操作

/**
 * 根据 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 条件,查询一条记录
 * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
 *
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
    List<T> ts = this.selectList(queryWrapper);
    if (CollectionUtils.isNotEmpty(ts)) {
        if (ts.size() != 1) {
            throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
        }
        return ts.get(0);
    }
    return null;
}

/**
 * 根据 Wrapper 条件,判断是否存在记录
 *
 * @param queryWrapper 实体对象封装操作类
 * @return
 */
default boolean exists(Wrapper<T> queryWrapper) {
    Long count = this.selectCount(queryWrapper);
    return null != count && count > 0;
}

/**
 * 根据 Wrapper 条件,查询总记录数
 *
 * @param queryWrapper 实体对象封装操作类(可以为 null)
 */
Long 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)
 */
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

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

3.5 删除操作

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

/**
 * 根据实体(ID)删除
 *
 * @param entity 实体对象
 * @since 3.4.4
 */
int deleteById(T entity);

/**
 * 根据 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<?> idList);

3.6 通用 CRUD 小结

  1. 以上是基本的 CRUD操作,如您所见,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD操作。 BaseMapper 提供了很多方法给大家使用,可以极其方便的实现单一、批量、分页等操作。极大的减少开发负担,难道这就是 M P的强大之处了吗?
  1. 提出需求:
    现有一个需求,我们需要分页查询 Employee 表中,年龄在 18 ~ 50之间性别为男且姓名为 xx 的所有用户,这时候我们该如何实现上述需求呢?

    MyBatis:需要在 SQL映射文件中编写带条件查询的 SQL,并基于 PageHelper 插件完成分页。实现以上—个简单的需求,往往需要我们做很多重复单调的工作。普通的 Mapper 能够解决这类痛点吗?

    MP: 依旧不用编写 SQL语句, MP提供了功能强大的条件构造器 EntityWrapper。

4 条件构造器 QueryWrapper

4.1 AbstractWrapper

QueryWrapper(LambdaQueryWrapper) 和

UpdateWrapper(LambdaUpdateWrapper) 的父类

用于生成 sql 的 where 条件,entity 属性也用于生成 sql 的 where 条件

注意:entity 生成的 where 条件与使用各个 api 生成的 where 条件没有任何关联行为

所有的条件参数可以看官方文档,复制一大串在这里也挺没意思的。

4.1.1 继承关系

Wrapper:条件构造抽象类,最顶端父类,抽象类中提供4个方法西面贴源码展示

AbstractWrapper :用于查询条件封装,生成 sql 的 where 条件

AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。

LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper

LambdaUpdateWrapper : Lambda 更新封装Wrapper

QueryWrapper : Entity 对象封装操作类,不是用lambda语法

UpdateWrapper : Update 条件封装,用于Entity对象更新操作

4.2 QueryWrapper

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper ,可以通过 new QueryWrapper().lambda() 方法获取

https://baomidou.com/pages/10c804/#querywrapper

4.2.1 查询分页示例

在查询分页需要进行如下配置

package com.atguigu.mp.mp01.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

给一个简单示例,一看就能明白了

@Test
public void wrapper() {
    Page<User> userPage = userMapper.selectPage(new Page<>(1, 2),
            new QueryWrapper<User>().like("name", "%MP%")
                    .between("age", 15, 25)
                    .orderByDesc("age")
    );
    System.out.println(JSON.toJSONString(userPage));
}

这个是日志,可以看到打印的SQL

2022-01-07 10:15:40.610  INFO 2573 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-07 10:15:40.804  INFO 2573 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1185122508 wrapping com.mysql.cj.jdbc.ConnectionImpl@3e39f08c] will not be managed by Spring
==>  Preparing: SELECT COUNT(*) AS total FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ?)
==> Parameters: %%MP%%(String), 15(Integer), 25(Integer)
<==    Columns: total
<==        Row: 3
<==      Total: 1
==>  Preparing: SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ?) ORDER BY age DESC LIMIT ?
==> Parameters: %%MP%%(String), 15(Integer), 25(Integer), 2(Long)
<==    Columns: id, name, age, email
<==        Row: 11, MP, 25, mp@atguigu.com
<==        Row: 6, MP, 22, mp@atguigu.com
<==      Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d226f27]

如果不配置 MybatisPlusInterceptor ,MP 提供的分页方法是无效的,例如使用mysql,配置了 MybatisPlusInterceptor ,调用MP提供的分页方法,假设使用selectPage方法,他会执行两条sql语句,一条查询总记录数的sql,一条查询当前页记录的sql是带limit分页条件的。如果不配置,调用selectPage只会执行一条查询记录的sql,并且不带limit,有兴趣你可以试试,很容易就能验证出来的。

返回的结果如下:

{
    "current":1,
    "optimizeCountSql":true,
    "orders":[

    ],
    "pages":2,
    "records":[
        {
            "age":25,
            "email":"mp@atguigu.com",
            "id":11,
            "name":"MP"
        },
        {
            "age":22,
            "email":"mp@atguigu.com",
            "id":6,
            "name":"MP"
        }
    ],
    "searchCount":true,
    "size":2,
    "total":3
}

4.2.2 查询列表示例

测试一个带OR条件的例子

@Test
public void wrapper() {
    Page<User> userPage = userMapper.selectPage(new Page<>(1, 2),
            new QueryWrapper<User>().eq("name", "Jone")
                    .or(wrapper -> wrapper.eq("email", "test3@baomidou.com")
                            .and(subWrppaer -> subWrppaer.eq("name", "Tom"))
                    )
    );
    System.out.println(JSON.toJSONString(userPage));
}

打印的SQL如下

2022-01-07 10:23:16.429  INFO 2648 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-07 10:23:16.612  INFO 2648 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@547647542 wrapping com.mysql.cj.jdbc.ConnectionImpl@70c205bf] will not be managed by Spring
==>  Preparing: SELECT COUNT(*) AS total FROM user WHERE (name = ? OR (email = ? AND (name = ?)))
==> Parameters: Jone(String), test3@baomidou.com(String), Tom(String)
<==    Columns: total
<==        Row: 2
<==      Total: 1
==>  Preparing: SELECT id,name,age,email FROM user WHERE (name = ? OR (email = ? AND (name = ?))) LIMIT ?
==> Parameters: Jone(String), test3@baomidou.com(String), Tom(String), 2(Long)
<==    Columns: id, name, age, email
<==        Row: 1, Jone, 18, test1@baomidou.com
<==        Row: 3, Tom, 28, test3@baomidou.com
<==      Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1bb96449]
{"current":1,"optimizeCountSql":true,"orders":[],"pages":1,"records":[{"age":18,"email":"test1@baomidou.com","id":1,"name":"Jone"},{"age":28,"email":"test3@baomidou.com","id":3,"name":"Tom"}],"searchCount":true,"size":2,"total":2}

4.2.3 删除示例

@Test
public void wrapper() {
    userMapper.delete(new QueryWrapper<User>()
            .eq("id", 9)
            .eq("name", "MP")
    );
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e24592e] was not registered for synchronization because synchronization is not active
2022-01-07 10:44:14.443  INFO 2912 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-07 10:44:14.863  INFO 2912 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@545851930 wrapping com.mysql.cj.jdbc.ConnectionImpl@70a24f9] will not be managed by Spring
==>  Preparing: DELETE FROM user WHERE (id = ? AND name = ?)
==> Parameters: 9(Integer), MP(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e24592e]

4.3 UpdateWrapper

说明:

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaUpdateWrapper,可以通过 new UpdateWrapper().lambda() 方法获取!

public void wrapper() {
    /*
        根据 whereEntity 条件,更新记录
            Params:
            entity – 实体对象 (set 条件值,可以为 null)
            updateWrapper – 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    userMapper.update(new User(null, "Jerry", 22, "test11@baomidou.com"),
            new UpdateWrapper<User>().eq( "id", 11L)
                    .eq("email", "mp@atguigu.com")
    );
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f0a4e30] was not registered for synchronization because synchronization is not active
2022-01-07 10:35:04.705  INFO 2774 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-07 10:35:04.891  INFO 2774 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1370822209 wrapping com.mysql.cj.jdbc.ConnectionImpl@362a6aa5] will not be managed by Spring
==>  Preparing: UPDATE user SET name=?, age=?, email=? WHERE (id = ? AND email = ?)
==> Parameters: Jerry(String), 22(Integer), test11@baomidou.com(String), 11(Long), mp@atguigu.com(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f0a4e30]

5 ActiveRecord 活动记录

Active Record(活动记录 ),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的 一行记录。

ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP也在 AR 道路上进行了一定的探索

5.1 如何使用 AR 模式

仅仅需要让实体类继承Model类且实现主键制定方法,即刻开启AR之旅。

新版本中不这么干也行

package com.atguigu.mp.mp01.bean;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class User extends Model<User> {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;

    @Override
    public Serializable pkVal() {
        return this.id;
    }
}

5.2 AR 的使用

这里就用一个新增举例子了,其他的都是一样的,看父类里面的方法和参数就行了 。

@Test
public void arInsert() {
    User user = new User();
    user.setName("Song");
    user.setEmail("sls@atguigu.com");
    user.setAge(35);

    boolean result = user.insert();
    System.out.println("result = " + result);
}

日志长这样

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64f3ca6] was not registered for synchronization because synchronization is not active
2022-01-07 12:35:12.278  INFO 5830 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-07 12:35:12.462  INFO 5830 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@313145375 wrapping com.mysql.cj.jdbc.ConnectionImpl@6115846e] will not be managed by Spring
==>  Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: Song(String), 35(Integer), sls@atguigu.com(String)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64f3ca6]
result = true

5.3 AR 小结

AR模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 MyBatis 对应的方法,类似于语法糖。

到此,我们简单领略了 MyBatis-Plus 的魅力与高效率,值得注意的一点是:我们提供了强大的代码生成器,可以快速生成各类代码,真正的做到了即开即用。

6 代码生成器

MP提供了大量的自定义设置,生成的代码完全能够满足各类型的需求

表及字段命名策略选择

在MP中,我们建议数据库表名和表字段名采用驼峰命名方式,如果采用下划线命名方式谙开启全局下划线开关。(如果表名字段名命名方式不一致请注解指定)我们建议最好保持一致。

这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虎这点性能损耗,那么你采用下划线也是没问题的。只需要在生成代码时配置 dbColumnUnderline 属性就可以。

6.1 快速入门

Generator源码地址:

Github:https://github.com/baomidou/generator

Gitee:https://gitee.com/baomidou/generator

Maven仓库坐标

https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator

官方文档:https://baomidou.com/pages/779a6e/#安装

6.1.1 构建者模式

在我们展开使用代码生成器之前,有必要简单介绍下构建者模式Builder Pattern

由于生成器涉及大量的配置,之前旧版本普通为普通javabean配置方式,当配置属性足够多时,弊端就显现了代码不易编写、不易阅读。于是引入构建者设计模式来解决以上问题。

其实本质是一样,就是换了一个写法大家来观摩下:https://baomidou.com/guide/generator-new.html

普通的生成方式

package com.atguigu.mp.mp01.builder;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CodeGenerator {
    /**
     * 数据源配置
     */
    private String dataSource;
    /**
     * 包配置
     */
    private String packageConfig;
    /**
     * 策略配置
     */
    private String strategyConfig;

    /**
     * 执行代码生成
     */
    public void execute() {
        System.out.println(
                "CodeGenerator [dataSource=" + dataSource + ", packageConfig=" + packageConfig + ", strategyConfig="
                        + strategyConfig + "]");
        ;
    }
}

构建者模式

package com.atguigu.mp.mp01.builder;

/**
 * 代码生成器构建模式
 */
public class CodeGeneratorBuilder {
    /**
     * codeGenerator
     */
    private CodeGenerator codeGenerator;

    public CodeGeneratorBuilder(CodeGenerator codeGenerator) {
        this.codeGenerator = codeGenerator;
    }

    public static CodeGeneratorBuilder create(String dataSource) {
        CodeGenerator cg = new CodeGenerator();
        cg.setDataSource(dataSource);
        return new CodeGeneratorBuilder(cg);
    }

    public CodeGeneratorBuilder packageConfig(String packageConfig) {
        codeGenerator.setPackageConfig(packageConfig);
        return this;
    }

    public CodeGeneratorBuilder strategyConfig(String strategyConfig) {
        codeGenerator.setStrategyConfig(strategyConfig);
        return this;
    }

    public void execute() {
        codeGenerator.execute();
    }
}

测试

package com.atguigu.mp.mp01.builder;

public class Test {
    public static void main(String[] args) {
        // 普通模式
        CodeGenerator codeGenerator = new CodeGenerator();
        codeGenerator.setDataSource("mysql");
        codeGenerator.setPackageConfig("com.atguigu.mp.mp01");
        codeGenerator.setStrategyConfig("驼峰命名");
        codeGenerator.execute();

        // 构建者模式
        CodeGeneratorBuilder.create("mysql")
                .packageConfig("com.atguigu.mp.mp01")
                .strategyConfig("驼峰命名")
                .execute();
    }
}

6.1.2 代码生成测试

倒入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

代码

package com.atguigu.mp.mp01.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;

public class CodeGenerator {
    static final String URL = "jdbc:mysql://localhost:3306/mp?useSSL=false&useUnicode=true&characterEncoding=utf-8";

    public static void main(String[] args) {
        FastAutoGenerator.create(URL, "root", "12345678")
                // 全局配置
                .globalConfig(builder -> builder.outputDir("src/main/java"))
                // 策略配置
                .strategyConfig(builder -> builder.addInclude("user"))
                // 执行
                .execute();
    }
}

6.2 数据库配置

全部配置项地址:数据库配置

  • 添加指定数据库驱动依赖jar
  • 数据库网络可用,云主机注意防火墙、网络安全组对应端口是否开放
  • 严禁使用数据库关键词命名
  • 务必表字段添加注释
  • 多schema非默认必须指定

6.3 包配置

全部配置项地址:包配置

  • 生成当前项目目录System.getProperty("user.dir") + "/src/main/java"
  • 定义父目录parent
  • 自定义输出pathInfo
  • 模块化输出指定parent动态路径

例子:

package com.atguigu.mp.mp01.pkg;

import java.text.MessageFormat;
import java.util.Collections;

import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;

public class Generater {
    public static void main(String[] args) {
        String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
        FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
                // 全局配置
                .globalConfig(builder -> builder
                        .author("Shibuya Kanon")
                        .commentDate("yyyy-MM-dd hh:mm:ss")
                        .outputDir(pkgPath)
                        .fileOverride()
                        .disableOpenDir())
                // 包配置
                .packageConfig(builder -> builder.parent("com.atguigu.mp.mp01")
                        .pathInfo(Collections.singletonMap(OutputFile.mapperXml,
                                MessageFormat.format("{0}/src/main/resources/mappers",
                                        System.getProperty("user.dir")))))
                // 策略配置
                .strategyConfig(builder -> builder.addInclude("user"))
                // 执行
                .execute();
    }
}
package com.atguigu.mp.mp01.pkg;

import java.text.MessageFormat;

import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;

public class Generater {
    public static void main(String[] args) {
        String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
        FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
                // 全局配置
                .globalConfig(builder -> builder
                        .author("Shibuya Kanon")
                        .commentDate("yyyy-MM-dd hh:mm:ss")
                        .outputDir(pkgPath)
                        .fileOverride()
                        .disableOpenDir())
                // 包配置
                .packageConfig((scanner, builder) -> builder.parent("com.atguigu.mp" + scanner.apply("请输入你的模块名称:")))
                // 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(scanner.apply("请输入表名:")))
                // 执行
                .execute();
    }
}

6.4 模版配置

全部配置:模版配置

  • 禁用模块disable
  • 自定义模板,先要了解下什么是模板引擎?
  • 渲染模板的参数怎么查看?
package com.atguigu.mp.mp01.template;

import java.text.MessageFormat;

import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.TemplateType;

public class TemplateGenerator {
    public static void main(String[] args) {
        String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
        FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
                // 全局配置
                .globalConfig(builder -> builder
                        .author("Shibuya Kanon")
                        .commentDate("yyyy-MM-dd hh:mm:ss")
                        .outputDir(pkgPath)
                        .fileOverride()
                        .disableOpenDir())
                // 模板配置
                .templateConfig(builder -> builder.disable(TemplateType.XML, TemplateType.CONTROLLER))
                // 包配置
                .packageConfig((scanner, builder) -> builder.parent("com.atguigu.mp" + scanner.apply("请输入你的模块名称:")))
                // 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(scanner.apply("请输入表名:")))
                // 执行
                .execute();
    }
}

6.5 注入配置

方法 说明 示例
beforeOutputFile(BiConsumer<TableInfo, Map<String, Object>>) 输出文件之前消费者
customMap(Map<String, Object>) 自定义配置 Map 对象 Collections.singletonMap("test", "baomidou")
customFile(Map<String, String>) 自定义配置模板文件 Collections.singletonMap("test.txt", "/templates/test.vm")
new InjectionConfig.Builder()
    .beforeOutputFile((tableInfo, objectMap) -> {
        System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
    })
    .customMap(Collections.singletonMap("test", "baomidou"))
    .customFile(Collections.singletonMap("test.txt", "/templates/test.vm"))
    .build();

7 插件扩展

7.1 Mybatis插件机制简介

1)插件机制

Mybatis通过插件(Interceptor)可以做到拦截四太对象相关方法的执行,根据需求,完成相关数据的动态改变。

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

2)插件原理

四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll() , 会经过每个插件对象的 plugin() 方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四太对象的方法需要经过代理。

7.2 分页插件 PaginationInnerInterceptor

启用分页插件

package com.atguigu.mp.mp01.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

具体的使用方法之前已经有过样例了,这里就直接给一下官网的文档了。

7.2.1 支持的数据库

  • mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

👉 如果没有支持你需要的数据库,点击参考 Pull Request 我们会第一时间审核(opens new window)

7.2.2 属性介绍

属性名 类型 默认值 描述
overflow boolean false 溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法)
maxLimit Long 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法)
dbType DbType 数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法)
dialect IDialect 方言实现类(参见 插件#findIDialect 方法)

建议单一数据库类型的均设置 dbType

7.2.3 自定义的 mapper#method 使用分页

IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
// or (class MyPage extends Ipage<UserVo>{ private Integer state; })
MyPage selectPageVo(MyPage page);
// or
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
    SELECT id,name FROM user WHERE state=#{state}
</select>

如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage

如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);

如果 xml 需要从 page 里取值,需要 page.属性 获取

7.2.4 其他

生成 countSql 会在 left join 的表不参与 where 条件的情况下,把 left join 优化掉。所以建议任何带有 left join 的sql,都写标准sql,即给于表一个别名,字段也要 别名.字段

7.3 不规范SQL拦截器

IllegalSQLInnerInterceptor:不规范SQL拦截器。

由于开发人员水平参差不齐,即使订了开发规范很多人也不遵守,SQL是影响系统性能最重要的因素,所以拦截掉不规范SQL语句。

拦截SQL类型的场景:

  1. 必须使用到索引,包含left join连接字段,符合索引最左原则

    • 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据
    • 如果检查到使用了索引,SQL性能基本不会太差
  2. SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做,SQL尽量单表执行的好处:

    • 查询条件简单、易于开理解和维护
    • 扩展性极强;(可为分库分表做准备)
    • 缓存利用率高
  3. where条件为空

  4. where条件使用了 !=

  5. where条件使用了 not 关键字

  6. where条件使用了 or 关键字

  7. where条件使用子查询

7.3.1 测试案例

配置类添加插件

package com.atguigu.mp.mp01.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
        return interceptor;
    }
}

测试执行,发现此SQL没有添加条件,被拦截

@Test
void interceptors() {
   List<User> users = userMapper.selectList(new QueryWrapper<>());
   System.out.println("users = " + users);
}

测试执行,发现此SQL没有添加条件,被拦截

Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@320ff86f] was not registered for synchronization because synchronization is not active
2022-01-10 16:11:20.169  INFO 10213 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-10 16:11:20.365  INFO 10213 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@47865820 wrapping com.mysql.cj.jdbc.ConnectionImpl@23321be7] will not be managed by Spring
检查SQL是否合规,SQL:SELECT  id,name,age,email  FROM user
original SQL: SELECT  id,name,age,email  FROM user
SQL to parse, SQL: SELECT  id,name,age,email  FROM user
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@320ff86f]

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件
### The error may exist in com/atguigu/mp/mp01/mapper/UserMapper.java (best guess)
### The error may involve com.atguigu.mp.mp01.mapper.UserMapper.selectList
### The error occurred while executing a query
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件
	... 

7.4 乐观锁插件

com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor

如果想实现如下需求:当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁的实现原理:

  1. 取出记录时,获取当前 version
  2. 更新时,带上这个 version
  3. 执行更新时,set version = yourVersion + 1 where version = yourVersion
  4. 如果 version 不对,就更新失败。

@Version用于注解实体字段,必须要有。

7.4.1 测试使用

增加数据库字段version

ALTER TABLE user
    ADD COLUMN version int DEFAULT NULL COMMENT '乐观锁'

代码中对version字段增加@Version注解

/**
 * 乐观锁
 */
@Version
private Integer version;

测试

@Test
void opLock() {
    User user = new User();
    user.setId(4L);
    user.setAge(50);
    user.setName("Sammy");
    user.setVersion(1);

    userMapper.updateById(user);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fde6f05] was not registered for synchronization because synchronization is not active
2022-01-10 16:25:54.141  INFO 10436 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-10 16:25:54.330  INFO 10436 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@967022668 wrapping com.mysql.cj.jdbc.ConnectionImpl@4404a6b] will not be managed by Spring
检查SQL是否合规,SQL:UPDATE user  SET name=?,
age=?,

version=?  WHERE id=?   AND version=?
original SQL: UPDATE user  SET name=?,
age=?,

version=?  WHERE id=?   AND version=?
SQL to parse, SQL: UPDATE user  SET name=?,
age=?,

version=?  WHERE id=?   AND version=?
parse the finished SQL: UPDATE user SET name = ?, age = ?, version = ? WHERE id = ? AND version = ?
==>  Preparing: UPDATE user SET name=?, age=?, version=? WHERE id=? AND version=?
==> Parameters: Sammy(String), 50(Integer), 2(Integer), 4(Long), 1(Integer)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fde6f05]

7.5 其他

其余的插件可以在这里看到

8 自定义全局操作

根据 MyBatis Plus 的 AbstractSqlInjector 可以自定义各种你想要的SQL,注入到全局中,相当于自定义 MyBatis Plus 自动注入的方法。

8.1 AbstractSqlInjector

从源代码中可以看到一个抽象方法

/**
 * <p>
 * 获取 注入的方法
 * </p>
 *
 * @param mapperClass 当前mapper
 * @return 注入的方法集合
 * @since 3.1.2 add  mapperClass
 */
public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass,TableInfo tableInfo);

8.2 自定义注入器的应用1——逻辑删除

增加字段is_deleted

ALTER TABLE `user`
    ADD COLUMN `is_deleted` boolean

application.yml增加配置

# 配置日志
mybatis-plus:
  global-config:
    db-config:
      logic-delete-value: 1
      logic-not-delete-value: 0

实体类增加注解

/**
 * 是否删除
 */
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Boolean isDeleted;

测试

@Test
void logicDelete() {
    int delete = userMapper.deleteById(1L);
    System.out.println(delete);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8383a14] was not registered for synchronization because synchronization is not active
2022-01-10 17:56:09.772  INFO 11606 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-10 17:56:09.963  INFO 11606 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1640529691 wrapping com.mysql.cj.jdbc.ConnectionImpl@1857fe6c] will not be managed by Spring
检查SQL是否合规,SQL:UPDATE user SET is_deleted=1 WHERE id=?  AND is_deleted=0
original SQL: UPDATE user SET is_deleted=1 WHERE id=?  AND is_deleted=0
SQL to parse, SQL: UPDATE user SET is_deleted=1 WHERE id=?  AND is_deleted=0
parse the finished SQL: UPDATE user SET is_deleted = 1 WHERE id = ? AND is_deleted = 0
==>  Preparing: UPDATE user SET is_deleted=1 WHERE id=? AND is_deleted=0
==> Parameters: 1(Long)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8383a14]

9 公共字段自动填充

9.1 元数据处理器接口

com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

元对象字段填充控制器抽象类,实现公共字段自动写入

所有入参的 MetaObject 必定是 entity 或其子类的 MetaObject


MetaObject:元对象,是 MyBatis 提供的一个用于更加方便、更加优雅的访问对象的属性,给对象的属性设置值的一个对象.还会用于包装对象。支持对 Object、Map、 Collection 等对象进行包装。

9.2 开发步骤

  1. 注解填充字段 @TableField(fill = FieldFill.INSERT) 查看 TableFill
  2. 自定义公共字段填充处理器。
  3. MP全局注入自定义公共字段填充处理器,

9.3 测试

在相关字段上增加注解

/**
 * 是否删除
 */
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Boolean isDeleted;
/**
 * 姓名
 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String name;

实现元数据处理器接口

package com.atguigu.mp.mp01.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;

@Slf4j
public class CustomMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("===================insertFill");
        // 添加逻辑删除的默认值
        this.setFieldValByName("name", "Jasmine", metaObject);
        this.setFieldValByName("isDeleted", false, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("===================updateFill");
        this.setFieldValByName("name", "Jasmine", metaObject);
    }
}

测试

@Test
void insert() {
    User user = new User();
    user.setAge(10);
    user.setEmail("eddie@qq.com");
    user.setVersion(1);

    userMapper.insert(user);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3321291a] was not registered for synchronization because synchronization is not active
2022-01-10 18:20:03.184  INFO 11885 --- [           main] c.a.m.m.handler.CustomMetaObjectHandler  : ===================insertFill
2022-01-10 18:20:03.188  INFO 11885 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-01-10 18:20:03.376  INFO 11885 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1853751897 wrapping com.mysql.cj.jdbc.ConnectionImpl@646e6d07] will not be managed by Spring
==>  Preparing: INSERT INTO user ( name, age, email, version, is_deleted ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: Jasmine(String), 10(Integer), eddie@qq.com(String), 1(Integer), false(Boolean)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3321291a]

本文作者:我係死肥宅

本文链接:https://www.cnblogs.com/iamfatotaku/p/15785516.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   我係死肥宅  阅读(680)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.

作曲 : Reol

作词 : Reol

fade away...do over again...

fade away...do over again...

歌い始めの一文字目 いつも迷ってる

歌い始めの一文字目 いつも迷ってる

どうせとりとめのないことだけど

伝わらなきゃもっと意味がない

どうしたってこんなに複雑なのに

どうしたってこんなに複雑なのに

噛み砕いてやらなきゃ伝わらない

ほら結局歌詞なんかどうだっていい

僕の音楽なんかこの世になくたっていいんだよ

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.

目の前 広がる現実世界がまた歪んだ

目の前 広がる現実世界がまた歪んだ

何度リセットしても

僕は僕以外の誰かには生まれ変われない

「そんなの知ってるよ」

気になるあの子の噂話も

シニカル標的は次の速報

麻痺しちゃってるこっからエスケープ

麻痺しちゃってるこっからエスケープ

遠く遠くまで行けるよ

安定なんてない 不安定な世界

安定なんてない 不安定な世界

安定なんてない きっと明日には忘れるよ

fade away...do over again...

fade away...do over again...

そうだ世界はどこかがいつも嘘くさい

そうだ世界はどこかがいつも嘘くさい

綺麗事だけじゃ大事な人たちすら守れない

くだらない 僕らみんなどこか狂ってるみたい

本当のことなんか全部神様も知らない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

僕は気にしない 君は気付かない

何処にももういないいない

Everybody don't know why.

Everybody don't know why.

Everybody don't know much.

忘れていく 忘れられていく

We don't know,We don't know.