本次笔记来自狂神说以及官网
官网:https://baomidou.com/pages/24112f/
MyBatisPlus详解
1.MyBatisPlus概述
1.1 为什么要学MyBatisPlus(MP)?
因为它可以节省我们大量工作时间,
所有的CRUD代码它都可以自动化完成!
》它是一个 MyBatis 的增强工具,
在 MyBatis 的基础上只做增强不做改变,
为简化开发、提高效率而生。
注意点:
CURD代表创建(Create)、更新(Update)、读取(Retrieve)和删除(Delete)操作。
1.2 MP 特性
-
无侵入:
只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑 -
损耗小:
启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
2.快速开始
2.0 注意点
-
如果数据库表名和实体类名不一样,
加上注解@TableName(“name”),值就是数据库的表名 -
如果数据库的字段名和实体类的变量不一致,
加上注解@TableField(“name”),值就是数据库的字段名 -
全局配置策略
博客:https://cloud.tencent.com/developer/article/1809566
2.1 使用第三方组件步骤
1、导入对应的依赖
2、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力!
2.2 创建数据库(user)
2.3 使用SpringBoot初始化
2.4 pom.xml
<!-- 数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mybatis-plus 导mybatis-plus就不用导mybatis-->
<!-- mybatis-plus 是自己开发,并非官方的-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
说明:
我们使用mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus !版本的差异!
2.5 aplication.yml
spring:
datasource:
username: root
password: zjj
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&userUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
#mysql 8 驱动不同com.mysql.cj.jdbc.Driver,并且需要增加时区的配置
#UTC 或 serverTimezone=GMT%2B8 但serverTimezone=GMT%2B8可以防止有时差
2.6 idea 连接数据库(可不配)
如果出现这个错误:下载失败Driver files are not download
解决方法:
1.Driver:MySQL>Go to Driver
2. 博客:https://www.jianshu.com/p/e13b459f73c8
加入你对应的数据库jar包
3.就成功了,输入密码,用户,测试连接
注意: 这边可能会出现 连接失败 时区问题
Server returns invalid timezone. Go to ‘Advanced’ tab and set 'serverTimezon
博客:https://blog.csdn.net/qq_43598138/article/details/103772854
在Database中输入你的数据库+?useSSL=false&serverTimezone=UTC
4.如果成功了却没有数据
记得在Schemas中选择你对应的数据库。
2.7 pojo
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
public User(String name, Integer age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
2.8 mapper
import cn.zjj.pojo.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
// 对应的Mapper上面继承基本的类BaseMapper
@Repository//表示它是Dao层的 代表持久层的
public interface UserMapper extends BaseMapper<User> {
//所有的CRUD操作都已经编写完成了
//你不需要像以前的配置一大堆文件了!
}
2.9 xxxApplication
//扫描并识别
//在主启动类上去扫描我们的mapper包下的所有接口
@MapperScan("cn.zjj.mapper")
@SpringBootApplication
public class Mybatisplus0119Application {
public static void main(String[] args) {
SpringApplication.run(Mybatisplus0119Application.class, args);
}
}
2.10 xxxApplicationTests
import cn.zjj.mapper.UserMapper;
import cn.zjj.pojo.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class Mybatisplus0119ApplicationTests {
//继承了BaseMapper,所有的方法都来自己父类
//我们也可以编写自己的扩展方法!
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//参数是一个Wrapper,条件构造器,这里我们先不用null
//查询全部用户
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
结果:
注意到没有,我们不需要写xxxMapper.xml,和里面的sql语句,和xxxMapper里的查询方法
这些MyBatis-Plus都帮我们写好了。
2.11 优化》配置日志
我们所有的sql现在是不可见的,
我们希望知道它是怎么执行的,所以我们必须要看日志!
日志能体现具体的步骤.
开发时需要上线时去掉 不然会耗资源
aplication.yml
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
结果:
3.CRUD扩展
3.1 Insert插入
xxxApplicationTests
@Test
public void testIn(){
User user = new User("asdf",3,"asdasdsaf@qq.com");
int i = userMapper.insert(user);//帮我们自动生成id
System.out.println(i);//受影响的行数
System.out.println(user);//id会自动回填
}
Preparing:
INSERT INTO user ( id,name, age, email) VALUES ( ?, ?, ?, ? )
3.2 主键生成策略(题外话)
3.2.1 (默认)ID_WORKER全局唯一id
@TableId(type = IdType.ID_WORKER)
private Long id;
但为什么id会出现一长串数字 ,这边用了雪花算法
实体类中的id对应数据库中的主键
(uuid\自增id\雪花算法\redis\zookeeper)
博客: https://www.cnblogs.com/haoxinyue/p/5208136.html
注意: 本次数据库中的id没有设置自增长 ,下面设置了。
3.2.2 主键自增
我们需要配置主键自增:
1.实体类字段上@TableId(type = IdType.AUTO)
2.数据库字段一定要是自增!
不然只写注解会报错
测试:
3.2.3 其他的源码解释
public enum IdType {
AUTO(0),//数据库id自增
NONE(1),//未设置主键 }一旦手动输入id之后,就需要主键配置id了 6L
INPUT(2),//手动输入
ID_WORKER(3),//默认的全局唯一id
UUID(4),//全局唯一id uuid
ID_WORKER_STR(5);//ID——WORKER 字符串表示法
}
3.3 update更新
xxxApplicationTests
@Test
public void testUp(){
User user = new User();
//通过条件自动拼接动态sql
user.setId(5L);
user.setName("wang");
//主要:updateById但是参数是一个对象
int i = userMapper.updateById(user);
System.out.println(i);
}
结果:
如果我再加 user.setAge(21);
所有的sql自动帮你动态配置的
4.自动填充
4.1 概念
创建时间、修改时间!
这些操作都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:
所有的数据库表: gmt_create、gmt_modified几乎所有的表都要配置上!而且需要自动化!
4.2 方式一:数据库级别
(工作中不允许你修改数据库!)
1.在表中新增字段 create_time、update_time
默认值 改成 当前时间》 CURRENT_TIMESTAMP
更新操作 》更新时间需要勾上自动更新
注意: navicat 没有CURRENT_TIMESTAMP
只能重新写sql然后字段后面加
DEFAULT CURRENT_TIMESTAMP(0)
2.再次测试插入方法,我们需要先把实体类同步!
private Date createTime;
private Date updateTime;
再次进行更新
到时候所有的都有时间了,都是默认的
你更新的那个update_time(更新)也变成实时更新的
4.3 方式二:代码级别
1.先把前面设置的默认格式 和更新都删除
2.书体类字段属性加注解 Field(字段)fill 填充
//字段添加填充内容
@TableField(fill = FieldFill.INSERT)//插入的时候更新
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)//插入和更新的时候更新
private Date updateTime;
默认是FieldFill.default 》不操作
3.编写处理器来处理这个注解即可!
新建一个handler包 》
然后新建个类》原数据处理 MetaObjectHandler
MyMetaObjectHandler
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.util.Date;
@Slf4j//日志
@Component // 一定不要忘记把处理器加到IOC容器中!
public class MyMetaObjectHandler implements MetaObjectHandler {
//插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill .....");//打印
//setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject)
//设置字段的值 我们需要修改的字段名 对应插入的值 需要传入的数据
this.setFieldValByName("createTime",new Date(),metaObject);
//插入的时候自动将数据填充进去
this.setFieldValByName("updateTime",new Date(),metaObject);
}
//更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill .....");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
//mybatis会自动处理该处理器
//执行插入的时候 通过反射自动的去读取你那边有对应注解的值,从而这段代码执行成功
//只要插入了,自动的帮你createTime 自动的填充new Date()的值
这样只要你插入和更新,那么对应的值都会实时更新。
注意 导的时间类型包 要和实体类保持一致
5.乐观锁
5.1 概念
乐观锁: 故名思意十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试
悲观锁: 故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
乐观锁和version一起用
判断版本号是否更新了 来确定这个数据有没有更新
5.2 乐观锁实现方式
1.取出记录时,获取当前 version
2.更新时,带上这个 version
3.执行更新时, set version = newVersion where version = oldVersion
4.如果 version 不对,就更新失败
5.3 乐观锁实现详解(MP)
5.3.1 给数据库中增加version字段!
默认值为1 》 初始版本
5.3.2 我们实体类加对应的字段
@Version//乐观锁Version 注解
private Integer version;
5.3.3 注册组件
1.写自己的配置,一般先建config包然后建MyBatisPlusConfig类
2.首先他要是配置类,加个注解@Configuration(Springboot里的 )
3.如果有事务控制的话,加个注解@EnableTransactionManagement
自动管理事务开启 默认是开启的
4.也可以把扫描包放在配置类(mybatis配置类要做的 让它去做扫描)
@MapperScan(“cn.zjj.mapper”)//扫描我们的mapper文件夹
不一定要放在启动函数
代码:
@MapperScan("cn.zjj.mapper")//扫描我们的mapper文件夹
@EnableTransactionManagement
@Configuration //配置类
public class MyBatisPlusConfig {
//注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
Interceptor拦截器在所有进行操作的时候自动进行拦截进行自动化的处理
5.3.4 测试
1.成功
//测试乐观锁成功! 单线程
@Test
public void testOptimisticLocker(){
//1. 查询用户信息
User user = userMapper.selectById(1L);
//2.修改用户信息
user.setName("happy");
user.setEmail("131242526@qq.com");
//3.执行更新操作
userMapper.updateById(user);
}
Preparing:
SELECT id,name,age,email,version,create_time,update_time FROM user WHERE id=?
Preparing:
UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?
》version =2 变了 修改成功
2.失败
//测试 乐观锁失败! 多线程下
@Test
public void testOptimisticLockerNo(){
//线程一
User user = userMapper.selectById(1L);
user.setName("happy111");
user.setEmail("131242526@qq.com");
//模拟另外一个线程执行了插队操作
User user2 = userMapper.selectById(1L);
user2.setName("happy222");
user2.setEmail("131242526@qq.com");
userMapper.updateById(user2);
//如果想要解决 可以使用自旋锁来多次尝试提交!
userMapper.updateById(user);//如果没有乐观锁就会覆盖插队线程的值!
//线程一设置了值 但是还没有更新 另外个线程设置了值并抢先更新
// 在并发情况下 被抢先 线程一的更新可能不会执行
}
Preparing:
UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=?
》 version 为2
Preparing:
UPDATE user SET name=?, age=?, email=?, version=?, create_time=?, update_time=? WHERE id=? AND version=? AND deleted=0
》 version 为2
两个sql都是拿2去改 按道理第一个改完就变成三了
如果非要执行,可以使用自旋锁
6.查询操作
6.1 查询某段
@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
Preparing:
SELECT id,name,age,email,create_time,update_time FROM user WHERE id=?
6.2 查询多段 批量 用户
@Test
public void testSelectByBatchId(){
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}
Preparing:
SELECT id,name,age,email,create_time,update_time FROM user WHERE id IN ( ? , ? , ? )
6.3 按条件查询之一 使用map操作
@Test
public void test01(){
HashMap<String, Object> map = new HashMap<>();
//自定义查询
map.put("name","Tom");
map.put("age",28);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}
Preparing:
SELECT id,name,age,email,create_time,update_time FROM user WHERE name = ? AND age = ?
6.4 分页查询
6.4.1 主要有三种方式可以实现分页
- 原始的limit进行分页
- pageHelper 第三方插件
- MP其实也内置了分页插件!
6.4.2 如何使用
用拦截器配置的
1.配置拦截器组件即可
MyBatisPlusConfig
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
2.直接使用Page对象即可!
//测试分页查询
@Test
public void testPage(){
//当前页:第一页 页面大小:每页只有五个
//使用了分页插件后,所有的分页操作也变得简单了
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page,null);
// 记录 遍历 输出
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
}
》Preparing:
SELECT COUNT(1) FROM user
》Preparing:
SELECT id,name,age,email,create_time,update_time FROM user LIMIT 0,5
7.删除操作
7.1 删除某段
@Test
public void testDeleteById(){
userMapper.deleteById(1484069334090285061L);
}
》Preparing:
DELETE FROM user WHERE id=?
7.2 通过id批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1484069334090285059L,1484069334090285060L));
}
》Preparing:
DELETE FROM user WHERE id IN ( ? , ? )
7.3 通过map删除
@Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","asdf");
userMapper.deleteByMap(map);
}
》Preparing:
DELETE FROM user WHERE name = ?
我们在工组中会遇到一些问题:逻辑删除!
8.逻辑删除
8.1 对比
物理删除: 从数据库中直接移除
逻辑删除:
在数据库中没有被移除,而是通过一个变量来让他失效!
deleted = 0 => deleted = 1
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
8.2 测试
8.2.1 在数据库表中增加一个deleted字段
默认值为0 代表没有被删除掉
8.2.2 实体类添加
@TableLogic //逻辑删除
private Integer deleted;
8.2.3 配置逻辑删除组件
MyBatisPlusConfig
//逻辑删除组件!
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
application.yml
#配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
8.2.4 测试
//测试删除
@Test
public void testDeleteById(){
userMapper.deleteById(1484069334090285062L);
}
》走的是更新操作,并不是删除操作
记录还在,但是该用户的deleted从0变成了1
再次查询该用户,那么你会查不到该用户
(查询时候 会自动 过滤 被逻辑删除的字段)
9.性能分析插件
9.1 概述
我们在平时的开发中,会遇到一些慢sq|,那怎么测试出来呢?
性能分析拦截器
用于输出每条SQL语句及其执行时间
》MP也提供性能分析插件,如果超过这个时间就停止运行! .
9.2 步骤详解
9.2.1 导入插件
MyBatisPlusConfig
//SQL执行效率插件
@Bean
@Profile({"dev","test"})//设置 dev test 环境开启,保证我们的效率 只有在测试环境和开发环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(10);
//ms毫秒 设置sql执行的最大时间,如果超过了则不执行
performanceInterceptor.setFormat(true);//是否开启格式化支持
return performanceInterceptor;
}
记住,要在SpringBoot中配置环境为dev或者test 环境!
》设置开发环境 自动识别到dev 然后那个插件就生效了
application.yml
spring.profiles.active=dev
9.2.2 测试使用!
@Test
void contextLoads() {
//参数是一个Wrapper,条件构造器,这里我们先不用null
//查询全部用户
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
测试
》查询全部用户
》超过了规定时间就会报错 异常
结果:
上面的代码就是格式化,看的更明白。
注意:
这个sql语句执行了30毫秒
如果你设置sql执行的最大时间<30 那么运行时会报错
而我此时设置的是10毫秒,报异常
PersistenceException》连接异常
xxx.MybatisPlusException:
The SQL execution time is too large, please optimize !
》这个sql执行的时间太大了 请你修改验证
所以作用:
以后就是超过几毫秒 xxxms的sql语句都拿出来,进行优化
使用性能分析插件,可以帮助我们提高效率!
10.条件构造器(Wrapper)
10.1 查询name不为空和邮箱不为空的用户,年龄>=12
@SpringBootTest
public class WrapperTest {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
//查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("email").ge("age",20);//ge 大于等于
userMapper.selectList(wrapper).forEach(System.out::println);
}
Execute SQL:
SELECT id,name,age,email,deleted,create_time,update_time FROM user WHERE deleted=0 AND name IS NOT NULL AND email IS NOT NULL AND age >= 20
10.2 查询一个数据
@Test
void test02(){
//查询名字
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("name","Tom"); //查询一个数据,出现多个结果使用List或者Map
//eq 》 = 》 name = Tom
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
Execute SQL:
SELECT id,name,age,email,deleted,create_time,update_time FROM user WHERE deleted=0 AND name = ‘Tom’
10.3 查询年龄在20~30岁之间的用户
@Test
void test03(){
//查询年龄在20~30岁之间的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.between("age",20,30);
Integer count = userMapper.selectCount(wrapper);
System.out.println(count);
}
Execute SQL:
SELECT COUNT(1) FROM user WHERE deleted=0 AND age BETWEEN 20 AND 30
10.4 不包含e并且邮箱号是以t开头的
@Test
void test04(){
//模糊查询 不包含e并且邮箱号是以t开头的
QueryWrapper<User> wrapper = new QueryWrapper<>();
//左 %x 和右 xx%
wrapper.notLike("name","m")
.likeRight("email","t");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
Execute SQL:
SELECT id,name,age,email,deleted,create_time,update_time FROM user WHERE deleted=0 AND name NOT LIKE ‘%m%’ AND email LIKE ‘t%’
10.5 id 在子查询中查出来
@Test
void test05(){
//id 在子查询中查出来
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(wrapper);
objects.forEach(System.out::println);
}
Execute SQL:
SELECT id,name,age,email,deleted,create_time,update_time FROM user WHERE deleted=0 AND id IN (select id from user where id < 3)
10.6 通过id来进行排序
@Test
void test06(){
//通过id来进行排序
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
}
Execute SQL:
SELECT id,name,age,email,deleted,create_time,update_time FROM user WHERE deleted=0 ORDER BY id DESC
10.7 附注
11.代码自动生成器(省略)
12.完整版application.yml
#设置开发环境 自动识别到dev 然后那个插件就生效了
spring:
profiles:
active: dev
datasource:
username: root
password: zjj
url: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&userUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
driver-class-name: com.mysql.cj.jdbc.Driver
#数据库 连接配置
#配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0