本文将对MyBatis-Plus进行学习,主要是对简单的增删改查、分页查询、自动填充、乐观锁、逻辑删除、性能分析进行学习。



一、MyBatis-Plus介绍

MyBatis-Plus官网:​​https://mp.baomidou.com/​

1. Mybatis-Plus简介

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

2. MyBatis-Plus特性


  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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. 支持数据库


  • mysql 、 mariadb 、 oracle 、 db2 、 h2 、 hsql 、 sqlite 、 postgresql 、 sqlserver 、 presto
  • 达梦数据库 、 虚谷数据库 、 人大金仓数据库

二、MyBatis-Plus入门

1. 入门案例

入门案例主要讲一下使用mybatis-plus基本的增删改查

1.1 创建并初始化数据库

1.1.1 创建数据库

这里采用Navicat可视化工具创建数据库,当然,也可以在命令窗口创建,如下:


  • 数据库名:mybatis_plus
  • 字符集:常用为utf8
  • 排序规则:这里选utf8_general_ci

MyBatis-Plus学习使用(Springboot整合MyBatis-Plus)_springboot

1.1.2 创建User表

这里为了方便演示,就直接创建一个简单的User表

建表语句如下:

DROP TABLE IF EXISTS USER;

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

插入测试数据:

INSERT INTO user (id,name,age,email) VALUES
(1,'onestar',18,'onestar@136.com'),
(2,'twostar',18,'twostar@136.com'),
(3,'threestar',18,'threestar@136.com'),
(4,'fourstar',18,'fourstar@136.com'),
(5,'fivestar',18,'fivestar@136.com');

1.2 创建springboot工程

使用idea快捷创建springboot项目,这里就不细说,还有不懂的可以看我之前的文章​​SpringBoot 框架入门:​​ ,idea快速创建SpringBoot项目那一部分。

注:这里使用的是springboot2.2.1的版本

1.2.1 导入依赖

这里需要将mybatis-plus、MySQL依赖导入,另外,为了简化实体类,不写get、set、tostring方法,添加lombok依赖,用lombok需要下载插件,可以再settings里面的Plugins搜索下载,下载后重启idea

  • 导入依赖
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok用来简化实体类,不用写get、set、toString方法-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
  • 下载lombok插件 MyBatis-Plus学习使用(Springboot整合MyBatis-Plus)_数据_02

1.2.2 修改配置文件

springboot配置文件方式有多种,这里选用yml格式的,所以将配置文件后缀改成yml,并添加以下两个配置,如下:


  1. 数据库配置:driver、url、username、password
  2. mybatisplus日置配置:用来在终端显示数据库执行的详细信息

spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 806188

mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

1.3 代码编写

在com.star包下创建两个包,并创建User实体类和UserMapper接口,目录结构如下:


  • entity包:用来放实体类
  • mapper包:用来放持久层接口

这里还需要让springboot扫描到mapper接口,在MybatisplusdemoApplication类中添加注解@MapperScan("com.star.mapper")

@SpringBootApplication
@MapperScan("com.star.mapper") //扫描到mapper接口
public class MybatisplusdemoApplication {...}

MyBatis-Plus学习使用(Springboot整合MyBatis-Plus)_springboot_03

1.3.1 编写User实体类

package com.star.entity;

import lombok.Data;

/**
* @Description: User实体类
* @Date: Created in 11:54 2020/7/28
* @Author: ONESTAR
* @QQ群: 530311074
* @URL: https://onestar.newstar.net.cn/
*/
@Data //可以简化实体类
public class User {

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

private String name;
private Integer age;
private String email;

}

分析:



  • @Data注解:用于简化实体类,可以不用写get、set等方法
  • @TableId(type = IdType.AUTO)注解:这是主键策略注解

  • AUTO:表示自动增长策略
  • INPUT:表示需要自己设置ID,需要输入
  • NONE:表示没有策略,需要输入
  • UUID:随机生成唯一的值
  • ID_WORKER:mybatisplus自带策略,生成19位的值,数字类型使用该策略
  • ID_WORKER_STR:mybatisplus自带策略,生成19位的值,字符串类型使用该策略



1.3.2 编写UserMapper接口

  • @Repository注解:可有可无,可以消去依赖注入的报错信息
package com.star.mapper;

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

/**
* @Description: UserMapper接口
* @Date: Created in 11:57 2020/7/28
* @Author: ONESTAR
* @QQ群: 530311074
* @URL: https://onestar.newstar.net.cn/
*/
@Repository
public interface UserMapper extends BaseMapper<User> {

}

1.3.3 编写测试类(简单增删改查)

编写好以上代码后,框架基本就搭建好了,可以在测试类中进行测试了,在test中添加测试,代码如下:

  • 注入mapper
@Autowired
private UserMapper userMapper;
  • 简单增删改查
//查询User
@Test
void findUser() {

//查询列表
List<User> user = userMapper.selectList(null);
System.out.println("user:" + user);

//根据ID查询
User user1 = userMapper.selectById(1L);
System.out.println("user1:" + user1);

//通过多个ID批量查询
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);

//简单的条件查询
HashMap<String, Object> map = new HashMap<>();
map.put("name", "onestar");
map.put("age", 18);
List<User> users2 = userMapper.selectByMap(map);
users2.forEach(System.out::println);
}

//添加User
@Test
void addUser(){
User user = new User();
user.setName("sixstar");
user.setAge(18);
user.setEmail("sixstar@136.com");
int insert = userMapper.insert(user);
System.out.println(insert);
}

//修改User
@Test
void updateUser(){
User user = new User();
user.setId(1L);
user.setAge(12);

int result = userMapper.updateById(user);
System.out.println(result);
}

//删除User
@Test
void deleteUser(){
//根据ID删除
int result = userMapper.deleteById(5L);
System.out.println(result);

//批量删除
int result2 = userMapper.deleteBatchIds(Arrays.asList(1,2,3));
System.out.println(result2);

//简单的条件删除
HashMap<String,Object> map = new HashMap<>();
map.put("name","fourstar");
map.put("age",18);
int result3 = userMapper.deleteByMap(map);
System.out.println(result3);
}

分析:


这里基本上都是直接调用mybatisplus封装好了的方法,有一些基本的方法:


  • 查询列表:selectList
  • 根据ID查询:selectById
  • 根据ID批量查询:selectBatchIds
  • 条件查询:selectByMap
  • 添加:insert
  • 根据ID修改:updateById
  • 根据ID删除:deleteById
  • 根据ID批量删除:deleteBatchIds
  • 条件删除:deleteByMap


2. 提升案例

进阶案例主要讲以下几个知识点:


  • 分页查询
  • 自动填充
  • mybatisplus实现乐观锁
  • 逻辑删除
  • 性能分析
  • 复杂条件查询

2.1 分页查询

Mybatis-Plus是自带了分页查询功能的,直接使用自集成的插件进行分页查询,在使用之前要配置插件,可以专门创建一个配置类来配置插件

2.1.1 配置插件

在com.star包下创建config包,创建MpConfig配置类,添加分页插件

package com.star.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* @Description: 配置类
* @Date: Created in 10:26 2020/7/29
* @Author: ONESTAR
* @QQ群: 530311074
* @URL: https://onestar.newstar.net.cn/
*/
@Configuration
public class MpConfig {

/**
* 分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}

2.1.2 测试分页

//分页查询
@Test
void selectPage(){

//创建page对象(1:当前页;3:每页显示记录数)
Page<User> page = new Page<>(1,3);
//调用分页查询方法,将分页所有数据封装到page对象里面
userMapper.selectPage(page,null);
//通过page对象获取分页数据
System.out.println(page.getCurrent()); //当前页
System.out.println(page.getRecords()); //每页数据list集合
System.out.println(page.getPages()); //总页数
System.out.println(page.getSize()); //每页显示记录数
System.out.println(page.getTotal()); //总记录数
System.out.println(page.hasNext()); //是否有下一页
System.out.println(page.hasPrevious()); //是否有上一页

}

分析:



  • 创建page对象:new Page<>(1,3); 参数表示当前页和每页显示记录数
  • 调用分页查询方法,将分页所有数据封装到page对象里面:selectPage(page,null);
  • 通过调用Mybatisplus插件提供的方法实现分页功能

  • page.getCurrent():当前页
  • page.getRecords():每页数据list集合
  • page.getPages():总页数
  • page.getSize():每页显示记录数
  • page.getTotal():总记录数
  • page.hasNext():是否有下一页
  • page.hasPrevious():是否有上一页



2.2 自动填充

在平时开发中,会有些数据需要自动填充,比如创建时间、更新时间等,这就可以使用MybatisPlus的自动填充功能,这里就以创建时间和更新时间为例进行演示

2.2.1 添加字段

User表中添加字段:


  • 创建时间:create_time
  • 更新时间:update_time

实体类添加属性和注解

@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

2.2.2 实现元对象处理器接口

这里专门创建一个hander包来访处理器接口,在com.star包下创建hander包,创建MyMetaObjectHandler接口

package com.star.hander;

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

import java.util.Date;

/**
* @Description: 元对象处理器
* @Date: Created in 14:03 2020/7/29
* @Author: ONESTAR
* @QQ群: 530311074
* @URL: https://onestar.newstar.net.cn/
*/
@Component //交给spring管理
public class MyMetaObjectHandler implements MetaObjectHandler {

//使用MybatisPlus实现添加操作,执行该方法
@Override
public void insertFill(MetaObject metaObject) {
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}

//使用MybatisPlus实现修改操作,执行该方法
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}

分析:



  • @Component:将该类交给spring管理
  • metaObject:元数据对象
  • setFieldValByName:根据名称来设置值,这里根据创建时间来设置


配置好后可以再测试类中添加User和修改User进行测试

2.3 mybatisplus实现乐观锁

概念

乐观锁和悲观锁


  • 悲观锁,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。悲观锁适用于并发竞争很厉害,写比较多的操作。
  • 乐观锁,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于读多写少的应用场景,这样可以提高吞吐量。

这里只讲乐观锁,为了解决写数据时丢失更新问题而出现的一种解决方法。当多个人同时修改同一条记录,最后提交的把之前的提交数据覆盖,这就是丢失更新,为了防止出现这种情况,就可以采用乐观锁,在提交更新的时候会判断一下在此期间别人有没有去更新这个数据,如12306抢票

MybatisPlus实现原理:通过添加version字段来判断是否对数据进行了修改,修改将version加一,比较新的version和原有的version是不是一样,一样的version才进行更新操作,更新完成后,version就会+1,这时候另外一个拿到数据想要更新的人,在比较version那里就会不同从而更新失败。

2.3.1 添加乐观锁版本号字段

  • 添加字段
ALTER TABLE `user` ADD COLUMN `version` INT
  • 实体类添加属性和注解
@Version
@TableField(fill = FieldFill.INSERT_UPDATE) //更新时修改
private Integer version;

添加操作初始化version

//使用MybatisPlus实现添加操作,执行该方法
@Override
public void insertFill(MetaObject metaObject) {
......
this.setFieldValByName("version", 1, metaObject);
}

注:



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


2.3.2 配置乐观锁插件

在MpConfig配置文件中添加乐观锁配置插件

/**
* 乐观锁插件
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

2.3.3 测试乐观锁

//乐观锁
@Test
void optimisticLocker(){
//查询
User user = userMapper.selectById(1L);
//修改数据
user.setAge(1);

//修改失败:模拟取出数据后,数据库中version实际数据比取出的值大,即已被其它线程修改并更新了version
//user.setVersion(user.getVersion() - 1);

//执行更新
userMapper.updateById(user);
}

2.4 逻辑删除

删除可以分为两种,一种是物理删除,一种是逻辑删除


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

2.4.1 添加字段

  • 添加deleted标志位字段
ALTER TABLE `user` ADD COLUMN `deleted` boolean
  • 实体类添加属性和注解
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
  • 添加初始化deleted
//使用MybatisPlus实现添加操作,执行该方法
@Override
public void insertFill(MetaObject metaObject) {
......
this.setFieldValByName("deleted", 0, metaObject);
}

2.4.2 配置逻辑删除插件

在MpConfig配置类中添加逻辑删除插件

/**
* 逻辑删除插件
*/
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}

2.4.3 逻辑删除测试

先添加一条数据,可以看到添加数据的deleted字段默认为0,然后逻辑删除该数据,执行删除后,可以看到数据库中该数据任然在,只是deleted字段由0变成了1,逻辑删除成功

//逻辑删除
@Test
void logicDelete(){
//根据ID删除
int result = userMapper.deleteById(1288666354731237378L);
System.out.println(result);
}

分析:



  • 测试后,数据并没有被删除,deleted字段的值由0变成了1,0 和 1 可以在配置文件里面进行配置
  • 测试后分析打印的sql语句,是一条update:UPDATE user SET deleted=1 WHERE id=? AND deleted=0
  • 被删除数据的deleted 字段的值必须是 0,才能被选取出来执行逻辑删除的操作
  • 逻辑删除后查询中包含了where deleted = 0:SELECT id,name,age,email,create_time,update_time,deleted FROM user WHERE deleted=0


2.5 性能分析

性能分析是记录每条SQL语句执行的时间,用来帮助开发者判断某些部分是否还需要优化,有助于发现问题,需要配置插件

2.5.1 配置插件

在MpConfig配置类中添加性能分析配置插件

/**
* SQL 执行性能分析插件
* 开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100);//ms,超过此处设置的ms则sql不执行
performanceInterceptor.setFormat(true);
return performanceInterceptor;
}

分析:



  • setMaxTime(100);:表示超过此处设置的ms则sql不执行,会报错
  • @Profile({"dev","test"}):当前插件对什么环境起作用,一般有三种环境

  • dev环境:表示开发环境
  • test环境:表示测试环境
  • pro环境:表示产生环境

  • 这里表示对开发环境和测试环境性能分析有效,生产环境还去分析性能的话就没必要了,可以针对各环境新建不同的配置文件:

  • application-dev.yml
  • application-test.yml
  • application-pro.yml



指定使用哪个配置文件需要在application.yml文件中进行配置,这里配置表示使用的是开发配置环境

spring:
profiles:
active: dev

2.5.2 性能分析测试

随便执行增删改查中的一个测试,便可以看到控制台会有SQL的执行时间


将maxTime改小之后再执行测试,如果执行时间过长,则会抛出异常:The SQL execution time is too large

performanceInterceptor.setMaxTime(5);//ms,超过此处设置的ms不执行


【点关注,不迷路,欢迎持续关注本站】


MyBatis-Plus学习使用(Springboot整合MyBatis-Plus)_springboot_04