MyBatis-Plus框架(千峰学习笔记)

简介

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

支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,几乎都能支持,我们本次课程 主要使用的是MySql数据库

官方网址: https://www.baomidou.com/

为什么要使用

使用MyBatis-Plus的目的是为了大大简化持久层的开发,它可以帮助我们节省大量工作时间,所 有的CRUD代码都可以自动化完成,简单理解学习MyBatis-Plus的目的就是为了 “偷懒”

使用前提:

1. 需要掌握SSM

2. 掌握Maven

3. 掌握SpringBoot

快速开始

具体使用步骤如下:

1、导入对应的依赖

2、对应的配置

3、代码的编写

数据初始化

创建一个User用户表用于测试(直接采用官网提供的测试数据)

create database db_mybatisPlus character set utf8;

use db_mybatisPlus;

DROP TABLE IF EXISTS user;

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

DELETE FROM user;

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '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');

创建SpringBoot项目

1、导入依赖

        <!--  MyBatisPlus  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--  MySql  -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--  lombok  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>

注意:一般使用MyBatis-Plus以后就不要再导入MyBatis了,尽量避免它俩同时存在,有可能 会出现一些版本差异等的问题

2、配置application.properties

# MyBatis 8 需要配置时区 serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3307/db_mybatisplus?
useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8

传统方式配置到此,我们如果需要完成业务,就需要完成以下步骤:

● 实体类-dao(连接MyBatis,配置核心配置以及Mapper映射)

使用MyBatis-Plus也是三个步骤,但是却大大简化操作:

● 实体类-mapper-使用

Lombok

Lombok是一个java库,可以自动插入到你的编辑器和构建工具中,让你的java变得更加简单。 再也不用写其getter或equals方法了,只需一个注解,你的类就可以拥有一个功能齐全的 JavaBean

具体功能

为了在IDEA中支持Lombok功能,需要安装Lombok插件,打开File>Settings>安装Lombok插 件,安装以后才能支持Lombok提供的功能。

Lombok提供的常用注解:

● @Getter/@Setter:用在类或属性上,用在类上可以作用于这个类的所有属性,写在属性 上只作用于属性名,再也不用自己手写setter和getter方法了

● @ToString:用在类上,可以自动覆写toString方法,当然还可以加其他参数,例如 @ToString(exclude=”id”)排除id属性,或者@ToString(callSuper=true, includeFieldNames=true)调用父类的toString方法,包含所有属

● @EqualsAndHashCode:用在类上,自动生成equals方法和hashCode方法

● @NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor:用 在类上,自动生成无参构造和使用所有参数的构造函数以及把所有@NonNull属性作为参数 的构造函数

● @RequiredArgsConstructor主要是对当前类中带有final 的属性进行属性注入

● @Data:注解在类上,相当于同时使用了 @Getter、 @ToString、 @Setter和 @EqualsAndHashCode、 @RequiredArgsConstrutor这些注解,对于 POJO类 十分有用

● @Value:用在类上,是@Data的不可变形式,相当于为属性添加final声明,只提供getter 方法,而不提供setter方法

● @Slf4j: 自动创建Slf4j日志对象log,用于记录系统日志信息

具体步骤

1、创建实体类User

package com.qf.mybatisplusdemo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

2、创建UserMapper

按照传统的MyBatis的方式此时我们需要做的是创建dao层,也就是需要有Mapper接口和对应的 Mapper.xml映射文件,但是一旦使用MyBatis-Plus以后,我们只需要写一个mapper接口继承 Plus提供的BaseMapper接口即可,此时就已经完成了几乎所有的CRUD的操作

package com.qf.mybatisplusdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qf.mybatisplusdemo.entity.User;
import org.springframework.stereotype.Repository;

/**
 * Description:
 */
@Repository
public interface UserMapper extends BaseMapper<User> {
    //自定义CRUD方法
    /**
     * BaseMapper中包含常用的,或者叫做通用的crud方法,但是如果我们自己的业务有
     * 一些特殊的CRUD操作,我们可以再这个接口中自定义,那么这个操作就和之前的MyBatis操作是一样的了
     */
}

我们可以查看BaseMapper的源码,不难看出,所有的crud功能MyBatis-Plus通过此类型已经帮 助我们实现了

3、开启扫描

为了能够让SpringBoot扫描并且识别此组件,我们需要在SpringBoot启动类上开启Mapper接口 扫描功能,添加@MapperScan()注解

//开启扫描,注意包名不要写错
@MapperScan("com.qf.mybatisplusdemo.mapper")
@SpringBootApplication
public class MyBatisPlusDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisPlusDemoApplication.class, args);
    }
}

4、测试

我们在测试类中通过单元测试来测试一下MyBatisPlus提供的通用方法

package com.qf.mybatisplusdemo;

import com.qf.mybatisplusdemo.entity.User;
import com.qf.mybatisplusdemo.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

/**
 * Description:
 */
@SpringBootTest
public class MyBatisPlusDemoApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    void contextLoads(){
        //查询全部用户
        // 此方法需要我们传递Wrapper对象,此对象是条件构造器,我们现在要查询全部用户先不用,所以传null即可
        List<User> list = userMapper.selectList(null);
        list.forEach(user -> System.out.println(user));
    }
}

日志配置

其实这个道理和我们再用MyBatis的时候是一样的,我们希望知道具体执行的SQL语句,包括具 体的执行过程,所以此时我们需要通过日志的方式来完成。

在配置文件中添加日志配置

# 配置日志
mybatis-plus.configuration.logimpl=org.apache.ibatis.logging.stdout.StdOutImpl

重新执行查询最终效果

BaseMapper完成CRUD

BaseMapper中提供了常用的CRUD方法,我们需要测试一下其中复杂的方法,比如说条件查询 等

那我们就通过单元测试来验证一下

删除功能

BaseMapper中提供了5个删除方法,图中第三个方法的参数Wrapper是条件构造器,这个构造 器后续我们在探讨

第一个方法根据id删除,此方法跳过,因为比较简单,这里演示第二个与第四个方法

根据Map删除对应数据

    @Test
    public void deleteTest(){
        Map<String, Object> map = new HashMap<>();
        map.put("name","jerry");
        map.put("age",20);
        int num = userMapper.deleteByMap(map);
        System.out.println(num);
    }

根据多个id进行批量删除

    @Test
    public void deleteTest(){
        List<Long> ids = new ArrayList<>();
        ids.add(1661372335489392642L);
        ids.add(5L);
        int num = userMapper.deleteBatchIds(ids);
        System.out.println(num);
    }

修改功能

修改方法BaseMapper中提供了两个

根据id修改

此方法传入的参数是实体类对象,需要包含id和其他修改的内容

    @Test
    public void updateTest(){
        User user = new User();
        user.setId(4L);
        user.setName("李四");
        int num = userMapper.updateById(user);
        System.out.println(num);
    }

查询功能

查询功能是我们最常用的功能,所以BaseMapper中提供的查询方法也非常多,但是基本操作都 非常简单

其中以下图中的两个方法,分别是通过单个id查询数据,和id集合来查询数据,这两个方法不做 演示

此方法为根据Map中传递的条件进行查询

    @Test
    public void selectTest(){
        Map<String,Object> map = new HashMap<>();
        map.put("id",3L);
        map.put("name","Tom");
        List<User> users = userMapper.selectByMap(map);
        users.forEach(user -> System.out.println(user));
    }

此方法Wrapper条件构造器传递参数为null表示查询全部数据

注意:所有的CRUD方法中只要涉及到Wrapper条件构造器的如果不需要使用都可以传入为 null,但是要注意一般查询可以传入,但是修改和删除一般不可以,因为如果删除和修改没有条 件会导致影响全部数据

新增功能

    @Test
    public void insertTest(){
        User user = new User();
        user.setName("张三");
        user.setAge(20);
        user.setEmail("10000@qq.com");
        int num = userMapper.insert(user);//自动生成id,并且id会自动回填
        System.out.println(num);
    }

但是各位我们要注意的是此时看日志大家会发现我们的id并没有给任何的策略但是这里却是自动 生成了一段id

其实这里MyBatis-Plus是通过雪花算法来进行自动生成的id,但是要注意:我们的主键必须叫 id,并且必须是包装类型,否则不生效

主键生成策略

在我们业务量不大的时候,单库单表完全可以支持现在的业务,数据再大一点读写分离也算 OK。但是随着数据量的增长,单库单表终究是抗不住的。那就需要分库分表。分库分表后肯定 不能依赖分表中的自增主键。

因此需要一个 生成全局唯一ID的 主键生成策略

常见的策略

● 数据库自增长序列或字段:最常见的方式。利用数据库,全数据库唯一。

● UUID:常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

● Redis生成ID:当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成 ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。

● Twitter的snowflake(雪花)算法:snowflake是Twitter开源的分布式ID生成算法,结果是一 个long型的ID。

● zookeeper生成唯一ID:zookeeper主要通过其znode数据版本来生成序列号,可以生成32 位和64位的数据版本号,客户端可以使用这个版本号来作为唯一的序列号。

雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。

其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器 ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有 一个符号位,永远是0。

至于算法的具体源码学习,各位可以关注我们发的算法课程来详细学习

主键生成策略

这其中涉及到MyBatis-Plus中提供的一个注解@TableId

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

源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
    String value() default "";

    IdType type() default IdType.NONE;
}

这里我们要关注IdType这个枚举类型

public enum IdType {
    AUTO(0),
    NONE(1),
    INPUT(2),
    ASSIGN_ID(3),
    ASSIGN_UUID(4);

    private final int key;

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

    public int getKey() {
        return this.key;
    }
}
描述
AUTO 数据库 ID 自增,必须设置数据库自增主键
NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
INPUT insert 前自行 set 主键值
ASSIGN_ID 默认分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口 IdentifierGenerator的方法 DefaultIdentifierGenerator雪花算法)
ASSIGN_UUID

分配 UUID,主键类型为 String(since 3.3.0),使用接口

IdentifierGenerator的方法 nextUUID(默认 default 方法)

主键自增演示(注意:一定要把表改为主键自增)

User类型

@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

最终效果:

自定义CRUD

MyBatis-Plus的BaseMapper本身提供了很多通用的CRUD方法,但是由于我们的业务问题有的 时候必须要自定义一些方法,那么该如何实现那?

其实主要记住MyBatis-Plus是在MyBatis的基础之上只做增强不做修改的即可,所以如果想要实 现自定义的方法,具体操作其实和MyBatis没有什么区别

配置

如果我们需要进行复杂映射就可能会涉及到映射文件mapper.xml那么这个文件位置的配置,我 们可以在properties配置文件中进行配置

其实我们会发现此配置有一个默认配置,就是在类路径下的mapper目录下的任意位置(包含下 一级目录),所以其实我们用这个默认配置即可

# 指定mapper文件位置
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml

注解方式

根据name查询用户信息

    /**
     * 根据name查询数据
     * @param name
     * @return
     */
    @Select("select * from user where name=#{name}")
    List<User> selectByName(String name);

测试

    @Test
    public void selectByNameTest(){
        List<User> users = userMapper.selectByName("李四");
        users.forEach(user -> System.out.println(user));
    }

xml方式

这种方式,我们需要在resource目录中新建一个mapper目录,然后新建UserMapper.xml文件

注意:要把@Select注解注释掉

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mybatisplusdemo.mapper.UserMapper">
    <!--  List<User> selectByName(String name);  -->

    <select id="selectByName" resultType="com.qf.mybatisplusdemo.entity.User">
        select * from user where name=#{name}
    </select>
</mapper>

结果:

IService接口

说明:

● 通用Service CRUD封装IService (opens new windows)接口,进一步封装CRUD采用 get查询单行 remove删除 list查询集合 page分页 前缀命名方式区分 Mapper 层避免混淆,

● 泛型 T 为任意实体对象

● 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承 Mybatis-Plus 提供的基类

● 对象 W rapper 为 条件构造器

其实一般情况下,由于项目业务的复杂程度,我们都会使用自定义Service方法,那么这些如果我 们想即使用通用的IService接口提供的方法,又有自定义的方法的话,我们可以参考IService接口 的实现类ServiceImpl

IService源码

这里我们可以关注一下具体有哪些方法

ServiceImpl源码

这里我们要关注当前类型的声明方式,我们可以模仿

m:mapper对象

t:实体

自定义Service实现类

创建UserService接口以及对应实现类,注意包结构

UserService接口

//自定义接口继承通用Iservice接口
public interface UserService extends IService<User> {
    //自定义Service方法
}

UserServiceImpl实现类

//按照ServiceImpl实现类编写自己的业务层实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    //自定义service方法实现
}

测试通用Service方法

批量添加

直接在测试类中进行调用方法即可

@SpringBootTest
public class MyBatisPlusDemoApplicationTests {
    @Autowired
    private UserService userService;

    @Test
    public void insertMoreTest(){
        //批量添加测试
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User user = new User();
            user.setName("qf"+i);
            user.setAge(18+i);
            user.setEmail("1000"+i+"@qq.com");
            users.add(user);
        }
        boolean b = userService.saveBatch(users);
        System.out.println(b);
    }
}

自动填充处理

我们一般按照阿里巴巴开发手册要求:所有数据库的表几乎都要配置以下两个字段,并要求自动 化处理:

● 创建时间(gmt_create、create_time)

● 修改时间(gmt_modified、update_time)

要完成这两个字段的自动化处理有两种方式

1、数据库级别

2、代码级别

前提

修改user表,添加这两个字段

测试数据库级别

注意:此种方式工作中不可以使用,因为工作中是不允许修改数据库的

直接执行更新操作

    @Test
    public void updateTest1(){
        User user = new User();
        user.setId(4L);
        user.setName("王五");
        int num = userMapper.updateById(user);
        System.out.println(num);
    }

测试代码级别

准备:清除数据库所有的默认值和更新操作

这里我们需要使用到MyBatis-Plus提供的另外一个注解@TableField(.. fill = FieldFill.INSERT)

User类型

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableField() //注解填充字段
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

这里我们可以看一下此注解的源码,这里我们要关注以下内容

其中FieldFill是:字段填充策略枚举类

我们现在要求的策略就是,新增数据时,这两个字段都要更新,同时修改的时候update_time字 段要更新,所以具体策略设置如下

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

 最后,我们需要实现并重写处理器MetaObjectHandler来完成具体策略

package com.qf.mybatisplusdemo.handler;

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

import java.time.LocalDateTime;

/**
 * Description:
 */
@Slf4j
@Component
public class MyMetaObjectHandle implements MetaObjectHandler {
    //插入数据时的填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill ....");
        this.strictInsertFill(metaObject,"createTime", LocalDateTime.class,LocalDateTime.now());
        this.strictInsertFill(metaObject,"updateTime", LocalDateTime.class,LocalDateTime.now());
    }

    //更新时的填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("update insert fill ....");
        this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());
    }
}

 注意:

● 填充原理是直接给 entity的属性设置值!!!

● 字段必须声明 TableField注解,属性 fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段

● 填充处理器MyMetaObjectHandler在Spring Boot中需要声明@Component或@Bean注入

演示插入

演示修改

最终查看数据库

乐观锁插件

乐观锁与悲观锁

在程序世界中,乐观锁和悲观锁的最终目的都是为了保证线程安全,避免在并发场景下的资源竞 争问题。但是,相比于乐观锁,悲观锁对性能的影响更大!

字面意思理解:

乐观锁:总是假设最好的情况,认为别人都是友好的,所以每次获取数据的时候不会上锁,但更 新数据那一刻会判断数据是否被更新过了,如果数据的值跟自己预期一样的话,那么就可以正常 更新数据。

悲观锁:悲观锁就好像一个有迫害妄想症的患者,总是假设最坏的情况,每次拿数据的时候都以 为别人会修改,所以每次拿数据的时候都会上锁,直到整个数据处理过程结束,其他的线程如果 要拿数据就必须等当前的锁被释放后才能操作。

MyBatis-Plus给出的实现方式

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

● 更新时,带上这个 version

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

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

具体实现

首先第一件事情,需要在当前User表中添加版本version字段

ALTER TABLE db_mybatisplus.`user` ADD version INT DEFAULT 1 NULL COMMENT 
'乐观锁';

更新实体类,并且添加乐观锁注解

package com.qf.mybatisplusdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

/**
 * Description:
 */
@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Version//乐观锁注解
    private Integer version;
}

接下里注册组件(乐观锁拦截器),具体组件在官网上,我们可以直接复制粘贴即可

首先我们需要创建一个配置类,此配置类型是MyBatis-Plus的配置类,所以一切有关MyBatis-Plus的配置都可以放到这里,这也是推荐的方式

新建:config.MyBatisPlusConfig

@Configuration
//开启扫描,注意包名不要写错(有了MyBatisPlus的配置类,我们就可以把所有的相关配置都写在这里)
@MapperScan("com.qf.mybatisplusdemo.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //配置MyBatis-Plus插件(拦截器)
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //具体配置插件(乐观锁)
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}

单线程演示

在测试类中编写测试方法,来执行一个修改操作

    //成功
    @Test
    public void mybatisPlusInterceptorTest() {
        //查询用户信息
        User user = userMapper.selectById(3L);
        //修改此用户信息
        user.setName("abc");
        user.setAge(10);
        //执行更新
        userMapper.updateById(user);
    }

查看结果,确实这里在修改时会增加判断version条件

多线程争抢演示

在多线程时,我们模拟第二个线程抢先完成修改操作,让乐观锁生效

    //失败
    @Test
    public void mybatisPlusInterceptorTest2() {
        //----------------线程1----------------------
        //查询用户信息(查出version)
        User user1 = userMapper.selectById(3L);
        //修改此用户信息
        user1.setName("abc");
        user1.setAge(10);

        //---------------线程2------------------------
        User user2 = userMapper.selectById(3L);
        user2.setName("efg");
        user2.setAge(15);
        //先执行更新线程2(模拟线程2抢先完成修改操作)
        userMapper.updateById(user2);
        //再执行更新线程1 (如果乐观锁不存在会覆盖线程1的值)
        //可以尝试进行多次提交
        userMapper.updateById(user1);
    }

此时我们可以通过日志来进行查看,会发现,执行了两次的查询,以及两次的修改,并且按照执 行顺序来说,如果没有乐观锁应该是线程1覆盖线程2的值,但是此时乐观锁生效,所以最终的结 果还是线程2抢先执行之后的值

分页插件

MyBatisPlus还提供了分页插件,可以帮助我们快速的完成分页操作,简化原生操作

其实使用分页和乐观锁插件一样,都需要引入组件,步骤和乐观锁插件是一样的,我们都需要在 配置类中配置具体插件

MyBatisPlusConfig

@Configuration
//开启扫描,注意包名不要写错(有了MyBatisPlus的配置类,我们就可以把所有的相关配置都写在这里)
@MapperScan("com.qf.mybatisplusdemo.mapper")
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        //配置MyBatis-Plus插件(拦截器)
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //具体配置插件(乐观锁)
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
}

Page:该类继承了 IPage 类,实现了 简单分页模型 如果你要实现自己的分页模型可以继承 Page 类或者实现 IPage 类

进行单元测试

如果测试的单元测试方法为业务层方法,那么前端传递具体用户点击的页码就能实现分页功能

    @Test
    public void PaginationInnerInterceptorTest(){
        //简单分页模型  current:第几页   size:显示几条数据
        //底层逻辑就是使用Limit分页的公式 (index-1)*pageSize
        Page<User> page = new Page<>(1, 3);
        //条件构造器Wrapper目前没有就写null
        userMapper.selectPage(page,null);
    }

第一页分页,Limit语句可以只有一个参数即可

Page对象常用方法

    @Test
    public void PaginationInnerInterceptorTest(){
        //简单分页模型  current:第几页   size:显示几条数据
        //底层逻辑就是使用Limit分页的公式 (index-1)*pageSize
        Page<User> page = new Page<>(1, 3);
        //条件构造器Wrapper目前没有就写null
        userMapper.selectPage(page,null);
        //获取记录
        List<User> users = page.getRecords();
        users.forEach(user -> System.out.println(user));
        System.out.println(page.getPages());  //获取总页数
        System.out.println(page.getTotal());  //获取总数据量
        System.out.println(page.hasNext());  //是否有下一页
        System.out.println(page.hasPrevious());  //是否有上一页
    }

逻辑删除

讲到这里基本上一些增删改查,包括分页等操作我们都已经搞定了,那么在企业中我们对于删 除,一般情况下是不会真正的把数据删除掉的,而是会进行 逻辑删除

所以大家会发现一些系统,管理员可以看到已经删除的用户信息,实际上这就是一种逻辑删除, 也就是根据数据表中的类似于:deleted的属性来标记用户删除,而并非真正删除。

逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。

具体操作

在User表中添加字段deleted

 ALTER TABLE db_mybatisplus.`user` ADD deleted INT DEFAULT 0 NULL COMMENT 
'逻辑删除 0表示未删除 1表示删除';

更新实体类

在实体类中给对应deleted属性配置注解@TableLogic

此注解有两个属性(可以默认不写)

● value:默认逻辑未删除值

● delval:默认逻辑删除值

@Data //@Data : 注解在类上,提供类的get、set、equals、hashCode、canEqual、toString、无参构造方法,没有有参构造方法。
@AllArgsConstructor // 全部参数构造方法
@NoArgsConstructor //无参数构造方法
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer age;
    private String email;
    //插入操作更新字段策略
    @TableField(fill = FieldFill.INSERT) //注解填充字段
    private LocalDateTime createTime;
    //更新操作更新字段策略
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @Version//乐观锁注解
    private Integer version;
    @TableLogic(value = "0",delval = "1") //逻辑删除(这两个值也可以不设置)
    private Integer deleted;
}

测试删除

    @Test
    public void deletedTest(){
        userMapper.deleteById(2L);
    }

逻辑删除会把删除语句变成修改语句

注意:查询的时候因为添加了@TableLogic注解,所以会自动跳过被标记的数据

条件构造器

MyBatis-Plus提供了强大的条件构造器。通过条件构造器可以写一些复杂的SQL语句,从而提高 开发效率。

查询mybatisPlus源码可以看到,条件构造器wrapper继承情况

● Wrapper:条件构造器,最顶端的一个类

    ○ AbstractWrapper:用于sql语句条件的封装,主要是封装where条件

         ■ QueryWrapper:查询条件封装(一般删除的条件也使用QueryWrapper)

         ■ UpdateWrapper:更新条件封装

         ■ AbstractLambdaWrapper:具有Lambda语法的条件封装

              ■ LambdaQueryWrapper:具有Lambda语法查询条件封装

              ■ LambdaUpdateWrapper:具有Lambda语法更新条件封装

所有的条件使用方法,在官网中有很详细的介绍

具体地址: https://www.baomidou.com/pages/10c804/#abstractwrapper

QueryWrapper

一般我们比较常用的就是这个QueryWrapper,因为查询的业务居多,比如我们可以举几个例 子:

首先创建一个测试类WrapperTest,先做一个测试

    // 查询name不为空的用户,并且年龄大于18岁
    @Test
    void selectTest1() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.isNotNull("name")
                .ge("age", 18);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

查看结果,主要查看执行的SQL语句

其实看了一个大家应该就明白了,复杂查询我们现在完全可以通过这种方式来进行完成,需要使 用什么条件,通过官网查询一下具体方法即可

比如多做几个实验

    // 查询名字为jack的用户
    @Test
    void selectTest2() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name", "jack");
        User user = userMapper.selectOne(wrapper);//类似于map传入条件
        System.out.println(user);
    }

    // 查询年龄18~25岁之间的用户
    @Test
    void selectTest3() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age", 18, 25);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    // 查询年龄大与等于25岁的用户数量
    @Test
    void selectTest4() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ge("age", 25);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    // 模糊查询:查询名字不包含qf的用户,反之like就是包含
    @Test
    void selectTest5() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.notLike("name", "qf");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    //模糊查询:包含左侧或者右侧具体内容
    @Test
    void selectTest6() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 模糊查询:查询名字不包含qf的用户,反之like就是包含
        // likeLeft和likeRight
        // 左右就是 %在左或者在右
        // 以下就是 e%  相当于以e开头的名字
        wrapper.likeRight("name","e");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

    //通过子查询,查询id等于3的用户信息,此方法也可以进行表关联查询
    @Test
    void selectTest7(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 模糊查询:查询名字不包含qf的用户,反之like就是包含
        wrapper.inSql("id","select id from user where id=3");
        userMapper.selectObjs(wrapper).forEach(System.out::println);
    }

总之这些演示,大家要记住每个都要分析执行的SQL,这样才能掌握QueryWrapper的具体用 法。

lambda表达式执行条件

在QueryWrapper中有and或者or这样的方法,要注意的是默认都是通过and来进行连接条件, 但是如果主动调用or方法,将会改变,还有一点如果and或者or表达式时中出现lambda表达式, 将会改变条件的优先级,先来执行lambda表达式的条件

错误写法:

    // 查询用户名中包含qf并且(年龄大于26或者邮箱为null)的用户
    @Test
    void selectTest8(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name","qf").gt("age",26).or().isNull("email");
        userMapper.selectList(wrapper).forEach(System.out::println);
    }
    -----------------执行的SQL--------------------

此时执行的SQL语句不符合要求

and(Consumer<Param> consumer) //此方法要求传入Lambda表达式
or(Consumer<Param> consumer) //此方法要求传入Lambda表达式

这里我们可以查看一下源码,从源码中可以看出当前的泛型就相当于是一个条件构造器

正确写法(采用Lambda表达式写的条件优先执行):

    // 查询用户名中包含qf并且年龄大于26或者邮箱为null的用户
    @Test
    void selectTest8(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like("name","qf")
                .and(w -> w.gt("age",26).or().isNull("email"));
        userMapper.selectList(wrapper).forEach(System.out::println);
    }
    -----------------执行的SQL--------------------
    SELECT id,name,age,email,version,deleted,create_time,update_time FROM user 
    WHERE deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))

condition

我们在写项目的时候,所有的条件都是由用户进行传递的,那么有的时候就无法避免参数出现空 值null的情况,所以我们应该要做一些判断,其实很多方法都提供了boolean condition这个参 数,表示该条件是否加入最后生成的sql中,也就是可以通过它来进行判断

    // 模糊查询:查询名字包含qf的用户,并且按照age升序排序,注意参数不能为空
    @Test
    void selectTest9(){
        //假设用户传递参数
        String name = "f";
        Integer age = null;
        //先判断条件是否符合,符合才会组合到SQL语句中
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name),"name",name)
                .orderByAsc(age != null,"age");
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

QueryWrapper执行修改和删除操作

其实QueryWrapper也是可以进行修改和删除操作的,因为我们的BaseMapper中提供了对应的 方法

修改

    //修改用户id为5的name为tom
    @Test
    void updateTest1(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("id",5L);
        User user = new User();
        user.setName("Tom");
        userMapper.update(user,wrapper);
    }

执行SQL

删除

    //删除name为Tom的用户
    @Test
    void deleteTest1(){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","Tom");
        userMapper.delete(wrapper);
    }

UpdateWrapper

继承自 AbstractWrapper ,自身的内部属性entity 也用于生成 where 条件

可以通过set方法来进行修改

    //修改年龄大于26,并且name为mask的用户邮箱为 qf1000@qq.com
    @Test
    void updateTest2(){
        UpdateWrapper<User> wrapper = new UpdateWrapper<>();
        wrapper.gt("age",26)
                .eq("name","mask")
                .set("email","qf1000@qq.com");
        //无需传递user对象,直接赋值为null
        userMapper.update(null,wrapper);
    }

LambdaQueryWrapper&LambdaUpdateWrapper

它们两个的主要目的是为了防止我们在编写的时候,字段名称编写错误,我们可以直接通过 Lambda的方式来直接获取指定字段对应的实体类对应的名称

LambdaQueryWrapper 查询

    @Test
    void selectTest10(){
        //假设用户传递参数
        String name = "f";
        Integer age = null;
        //LambdaQueryWrapper就是为了防止我们写错字段,可以直接通过Lambda的方式来直接获取指定字段对应的实体类对应的名称
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(StringUtils.isNotBlank(name),User::getName,name)
                .orderByAsc(age != null,User::getAge);
        userMapper.selectList(wrapper).forEach(System.out::println);//类似于map传入条件
    }

LambdaUpdateWrapper 修改

    //修改年龄大于26,并且name为mask的用户邮箱为 qf1000@qq.com
    @Test
    void updateTest3(){
        LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
        wrapper.gt(User::getAge,26)
                .eq(User::getName,"mask")
                .set(User::getEmail,"qf1000@qq.com");
        //无需传递user对象,直接赋值为null
        userMapper.update(null,wrapper);
    }

代码生成器

代码生成器的使用官网有详细的配置,我们可以参考来进行完成

具体地址:代码生成器(新) | MyBatis-Plus (baomidou.com)icon-default.png?t=N7T8https://www.baomidou.com/pages/779a6e/#%E5%AE%89%E8%A3%85

安装

引入对应的依赖

        <!--  MyBatisPlus  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--  代码生成器  -->
        <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>

使用

只需要复制官网的代码,创建测试类 FastAutoGeneratorTest类型,把代码复制其中,按照自己 的要求修改之后执行即可

public class FastAutoGeneratorTest {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://localhost:3306/db_mybatisplus?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8",
                "root", "1234")
                .globalConfig(builder -> {
                    builder.author("mask") // 设置作者
                            //.enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://mybatis_plus"); // 指定输出目录
                })
                .packageConfig(builder -> {
                    builder.parent("com.qf") // 设置父包名
                            .moduleName("mybatisplus") // 设置父包模块名

                            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://mybatis_plus"));
                    // 设置mapperXml生成路径
                })
                .strategyConfig(builder -> {
                    builder.addInclude("user"); // 设置需要生成的表名
                    //.addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker 引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}

MyBatisX插件

MyBatis-Plus确实能够为我们提高很多的开发效率,但是当它碰到一些复杂的sql,比如说多表联 查的时候,可能我们就需要使用原生的MyBatis方式来进行编写了,所以MyBatis-Plus提供了 MybatisX插件来帮助我们解决问题。

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

官网具体配置地址:MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

安装之后,我们所有的Mapper映射图标会发生变化,当我们点击图标时,可以帮助我们快速跳 转到对应的接口以及映射方法上

MyBatisx代码生成器

我们来创建一个新的Boot项目来进行测试 myBatisXDemo,此项目只需要复制Maven依赖,以 及对应的properties配置即可

使用此功能的前提是要保证先在 idea 配置 Database 配置数据源

还要使用DataBase功能

配置连接数据库

选择使用的数据库

找到要使用的表User选择MyBatisX-Generator

next

点击finish以后,我们看一下生成的效果

JPA提示

帮助我们快速生成接口方法以及对应的映射

我们只需要按照“见名知意”的规则来编写即可,会自动生成对应的方法以及Mapper映射

UserMapper

相关推荐

  1. MyBatis Plus笔记

    2024-02-19 15:00:02       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-19 15:00:02       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-19 15:00:02       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-19 15:00:02       82 阅读
  4. Python语言-面向对象

    2024-02-19 15:00:02       91 阅读

热门阅读

  1. Elcomsoft 取证工具包系列:Advanced SQL Password Recovery

    2024-02-19 15:00:02       54 阅读
  2. 运用多设计模式的同步&异步滚动日志系统

    2024-02-19 15:00:02       43 阅读
  3. 6.路由基础-动态路由

    2024-02-19 15:00:02       39 阅读
  4. MCU中断响应流程及注意事项

    2024-02-19 15:00:02       48 阅读
  5. 【IOS】Xcode 15.2版本下载 iOS_17 Simulator失败

    2024-02-19 15:00:02       76 阅读
  6. LeetCode //C - 338. Counting Bits

    2024-02-19 15:00:02       56 阅读
  7. C 练习实例69-约瑟夫环

    2024-02-19 15:00:02       60 阅读
  8. 微服务中4种应对跨库Join的思路

    2024-02-19 15:00:02       54 阅读
  9. 我的创作纪念日

    2024-02-19 15:00:02       51 阅读
  10. P1106 删数问题题解

    2024-02-19 15:00:02       47 阅读