1 MyBatisPlus概述
1.1 MyBatis介绍
- MyBatisPlus(简称MP)基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
- MyBatisPlus官网
1.2 MyBatisPlus特性
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
- 支持 Lambda:编写查询条件无需担心字段写错
- 支持主键自动生成
- 内置分页插件
- ……
2 标准数据层开发
2.1 MyBatisPlus的CRUD操作API
2.2 分页功能接口实现
2.2.1 config(配置层)拦截器实现
此处拦截SQL语句,目的是为了拼接,分页条件
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor(){
//1.定义Mp拦截器 ,创建MybatisPlusInterceptor拦截器对象
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加具体的拦截器、添加分页拦截器
mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return mpInterceptor;
}
}
2.2.2 Dao(Mapper)数据访问层(CRUD)操作
实现BaseMapper<>接口
@Mapper
public interface UserDao extends BaseMapper<User> {
}
2.2.3 Junit单元测试进行测试
@Test
public void testPage() {
IPage<User> page = new Page(2, 5);// 分页构造器:设置 当前第几页 一页多少条
IPage<User> pageResult = userDao.selectPage(page, null);
System.out.println(JSON.toJSONString(page));
System.out.println("数据列表" + JSON.toJSONString(pageResult.getRecords()));
System.out.println("当前页码" + pageResult.getCurrent());
System.out.println("每页条数" + pageResult.getSize());
System.out.println("总记录数" + pageResult.getTotal());
System.out.println("总页数" + pageResult.getPages());
}
}
2.3 开启MyBatisPlus日志
# 开启mp的日志(输出到控制台)
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2.4 取消初始化spring日志打印
做法:在resources下新建ogback.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
</configuration>
2.5 取消SpringBoot启动banner图标、关闭mybatisplus启动图标
spring:
main:
banner-mode: off # 关闭SpringBoot启动图标(banner)
# mybatis-plus日志控制台输出
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: off # 关闭mybatisplus启动图标
3 MyBatisPlus DQL
3.1 条件查询API
3.2 条件查询
3.2.1 方式一:按条件查询(常规格式)
//方式一:按条件查询
@Test
public void test1(){
// select * from user where age >= 18 and age < 65
QueryWrapper<User> qw = new QueryWrapper<>();
qw.ge("age",18);
qw.lt("age",65);
List<User> userList = userDao.selectList(qw);
System.out.println(JSON.toJSONString(userList)); //[{"age":28,"id":5,"name":"snake","password":"123456","tel":"12345678910"},{"age":22,"id":6,"name":"张益达","password":"123456","tel":"12345678910"}]
/*
System.out.println(userList);
不进行JSON转换输出[User(id=5, name=snake, password=123456, age=28, tel=12345678910), User(id=6, name=张益达, password=123456, age=22, tel=12345678910)]
* */
}
3.2.2 方式二:按条件查询(lambda格式)
查阅源码优化全局变量处声明对象操作
3.2.2.1全局变量声明
LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery();//lambdaQuery 可用屏蔽底层的具体实现,未来会有变化上层代码无需过多的调整。而且不用new对象减少内存
@Autowired
private UserDao userDao;
3.2.2.2 select * from user where age >= 18 and age < 65
传统语法 | MyBatisPlus语法 | 说明 |
---|---|---|
< | lt | less than |
<= | le | less equal |
> | gt | greater than |
>= | ge | greater equal |
= | eq | equal |
between and | 范围 | |
like | 模糊查询 | |
in | 在in之后的列表中的值,多选一 |
//lambda格式
@Test
public void test2(){
// select * from user where age >= 18 and age < 65
//LambdaQueryWrapper<User> userLambdaQueryWrapper1 = new LambdaQueryWrapper<>();
// LambdaQueryWrapper<User> userLambdaQueryWrapper = Wrappers.lambdaQuery();
userLambdaQueryWrapper.ge(User::getAge,18).lt(User::getAge,65);
List<User> userList = userDao.selectList(userLambdaQueryWrapper);
System.out.println(JSON.toJSONString(userList));
System.out.println("=================");
System.out.println(userList);
}
// 等于
@Test
public void test3(){
//select * from user where name = "tom"
userLambdaQueryWrapper.eq(User::getName,"tom");
List<User> userList = userDao.selectList(userLambdaQueryWrapper);
System.out.println(JSON.toJSONString(userList));
}
3.3 MyBatisPlus中null值判断
/**
* null值判断
* 判断 字段值是否为null 不为null才拼接查询条件
*/
@Test
public void test31(){
//模拟前端传的参数
//select * from user where name = null
User user = new User();
/* if (user.getName()!=null){
userLambdaQueryWrapper.eq(User::getName,user.getName());
}健壮性判断*/
userLambdaQueryWrapper.eq(user.getName()!= null,User::getName,user.getName());
//此处user.getName()为空,User::getName与user.getName()作对比,
// 即 在数据库实体类中的name属性="null" 做判断条件
List<User> userList = userDao.selectList(userLambdaQueryWrapper);
System.out.println(JSON.toJSONString(userList));
}
// like模糊查询
@Test
public void test4(){
//select * from user where name like "j%"
userLambdaQueryWrapper.like(User::getName,"j");
List<User> list = userDao.selectList(userLambdaQueryWrapper);
System.out.println(list);
}
/**
* between
*/
@Test
public void test5(){
//select * from user where age between 16 and 28
userLambdaQueryWrapper.between(User::getAge, 16, 28);
List<User> list = userDao.selectList(userLambdaQueryWrapper);
System.out.println(list);
}
/**
* in
*/
@Test
public void test6(){
//select * from user where id in (1,2,3)
List<Integer> inList = Arrays.asList(1, 2, 3);
userLambdaQueryWrapper.in(User::getId, inList);
List<User> list = userDao.selectList(userLambdaQueryWrapper);
System.out.println(list);
}
3.4 批量(Batch)操作
/**
* 根据id列表批量查询
*/
@Test
public void test1(){
List<Integer> ids = Arrays.asList(1, 2, 3);//将一个变长参数或者数组转换成List
List<User> userList = userDao.selectBatchIds(ids);
System.out.println(userList);
}
/**
* 根据id列表批量删除
*/
@Test
public void test2(){
List<Integer> ids = Arrays.asList(1, 2, 3);
int count = userDao.deleteBatchIds(ids);
System.out.println(count);
}
3.5 查询投影(聚合查询)【查询字段、分组、分页】
/**
* 聚合查询一般用: selectMaps
*/
@Test
public void test(){
//select tel, count(*) as cnt from user group by tel order by cnt;
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.select("tel","count(*) as cnt");
userQueryWrapper.groupBy("tel");
userQueryWrapper.orderByAsc("cnt");
List<Map<String, Object>> mapList = userDao.selectMaps(userQueryWrapper); //selectMaps:根据 Wrapper 条件,查询全部记录
System.out.println(JSON.toJSONString(mapList));
}
3.6 字段映射与表名映射
注解 | 说明 |
---|---|
@TableField | 通过value属性(value="数据库中实际字段名") ,设置当前属性对应的数据库表中的字段关系 |
@TableField | 通过exist属性(true存在;false不存在) ,设置属性在数据库表字段中是否存在,默认为true。不能与value合并使用 |
@TableField | 通过select属性(true参与;false不参与) :设置该属性是否参与查询。此属性与select()映射配置不冲突。 |
@TableName | 通过value属性@TableName("数据库中实际表名") ,设置当前类对应的数据库表名称 |
4 DML编程控制
4.1 id生成策略控制(Insert)
注解 | 说明 |
---|---|
@TableId | 1、AUTO(0): 使用数据库id自增策略控制id生成; 2、NONE(1):不设置id生成策略;3、INPUT(2):用户手工输入id;4、ASSIGN_ID(3):雪花算法生成id (可兼容数值型与字符串型); 5、ASSIGN UUID(4):以UUID生成算法作为id生成策略 |
4.1.1 全局配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
table-prefix: tbl_
4.2 逻辑删除(Delete/Update)
4.2.1 数据库表中添加逻辑删除字段
4.2.2 实体类中添加对应字段,并设定当前字段为逻辑删除标记字段
package com.itheima.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
@Data
public class User {
private Long id;
//逻辑删除字段,标记当前记录是否被删除
@TableLogic
private Integer deleted;
}
4.2.2.1 全局配置逻辑删除字面值(不建议)
mybatis-plus:
global-config:
db-config:
table-prefix: tbl_
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
5 乐观锁(Update)
乐观锁的解决思想(版本号机制或 CAS 算法):
版本号机制:首先,给数据库添加一个version(int)
的标记字段,然后,在多个线程同时访问数据库时也都获取到version的值,在提交更新时,若刚才读取到的version
为当前数据库中的version
时才更新,更新过后version
值会发生改变,其他线程再提交更新时,获取到的version
值和当前数据库version
值不一致,提交更新失败。
举一个简单的例子 :假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。
- 操作员 A 此时将其读出(
version
=1 ),并从其帐户余额中扣除 $50( $100-$50 )。 - 在操作员 A 操作的过程中,操作员 B 也读入此用户信息(
version
=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。 - 操作员 A 完成了修改工作,将数据版本号( version=1 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本等于数据库记录当前版本,数据被更新,数据库记录
version
更新为 2 。 - 操作员 B 完成了操作,也将版本号(
version
=1 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 1 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须等于当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样就避免了操作员 B 用基于 version
=1 的旧数据修改的结果覆盖操作员 A 的操作结果的可能。
5.2 乐观锁案例解决
5.2.1 数据库中添加标记字段version
5.2.2 实体类中添加相应字段,并设定当前字段为逻辑删除标记字段
注解 | 说明 |
---|---|
@Version | MP中自带对乐观锁定支持的注解 |
@Data
public class User {
private Long id;
@Version
private Integer version;
}
5.2.3 配置乐观锁拦截器实现锁机制对应的动态SQL语句拼接
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mpInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
5.2.4 使用乐观锁机制在修改前必须先获取到对应数据的verion方可正常进行
@Test
public void testUpdate() {
/*User user = new User();
user.setId(3L);
user.setName("Jock666");
user.setVersion(1);
userDao.updateById(user);*/
//1.先通过要修改的数据id将当前数据查询出来
//User user = userDao.selectById(3L);
//2.将要修改的属性逐一设置进去
//user.setName("Jock888");
//userDao.updateById(user);
//1.先通过要修改的数据id将当前数据查询出来
User user = userDao.selectById(3L); //version=3
User user2 = userDao.selectById(3L); //version=3
user2.setName("Jock aaa");
userDao.updateById(user2); //version=>4
user.setName("Jock bbb");
userDao.updateById(user); //verion=3?条件还成立吗?
}
6 简单CURD代码快速生成器
按以下方法操作,注意:此
插件只针对单表简单CURD
操作有效