文章目录
- 简介
- 快速入门
- 简单程序
- 常用注解
- 常见配置
- 核心功能
- 条件构造器
- 自定义SQL
- Service接口
- 基本的使用
- Lambda操作
- 批量新增
- 扩展功能
- 代码生成
- 代码
- 插件
- 静态工具
- 逻辑删除
- 枚举处理器
- json处理器
- 插件功能
- 分页插件
- 乐观锁
简介
MyBatisPlus(简称MP)是基于MyBatis框架基础上开发的增强型工具,旨在简化开发、提高效率
特征
- 无入侵
- 强大的CRUD
- 支持lambda
- 支持主键自动生成
- 内置分页插件
快速入门
简单程序
boot3的pom
<!-- <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提供的类
// 即制定了实体类,又制定了表的名字就是book 可以通过注解指定 @TableName()
public interface BookMapper extends BaseMapper<Book> {
}
测试案例
List<Book> books =bookMapper.selectList(null);System.out.println(books);
常用注解
MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息
实体类转换数据库表的规则(约定):
- 类名驼峰转下划线作为表名
- 名为id的字段作为主键
- 变量名驼峰转下划线作为表的字段名
常见注解
-
@TableName
:用来指定表名 -
@TableField
:指定表的普通字段信息- 成员变量和数据库字段不一致
- 成员变量以is开头,且是布尔值
- 成员变量和数据库字段关键字冲突
- 成员变量不是数据库字段
-
@TableId
:指定表中主键字段信息-
type:设置主键属性的生成策略,参照IdType枚举,默认为雪花算法生成的id
-
value:设置数据库主键的名称
-
常见配置
# 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条件,可以满足日常开发的需求
//查询名字带着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的余额扣除200LambdaUpdateWrapper<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方法调用
LambdaUpdateWrapper<User> lqw = new LambdaUpdateWrapper<User>().in(User::getId,1L,2L,4L);int amount = 200;userMapper.updateBalanceByIds(lqw,amount);
3、在mapper方法参数中用Param
注解声明wrapper
变量名称,必须是ew
void updateBalanceByIds(@Param("ew") LambdaUpdateWrapper<User> lqw, @Param("amount") int amount);
4、书写sql语句,使用wrapper条件
<update id="updateBalanceByIds">update user<set>balance = balance - #{amount}</set>${ew.customSqlSegment}</update>
Service接口
基本的使用
- 自定义接口继承IService接口
- 自定义接口实现继承ServiceImpl
- 注意:指定泛型(mapper、entity)
/*接口继承IService
需要指定一个泛型:操作的实体类
*/
public interface IUserService extends IService<User> {
}/*实体类继续ServiceImpl
指定两个泛型
第一个:需要操作的mapper
第二个:对应的实体类*/
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
}
Lambda操作
//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
jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
扩展功能
代码生成
代码
模板:MyBatisPlus提供
数据库相关配置:读取数据库获取信息
开发者自定义配置:手工配置
导入依赖
<!--代码生成器--><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,防止循环注入
比如
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文件中配置逻辑删除的字段名称和值即可:
mybatis-plus:global-config:db-config:# 逻辑删除配置logic-delete-value: deleted # 指定代表逻辑删除的字段名,类型可以时Boolean或者Integerlogic-not-delete-value: 0 # 0代表没有被删除(默认为0)logic-delete-field: 1 # 1代表被删除(默认为1)
逻辑删除也存在一些问题:
- 逻辑删除会导致数据库数据越来越多,影响查询效率
- SQL中全都需要对逻辑删除字段做判断,影响查询效率
因此,不太推荐采用逻辑删除功能。如果数据不能删除,可以采用把数据迁移到其它表的办法
枚举处理器
1、配置枚举处理器
mybatis-plus:configuration:default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
2、配置枚举
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处理器
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、配置插件
@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor(){//1、初始化核心插件MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//2、配置分页插件PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();paginationInnerInterceptor.setMaxLimit(1000L);//设置分页上限//3、添加分页插件mybatisPlusInterceptor.addInnerInterceptor(paginationInnerInterceptor);//4、返回核心插件return mybatisPlusInterceptor;}
2、使用分页插件
//简单的分页查询 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 的封装
@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);}
}
@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);}
}
乐观锁
拦截器
@BeanMybatisPlusInterceptor mpInterceptor(){//定义mp拦截器MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();//添加乐观锁的拦截器mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return mybatisPlusInterceptor;}
属性配置
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("book")
public class Book {@TableId(type = IdType.AUTO)private Integer id;private String name;//乐观锁 需要加拦截器@Versionprivate Integer version;@TableField(select = false)private Integer online;
}
测试
@Testvoid testS(){Book user1 = bookMapper.selectById(1);//version=1Book user2 = bookMapper.selectById(1);//version=1user1.setName("平凡的世界 111");bookMapper.updateById(user1); //version=2user2.setName("平凡的世界 222");bookMapper.updateById(user2); //此时的version=1,修改失败}//并发操作可以进行控制