简介
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)https://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