MyBatisPlus
Mybatis-Plus是一个MyBatis的增强工具,在mybatis的基础上只做增强不做改变
简化开发,提高效率
MP的特性
无侵入
只做增强不做改变
损耗小
启动时自动注入基本CRUD,性能几乎无循环
强大的CRUD操作
内置通用Mapper和Service实现大部分单表操作
支持Lambda形式调用
通过Lambda表达式编写各类的查询条件
支持主键自动生成
支持4种主键策略(自增,雪花算法,UUID等)
支持ActiveRecord模式
实体类继承Model即可进行CRUD操作
支持自定义全局通用操作
全局通用方法注入
内置代码生成器
可快速生成各层代码
内置分页插件
基于MyBatis进行物理分页
分页插件支持多种数据库
内置性能分析插件
内置全局拦截插件
MyBatisPlus配置
依赖注入
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
配置Datasource
//控制台打印sql配置
#mybatis-plus配置控制台打印完整带参数SQL语句
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
定义实体类
package com.cfjg.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 实体类基于注解与表进行映射
*/
@Data
@Builder
// 指定表名
@TableName("tb_user")
@NoArgsConstructor
@AllArgsConstructor
public class User {
//主键字段注解
//设置主键自动生成方法
//AUTO(0),increment自增
//NONE(1),跟随全局
//INPUT(2),自定义主键,自己输入
//ASSIGN_ID(3),雪花算法
//雪花算法生成的id为19为数字,由时间戳/数据中心/机器标识/序列号四个部分组成
//ASSIGN_UUID(4);UUID
@TableId(value = "id",type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
//忽略该属性和数据库表中的映射关系
@TableField(exist = false)
private String ignore
}
MyBatisPlus实现数据库操作
在Mapper层继承BaseMapper
//泛型内写表对应的实体类类型
UserMapper extends BaseMapper<User>
普通单表操作都在接口中进行定义
直接调用接口方法即可
注:MP的更新操作不会将属性的空值覆盖原本的值
@Test
public void testUserInsert(){
User user = User
.builder()
.age(19)
.build();
userMapper.insert(user);
}
分页查询
需要先配置分页拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,-1不受限制
paginationInterceptor.setMaxLimit(-1L);
interceptor.addInnerInterceptor(paginationInterceptor);
return interceptor;
}
}
进行分页查询
@Test
public void testSelectPage() {
//创建分页对象,查询完成数据会回填到page对象中
Page<User> page = new Page<>(1,2);
//构建条件对象
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age","19");
userMapper.selectPage(page, wrapper);
System.out.println(page.getRecords());
System.out.println(page.getTotal());
System.out.println(page.getPages());
}
条件查询
使用wrapper接口实现条件查询和更新
//查询接口
QueryWrapper
//更新接口
UpdateWrapper
QueryWrapper常用API
//equal
eq( ) : 等于 =
//not equal
ne( ) : 不等于 <> 或者 !=
//greater than
gt( ) : 大于 >
//greater equal
ge( ) : 大于等于 >=
//less than
lt( ) : 小于 <
//less equal
le( ) : 小于等于 <=
or():或
between ( ) : BETWEEN 值1 AND 值2
notBetween ( ) : NOT BETWEEN 值1 AND 值2
in( ) : in
notIn( ) :not in
like(): like模糊查询%xxx%
likeLeft():左侧模糊查询%xxx
likeRight():右侧模糊查询xxx%
orderByAsc()
orderByDesc()
条件查询操作
@Test
public void testCondition(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("age",19).or().lt("age",30);
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
限定字段查询
通过wrapper限定select查询的字段
wrapper.eq("age",19).or().lt("age",30).select("age");
LambdaQueryWrapper查询
避免使用普通wrapper时出现的硬编码列名问题,通过传入对应属性值的get方法解析出这个属性对应的字段
@Test
public void testLambdaSelect(){
LambdaQueryWrapper<User> eq = Wrappers.<User>lambdaQuery()
.eq(User::getAge, 19);
userMapper.selectList(eq);
Wrappers.<User>lambdaUpdate()
.eq(User::getAge,19);
}
LambdaQueryWrapper删除
@Test
public void testDelete(){
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.gt(User::getAge, 19);
userMapper.delete(wrapper);
}
LambdaQueryWrapper更新
@Test
public void testUpdate(){
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.eq(User::getUserName,null);
User user = new User();
user.setName("a");
user.setAge(11);
user.setUserName("b");
userMapper.update(user,wrapper);
}
定义查询接口实现分页查询
对自定义SQL语句进行分页查询
@Test
public void sqlTest(){
Page<User> page = new Page<>(1,2);
userMapper.findGtIdByPage(page,1);
page.getRecords().forEach(System.out::println);
}
mybatisplus对应的xml文件在resource下的/mapper/**/*.xml读取
sql:
<?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.itheima.mapper.UserMapper">
<select id="findGtIdByPage" resultType="com.itheima.pojo.User">
select * from tb_user where id > #{id}
</select>
</mapper>
通过${ew.}实现条件构造器复用
Wrapper ew
//传入构造器替换select后的内容
${ew.sqlSelect}
//传入构造器替换sql中的条件,放在where后面
{ew.customSqlSegment}
MP实现Service封装
继承公共接口
//service接口继承Iservice接口
public interface UserService extends IService<User>
//service实现类继承ServiceImpl接口
//泛型内写Mapper对象和实体类对象
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
MP封装Service实现CRUD操作
大体和持久层一致
先构造条件构造器
再传入条件构造器实现条件查询
@Test
public void UserTest(){
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.eq(User::getUserName,"test");
System.out.println(userService.list(wrapper));
}
MP代码生成器
自动生成控制层业务层mapper层和xml代码
只需指定数据库和生成的表名即可
导入依赖
<!--mp 代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
代码生成器
package com.itheima;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.sql.Types;
import java.util.Collections;
public class CodeGenerator {
public static void main(String[] args) {
String url = "jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC";
String username = "root";
String password = "root";
FastAutoGenerator.create(url, username, password).globalConfig(builder -> {
builder.author("itheima") // 设置作者
.enableSwagger() // 开启 swagger 模式
.outputDir("F:\\code\\mp"); // 指定输出目录
}).dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
int typeCode = metaInfo.getJdbcType().TYPE_CODE;
if (typeCode == Types.SMALLINT) {
// 自定义类型转换
return DbColumnType.INTEGER;
}
return typeRegistry.getColumnType(metaInfo);
})).packageConfig(builder -> {
builder.parent("com.itheima") // 设置父包名
.moduleName("user") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.xml, "F:\\code\\mp")); // 设置mapperXml生成路径
}).strategyConfig(builder -> { //策略配置
builder.addInclude("tb_user") // 设置需要生成的表名
.addTablePrefix("tb_") // 设置过滤表前缀
.entityBuilder() //设置实体构建器,设置属性
.enableFileOverride() //文件覆盖
.enableLombok() //开启lombok
.controllerBuilder().enableRestStyle() //启用RestController
.enableFileOverride() //文件覆盖
.mapperBuilder().enableBaseResultMap() //开启生成 resultMap 结果映射
.enableFileOverride() //文件覆盖
;
}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
MybatisPlus逻辑删除
即在数据库中添加一个is_delete字段用来表示数据是否被删除
而非真的将数据删除,只是对数据进行标注
用这种方法删除的数据是可逆的,可以恢复
mybatisplus配置逻辑删除
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
实体类字段加@TableLogic注释
@TableLogic
private Integer is_deleted;
开启逻辑删除功能后,mp在删除查询和更新时会自动加上逻辑删除字段为未删除的条件
MybatisPlus乐观锁
相对于悲观锁而言,乐观锁假设数据在一般情况下不会发生冲突
只有在数据进行提交和更新时才会正式对数据的冲突与否进行检测
如果冲突,则返回错误的信息让用户决定后续操作
相比于悲观锁,并发下的程序吞吐量更大
乐观锁的实现方式
版本号控制
通过数据量增加一个version版本字段,判断每次读取的version版本和数据库记录的是否一致,如果不一致则过期
//通过对实体类字段添加@version注解来标记锁
@version
private Integer version
注册配置类
需要先注册乐观锁插件(乐观锁拦截器)
//扫描我们的repository文件夹
@MapperScan("com.trainingl.repository")
@EnableTransactionManagement
@Configuration //配置类
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//注册乐观锁插件
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
MybatisPlus自动填充
对于创建时间create_time,更新时间update_time这些每次对数据修改时都要设置的字段,如果在代码中进行手动设置会显得冗余且容易出错
mp提供了自动填充的功能
实现过程
通过给实体类添加@TableField注解来进行指定填充的时间
FieldFill字段枚举类有四种自动填充处理策略,分别是Default (默认不处理)、Insert (插入时填充)、Update (更新时填充)、Insert_Update (插入和更新时填充)
@TableField(fill = FieldFill.INSERT)
private LocalDateTime creatTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
再通过实现MetaObjectHandler接口对填充策略进行设计
/**
* mybatis-plus自动填充策略设置
*/
@Slf4j
@Component
public class DatetimeMetaObjectHandler implements MetaObjectHandler {
//进行插入时填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("mybatis-plus 开始在你插入的时候 字段填充字段......");
this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
//进行修改操作时填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("mybatis-plus 开始在你修改的时候 字段填充字段......");
this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
}
}
MybatisPlus通用枚举
实现数据库存入时指定值保存,读取时指定值展示
通过继承IEnum,@EnumValue实现
读取数据时可以通过使用@JsonValue执行显示的值
@AllArgsConstructor
public enum SexEnum implements IEnum<Integer> {
boy(0, "男孩"),
girl(1, "女孩");
private final Integer code;
// 序列化枚举值为 接口出参;接口入参(RequestBody),反序列化为枚举值
@JsonValue
private final String name;
@Override
public Integer getValue() {
return code;
}
}
或
@AllArgsConstructor
public enum SexEnum {
boy(0, "男孩"),
girl(1, "女孩");
@EnumValue
private final Integer code;
@JsonValue
private final String name;
}
MybatisPlus拦截器插件
防全表更新和删除插件
全表更新时会抛出异常
@Configuration
public class MybatisPlusConfig {
//拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//注册分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//注册防全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
MybatisPlus流式查询
流式查询:查询成功后不是返回一个集合而是返回一个迭代器,每次从迭代器取出一条结果进行查询
可以有效的降低内存使用
执行流式查询时,框架不负责对数据库连接进行关闭,需要在取完数据之后进行关闭
Mybatis的流式查询
提供了一个Cursor接口用于流式查询
继承了 java.io.Closeable 和 java.lang.Iterable 接口
所以流式查询的过程是可关闭和可遍历的
提供了三个方法
//用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据
isOpen()
//用于判断查询结果是否全部取完
isConsumed()
//返回已经获取了多少条数据
getCurrentIndex()
MyBatisPlus实现流式查询
需要自定义接口实现