文章目录
简介
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
特征
- 无入侵
- 强大的CRUD
- 支持lambda
- 支持主键自动生成
- 内置分页插件
快速入门
简单程序
boot3的pom
xml
<!-- <dependency>-->
<!-- <groupId>org.mybatis.spring.boot</groupId>-->
<!-- <artifactId>mybatis-spring-boot-starter</artifactId>-->
<!-- <version>3.0.3</version>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
mapper继承mp提供的类
java
// 即制定了实体类,又制定了表的名字就是book 可以通过注解指定 @TableName()
public interface BookMapper extends BaseMapper<Book> {
}
测试案例
java
List<Book> books =
bookMapper.selectList(null);
System.out.println(books);
常用注解
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息
实体类转换数据库表的规则(约定):
- 类名驼峰转下划线作为表名
- 名为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
常见注解
-
@TableName
:用来指定表名 -
@TableField
:指定表的普通字段信息- 成员变量和数据库字段不一致
- 成员变量以is开头,且是布尔值
- 成员变量和数据库字段关键字冲突
- 成员变量不是数据库字段
-
@TableId
:指定表中主键字段信息-
type:设置主键属性的生成策略,参照IdType枚举,默认为雪花算法生成的id
-
value:设置数据库主键的名称
-
常见配置
yml
# mybatis-plus继承了mybaits原生配置和一些自己独有的配置
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml # Mapper.xml的文件地址 默认值
type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
configuration:
map-underscore-to-camel-case: true # 是否开启驼峰到下划线的映射,默认值
cache-enabled: false # 是否开启二级缓存,默认值
global-config:
db-config:
id-type: assign_id # id为雪花算法生成,默认值
update-strategy: not_null # 更新策略:只更新非空字段,默认值
table-prefix: # 表的前缀 比如指定了为tb_ 我们实体类为User 那么查询的数据库的表就是tb_user
# 逻辑删除配置
logic-delete-value: xxx # 指定代表逻辑删除的字段名
logic-not-delete-value: 0 # 0代表没有被删除
logic-delete-field: 1 # 1代表被删除
核心功能
条件构造器
mp支持各种复杂的where条件,可以满足日常开发的需求
java
//查询名字带着o,并且存款大于1000的
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.select(User::getId,User::getUsername,User::getInfo,User::getBalance).
like(User::getUsername,"o").
ge(User::getBalance,1000);
List<User> users = userMapper.selectList(lqw);
users.forEach(System.out::println);
//将用户id为1 2 4的余额扣除200
LambdaUpdateWrapper<User> luw = new LambdaUpdateWrapper<>();
luw.setSql("balance = balance - 200").in(User::getId,1L,2L,4L);
userMapper.update(null,luw);
/*
QueryWrapper和LambdaQueryWrapper的区别就是后者使用Lambda语法,防止硬编码
原理还是利用反射机制获取对应的字段名
*/
自定义SQL
什么时候使用:where条件之外的部分使用mp不方便直接实现,需要拼接这样违背的企业开发的规范
利用mp的wrapper来构建复杂的where条件,然后自己定义SQL语句的剩下的部分
where条件交付给mp,其他的sql我们来自定义
操作步骤:
1、基于wrapper构建where条件
2、自定义SQL方法调用
java
LambdaUpdateWrapper<User> lqw = new LambdaUpdateWrapper<User>().in(User::getId,1L,2L,4L);
int amount = 200;
userMapper.updateBalanceByIds(lqw,amount);
3、在mapper方法参数中用Param
注解声明wrapper
变量名称,必须是ew
java
void updateBalanceByIds(@Param("ew") LambdaUpdateWrapper<User> lqw, @Param("amount") int amount);
4、书写sql语句,使用wrapper条件
xml
<update id="updateBalanceByIds">
update user
<set>
balance = balance - #{amount}
</set>
${ew.customSqlSegment}
</update>
Service接口
基本的使用
- 自定义接口继承IService接口
- 自定义接口实现继承ServiceImpl
- 注意:指定泛型(mapper、entity)
java
/*接口继承IService
需要指定一个泛型:操作的实体类
*/
public interface IUserService extends IService<User> {
}
/*实体类继续ServiceImpl
指定两个泛型
第一个:需要操作的mapper
第二个:对应的实体类
*/
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
Lambda操作
java
//lambda的查找操作
public List<User> queryUsers(UserQuery query) {
return lambdaQuery().like(query.getName() != null, User::getUsername, query.getName())
.eq(query.getStatus() != null, User::getStatus, query.getStatus())
.gt(query.getMinBalance() != null, User::getBalance, query.getMinBalance())
.lt(query.getMaxBalance() != null, User::getBalance, query.getMaxBalance())
.list();
}
//lambda的更新操作
int remainBalance = user.getBalance() - money;
lambdaUpdate()
.set(remainBalance==0,User::getStatus,2)
.set(User::getBalance,remainBalance)
.eq(User::getId,id)
.eq(User::getBalance,user.getBalance())//防止并发安全问题
.update();
批量新增
要在yml文件中增加参数rewriteBatchedStatements=true
yml
jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
扩展功能
代码生成
代码
模板:MyBatisPlus提供
数据库相关配置:读取数据库获取信息
开发者自定义配置:手工配置
导入依赖
xml
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.0</version>
</dependency>
<!--模板技术引擎-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
插件
mybatisplus
静态工具
和IService相同,但是需要多传一个class字节码
使用场景:需要service之间相互注入使用Db,防止循环注入
比如
java
lambdaUpdate()
.set(remainBalance==0,User::getStatus,2)
.set(User::getBalance,remainBalance)
.eq(User::getId,id)
.eq(User::getBalance,user.getBalance())//防止并发安全问题
.update();
//=>
Db.lambdaUpdate(User.class)
.set(remainBalance==0,User::getStatus,2)
.set(User::getBalance,remainBalance)
.eq(User::getId,id)
.eq(User::getBalance,user.getBalance())//防止并发安全问题
.update();
逻辑删除
逻辑删除就是基于代码的逻辑模拟删除效果,并不是真正删除数据
- 在表中添加一个字段标记数据是否被删除
- 当删除数据时把标记置为1
- 查询时只查询标记为0的数据
MybatisPlus提供了逻辑删除功能,无需改变方法调用的方式,而是在底层帮我们自动修改CRUD的语句。我们要做的就是在application.yaml文件中配置逻辑删除的字段名称和值即可:
yml
mybatis-plus:
global-config:
db-config:
# 逻辑删除配置
logic-delete-value: deleted # 指定代表逻辑删除的字段名,类型可以时Boolean或者Integer
logic-not-delete-value: 0 # 0代表没有被删除(默认为0)
logic-delete-field: 1 # 1代表被删除(默认为1)
逻辑删除也存在一些问题:
- 逻辑删除会导致数据库数据越来越多,影响查询效率
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,不太推荐采用逻辑删除功能。如果数据不能删除,可以采用把数据迁移到其它表的办法
枚举处理器
1、配置枚举处理器
yml
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
2、配置枚举
java
public enum UserStatus {
NORMAL(1,"正常"),
FROZEN(2,"冻结"),
;
@EnumValue //代表value属性和数据库对应
@JsonValue //设置给前端返回的值,默认时枚举的名字
private final Integer value;
private final String desc;
UserStatus(Integer value, String desc) {
this.value = value;
this.desc = desc;
}
}
json处理器
数据库保存的为json,但是我们java没有对应的json类型,我们希望自定义一个对象,让数据库的json自动对应,这就使用到json处理器
java
package com.itheima.mp.domain.po;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import com.itheima.mp.enums.UserStatus;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName(value = "user",autoResultMap = true)//自动结果集映射
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String phone;
//json和对象的转换
@TableField(typeHandler = JacksonTypeHandler.class)
private UserInfo info;
private UserStatus status;
private Integer balance;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
插件功能
分页插件
1、配置插件
java
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
//1、初始化核心插件
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//2、配置分页插件
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setMaxLimit(1000L);//设置分页上限
//3、添加分页插件
mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);
//4、返回核心插件
return mybatisPlusInterceptor;
}
2、使用分页插件
java
//简单的分页查询
void testPage(){
Page<User> page = Page.of(1, 2);//页码 每页2个
userService.page(page);
System.out.println(page.getPages());//多少页
System.out.println(page.getTotal());//多少数据
page.getRecords().forEach(System.out::println);//数据
}
//带过滤条件的分页查询 使用lambdaQuery
对于 PageQuery和 PageDTO 的封装
java
@Data
public class PageQuery {
private Integer pageNo;
private Integer pageSize;
private String sortBy;
private Boolean isAsc;
public <T> Page<T> toMpPage(OrderItem ... orders){
// 1.分页条件
Page<T> p = Page.of(pageNo, pageSize);
// 2.排序条件
// 2.1.先看前端有没有传排序字段
if (sortBy != null) {
p.addOrder(new OrderItem(sortBy, isAsc));
return p;
}
// 2.2.再看有没有手动指定排序字段
if(orders != null){
p.addOrder(orders);
}
return p;
}
public <T> Page<T> toMpPage(String defaultSortBy, boolean isAsc){
return this.toMpPage(new OrderItem(defaultSortBy, isAsc));
}
public <T> Page<T> toMpPageDefaultSortByCreateTimeDesc() {
return toMpPage("create_time", false);
}
public <T> Page<T> toMpPageDefaultSortByUpdateTimeDesc() {
return toMpPage("update_time", false);
}
}
java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageDTO<V> {
private Long total;
private Long pages;
private List<V> list;
/**
* 返回空分页结果
* @param p MybatisPlus的分页结果
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> empty(Page<P> p){
return new PageDTO<>(p.getTotal(), p.getPages(), Collections.emptyList());
}
/**
* 将MybatisPlus分页结果转为 VO分页结果
* @param p MybatisPlus的分页结果
* @param voClass 目标VO类型的字节码
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Class<V> voClass) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = BeanUtil.copyToList(records, voClass);
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
/**
* 将MybatisPlus分页结果转为 VO分页结果,允许用户自定义PO到VO的转换方式
* @param p MybatisPlus的分页结果
* @param convertor PO到VO的转换函数
* @param <V> 目标VO类型
* @param <P> 原始PO类型
* @return VO的分页对象
*/
public static <V, P> PageDTO<V> of(Page<P> p, Function<P, V> convertor) {
// 1.非空校验
List<P> records = p.getRecords();
if (records == null || records.size() <= 0) {
// 无数据,返回空结果
return empty(p);
}
// 2.数据转换
List<V> vos = records.stream().map(convertor).collect(Collectors.toList());
// 3.封装返回
return new PageDTO<>(p.getTotal(), p.getPages(), vos);
}
}
乐观锁
拦截器
java
@Bean
MybatisPlusInterceptor mpInterceptor(){
//定义mp拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//添加乐观锁的拦截器
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
属性配置
java
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("book")
public class Book {
@TableId(type = IdType.AUTO)
private Integer id;
private String name;
//乐观锁 需要加拦截器
@Version
private Integer version;
@TableField(select = false)
private Integer online;
}
测试
java
@Test
void testS(){
Book user1 = bookMapper.selectById(1);//version=1
Book user2 = bookMapper.selectById(1);//version=1
user1.setName("平凡的世界 111");
bookMapper.updateById(user1); //version=2
user2.setName("平凡的世界 222");
bookMapper.updateById(user2); //此时的version=1,修改失败
}
//并发操作可以进行控制