MyBatis-Plus
一、基本概念
1、简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,
在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2、特性
1.无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
2.损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
3.强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分CRUD 操作,更有强大的条件构造器,满足各类使用需求
4.支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
5.支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由
配置,完美解决主键问题
6.支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强
大的 CRUD 操作
7.支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
8.内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
9.内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
10.分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
11.内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
12.内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防 误操作
二、快速使用
1. 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
2. application.yml 配置文件
mybatis-plus:
configuration:
#开启sql日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
# 是否打印 Logo banner
banner: true
# 是否初始化 SqlRunner
#不打开,使用SqlRunner会报错:Cause: java.lang.IllegalArgumentException: Mapped
# Statements collection does not contain value
#for com.baomidou.mybatisplus.core.mapper.SqlRunner.SelectList
enableSqlRunner: true
3. 引导类中添加 @MapperScan 注解,扫描 Mapper 文件夹
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4.业务逻辑层接口继承IService接口
public interface UserService extends IService<UserEntity> {
/**
* 注册
* @param vo 用户请求Vo
* @return
*/
JsonResult postUser(UserRequestVo vo);
}
5.业务逻辑层接口继承 ServiceImpl 类
在业务逻辑层实现类中调用IService或者Mapper封装的CRUD方法
@Slf4j
@Service
@SuppressWarnings({"rawtypes","unchecked"})
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements UserService {
/**
* 注册
* @param vo 用户请求实体
* @return
*/
@Override
public JsonResult postUser(UserRequestVo vo) {
boolean phoneExistence = this.checkPhoneExistence(vo.getPhoneNumber());
log.info("用户是否已经存在:{}",phoneExistence);
if(phoneExistence){
return JsonResult.fail("该手机号已注册");
}
String password = vo.getPassword();
boolean isChinese = this.isChinese(password);
if(isChinese){
return JsonResult.fail("密码包含中文或者中文符号");
}
//对密码进行加密
String uuid = UUID.randomUUID().toString().replace("-", "");
String encryptByKey = AesFront.aesEncryptByKey(password, uuid);
log.info("加密后:encryptByKey{}",encryptByKey);
UserEntity entity = UserEntity.builder().build();
BeanUtils.copyProperties(vo,entity);
entity.setPassword(encryptByKey);
entity.setSalt(uuid);
entity.setOpenId(UUID.randomUUID().toString().replace("-", ""));
boolean flag = this.save(entity);
return flag ? JsonResult.ok("注册成功") : JsonResult.fail("注册失败");
}
}
6.Mapper继承 BaseMapper接口
@Mapper
public interface UserMapper extends BaseMapper<UserEntity>{
}
7.entity继承 自动填充 的BaseEntity
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@TableName("user")
public class UserEntity extends BaseEntity implements Serializable {
private static final long serialVersionUID = -7690860956874940192L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 姓名
*/
private String userName;
/**
* 手机号
*/
private String phoneNumber;
/**
* 密码
*/
private String password;
/**
* 性别:1:男 2:女
*/
private String gender;
/**
* 学历
*/
private String education;
/**
* 家庭地址
*/
private String address;
/**
* 盐
*/
private String salt;
/**
* 用户编号
*/
private String openId;
/**
* 用户状态:1:已入职,2,实习,3:已转正,4:已离职,5:已转岗
*/
private String userState;
}
8.自动填充 BaseEntity
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 953878410015883109L;
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 逻辑删除
* value = "" 默认的原值
* delval = "" 删除后的值
* :@TableLogic(value="默认的原值",delval="删除后的值")
*
* 删:逻辑删除
* 改:sql默认添加在where条件字段中 and is_del = 0
* 查:sql自动加上了条件未删除条件:SELECT * from xxxtable where is_del =0
*/
@TableLogic(value = "0",delval = "1")
private Integer isDel;
}
9.自定义填充器
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final String UPDATE = "updateBy";
private static final String UPDATE_TIME = "updateTime";
/**
* 新增 给公共的字段赋值
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
LoginUser loginUser = UserThreadLocal.getLoginUser();
log.info("MyMetaObjectHandler=insertFill=loginUser:{}",loginUser);
String userName = ObjectUtils.isNotNull(loginUser) ? loginUser.getUsername() : null;
this.setFieldValByName("createBy",userName,metaObject);
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName(UPDATE,userName,metaObject);
this.setFieldValByName(UPDATE_TIME,new Date(),metaObject);
}
/**
* 修改 给公共的字段赋值
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
LoginUser loginUser = UserThreadLocal.getLoginUser();
log.info("MyMetaObjectHandler=updateFill=loginUser:{}",loginUser);
this.setFieldValByName(UPDATE,ObjectUtils.isNotNull(loginUser) ? loginUser.getUsername() : null,metaObject);
this.setFieldValByName(UPDATE_TIME,new Date(),metaObject);
}
}
mybatis-plus日志打印内容:
三、常用注解
@TableName("user") 表名注解,标识实体类对应的表
@TableId(value = "id", type = IdType.AUTO) 主键注解
@TableField("name") 字段注解(非主键)
属性与字段名不相同需要加@TableField("name")
相同则可以不加@TableField(属性命名规则:数
据库字段去掉下划线,下个单词首字母大写)
updateStrategy 属性
FieldStrategy.IGNORED:在更新操作期间将忽略该字段,如果该字段为null 将在数据库设置为null
FieldStrategy.NOT_NULL:该字段将插入或更新为非空。
FieldStrategy.NULL:该字段将插入或更新为空。
FieldStrategy.DEFAULT:当值不为 null 时将更新该字段
@TableLogic(value = "0",delval = "1") 逻辑删除
value = "" 默认的原值
delval = "" 删除后的值
@OrderBy 内置 SQL 默认指定排序,优先级低于 wrapper 条
件查询
isDesc:是否倒序查询
sort: 数字越小越靠前
@TableField(fill = FieldFill.INSERT) 自动填充的字段
DEFAULT: 默认不处理
INSERT: 插⼊时填充字段
UPDATE: 更新时填充字段
INSERT_UPDATE: 插⼊和更新时填充字段
@TableField(exist = false) 表示该属性不为数据库表字段但又是必须使用的。
四、IService CRUD 接口
一、项目中的使用:
1.业务逻辑层接口继承IService
public interface UserService extends IService<UserEntity> {}
2.业务逻辑层接口实现类继承ServiceImpl
@Slf4j
@Service
@SuppressWarnings({"rawtypes","unchecked"})
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> implements
UserService {}
3.调用
1.新增
boolean flag = this.save(entity);
2.查询
LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(UserEntity::getPhoneNumber,phoneNumber);
queryWrapper.ne(UserEntity::getUserState, UserStatusEnum.RESIGNED.getStateCode());
return this.getOne(queryWrapper);
3.修改
LambdaUpdateWrapper<UserEntity> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(UserEntity::getUserName,vo.getUserName());
updateWrapper.eq(UserEntity::getId,vo.getId());
//调用 boolean update(Wrapper<T> updateWrapper)方法,自动填充会失效
boolean flag = this.update(UserEntity.builder().build(),updateWrapper);
4.分页查询
LambdaQueryWrapper<UserEntity> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(vo.getUserName()),UserEntity::getUserName,vo.getUserName());
queryWrapper.like(StringUtils.isNotBlank(vo.getPhoneNumber()),UserEntity::getPhoneNumber,vo.getPhoneNumber());
queryWrapper.eq(StringUtils.isNotBlank(vo.getEducation()),UserEntity::getEducation,vo.getEducation());
queryWrapper.orderByDesc(UserEntity::getCreateTime);
Page<UserEntity> entities = this.baseMapper.selectPage(page, queryWrapper);
return JsonResult.ok(new EuDataGridResult<>(entities.getTotal(),entities.getRecords()));
5.查询总数
LambdaQueryWrapper<CustomizedOrder> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CustomizedOrder::getUserOpenId,vo.getUserOpenId());
//下单结束时间
queryWrapper.lt(CustomizedOrder::getCreateAt,vo.getEndPlaceOrder());
//下单开始时间
queryWrapper.ge(CustomizedOrder::getCreateAt,vo.getBeginPlaceOrder());
return this.baseMapper.selectCount(queryWrapper);
五、Mapper CRUD 接口
一、项目中的使用:
1.接口实现BaseMapper
@Mapper
public interface UserMapper extends BaseMapper<UserEntity>{
}
2.调用
int insert = this.baseMapper.insert(entity);
六、逻辑删除
只对自动注入的 sql 起效: 执行原生sql:是否删除条件不回自动添加
插入: 不作限制
查找: 追加 where 条件过滤掉已删除数据,如果使用 wrapper.entity 生成的 where 条件也会自动
追加该字段,追加未删除的条件
更新: 追加 where 条件防止更新到已删除数据,如果使用 wrapper.entity 生成的 where 条件也会
自动追加该字段,追加未删除的条件
删除: 转变为 更新(逻辑删除),更改是否删除字段的值
一、方法一 配置文件配置
步骤 1 application.yml
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)
步骤 2: 实体类字段上加上@TableLogic注解
@TableLogic
private Integer deleted;
二、方法二 直接在Entity写@TableLogic,并指定value和delval 的属性值
/**
* 逻辑删除
* value = "" 默认的原值
* delval = "" 删除后的值
* :@TableLogic(value="默认的原值",delval="删除后的值")
*
* 删:逻辑删除
* 改:sql默认添加在where条件字段中 and is_del = 0
* 查:sql自动加上了条件未删除条件:SELECT * from xxxtable where is_del =0
*/
@TableLogic(value = "0",delval = "1")
private Integer isDel;
七、自动填充功能
执行原生sql:不会自动填充,是否删除条件不回自动添加
一、填充原理:直接给entity的属性设置值若无值则入库会是null
二、自动填充失效:
使用boolean update(Wrapper updateWrapper)这个方法,自动填充会失效
boolean update(Wrapper updateWrapper)的底层实现为:
default boolean update(Wrapper<T> updateWrapper) {
return this.update((Object)null, updateWrapper);
}
属性自动填充需要从第一个参数获取Object实体类,自动填充的核心方法:populateKeys中会判断
tableInfo就是获取的实体类对象,所以导致属性自动填充失效.
if (null == tableInfo) {
/* 不处理 */
return parameterObject;
}
使用带实体的修改方法
boolean update(T entity, Wrapper<T> updateWrapper)
例:new一个空的entity
this.update(new ServiceItemIcon(),
Wrappers.<ServiceItemIcon>lambdaUpdate()
.set(!ObjectUtils.isEmpty(status), ServiceItemIcon::getStatus, status)
.eq(ServiceItemIcon::getUid, uid)
);
三、配置步骤
步骤 1: 自定义填充器
package com.alib.scenery.config.obturator;
import com.alib.scenery.common.threadLocal.UserThreadLocal;
import com.alib.scenery.vo.user.request.LoginUser;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Description: 填充器 执行原生sql不会自动填充(如:SqlRunner)
*/
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final String UPDATE = "updateBy";
private static final String UPDATE_TIME = "updateTime";
/**
* 新增 给公共的字段赋值
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
LoginUser loginUser = UserThreadLocal.getLoginUser();
log.info("MyMetaObjectHandler=insertFill=loginUser:{}",loginUser);
String userName = ObjectUtils.isNotNull(loginUser) ? loginUser.getUsername() : null;
this.setFieldValByName("createBy",userName,metaObject);
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName(UPDATE,userName,metaObject);
this.setFieldValByName(UPDATE_TIME,new Date(),metaObject);
}
/**
* 修改 给公共的字段赋值
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
LoginUser loginUser = UserThreadLocal.getLoginUser();
log.info("MyMetaObjectHandler=updateFill=loginUser:{}",loginUser);
this.setFieldValByName(UPDATE,ObjectUtils.isNotNull(loginUser) ? loginUser.getUsername() : null,metaObject);
this.setFieldValByName(UPDATE_TIME,new Date(),metaObject);
}
}
步骤 2: 定义BaseEntity,每个Entity 需继承BaseEntity
package com.alib.scenery.common.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import java.io.Serializable;
import java.util.Date;
/**
* @Description: 统一自动填充实体(抽取每个表公共的字段) 需要配合填充器才能生效
*
* DEFAULT: 默认不处理
* INSERT: 插⼊时填充字段
* UPDATE: 更新时填充字段
*INSERT_UPDATE: 插⼊和更新时填充字段
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public class BaseEntity implements Serializable {
private static final long serialVersionUID = 953878410015883109L;
@TableField(fill = FieldFill.INSERT)
private String createBy;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/**
* 逻辑删除
* value = "" 默认的原值
* delval = "" 删除后的值
* :@TableLogic(value="默认的原值",delval="删除后的值")
*
* 删:逻辑删除
* 改:sql默认添加在where条件字段中 and is_del = 0
* 查:sql自动加上了条件未删除条件:SELECT * from xxxtable where is_del =0
*/
@TableLogic(value = "0",delval = "1")
private Integer isDel;
}
八、分页拦截器配置
注: 若不配置分页拦截器:mybatis-plus分页 ,records有记录,total却始终为0
/**
* @Description: mybatis-plus 分页的拦截器配置
*/
@Configuration
public class MyBatisPlusConfiguration {
/**
* 解决:mybatis-plus分页 records有记录,total却始终为0
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
九、执行原生sql (使用SqlRunner方式)
注:
执行原生sql不会自动填充(SqlRunner),不初始化SqlRunner会报错:IllegalArgumentException
1.使用SqlRunner需要在application.yml配置文件中配置:
# 是否初始化 SqlRunner
#不打开使用SqlRunner报错:Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not
#contain value for com.baomidou.mybatisplus.core.mapper.SqlRunner.SelectList
enableSqlRunner: true
代码
一、查询
/**
* 查询用户信息(原生sql) 执行原生sql不会自动填充(SqlRunner)
* @param vo
* @return
*/
public JsonResult sqlRunnerFindUserByCondition(UserRequestVo vo){
List<UserRequestVo> list = new ArrayList<>();
List<Map<String, Object>> maps = SqlRunner.db().selectList("SELECT * FROM `user` WHERE 1 = 1 AND is_del = '0' ORDER BY create_time DESC");
if(ObjectUtils.isNotNull(maps) && !maps.isEmpty()){
UserRequestVo dataVo = null;
for (Map<String, Object> map : maps) {
dataVo = new UserRequestVo();
Object id = map.get("id");
dataVo.setId(ObjectUtils.isNull(id) ? null : Integer.parseInt(String.valueOf(id)));
Object userName = map.get("user_name");
dataVo.setUserName(ObjectUtils.isNull(userName) ? "" : String.valueOf(userName));
Object phoneNumber = map.get("phone_number");
dataVo.setPhoneNumber(ObjectUtils.isNull(phoneNumber) ? "" : String.valueOf(phoneNumber));
list.add(dataVo);
}
}
return JsonResult.ok(list);
}
二、新增
/**
* 新增用户 (原生sql) 执行原生sql不会自动填充(SqlRunner)
* @param vo
* @return
* INSERT INTO `user` (user_name,phone_number,gender,open_id) VALUES('哈哈' , '17890000000' , '1', '7f5b07c0a2914a7a94a23b84d468fe8f')
*/
@Override
public JsonResult sqlRunnerSaveUser(UserRequestVo vo) {
boolean insertFlag = SqlRunner.db().insert("INSERT INTO `user` (user_name,phone_number,gender,open_id) VALUES({0} , {1} , {2}, {3})",
vo.getUserName(),vo.getPhoneNumber(),vo.getGender(),UUID.randomUUID().toString().replace("-",""));
return insertFlag ? JsonResult.ok("新增成功") : JsonResult.fail("新增失败");
}
三、修改
/**
* 修改用户信息 (原生sql) 执行原生sql不会自动填充(SqlRunner)
* @param vo
* @return
* UPDATE `user` SET user_name = '奥巴驴',gender = '1' WHERE id = 3
*/
@Override
public JsonResult sqlRunnerUpdateUser(UserRequestVo vo) {
boolean updateFlag = SqlRunner.db().update("UPDATE `user` SET user_name = {0},gender = {1} WHERE id = {2}",
vo.getUserName(), vo.getGender(), vo.getId());
return updateFlag ? JsonResult.ok("修改成功") : JsonResult.fail("修改失败");
}
十、新增后返回id
一、keyProperty=“id” useGeneratedKeys=“true”
<insert id="insert" keyProperty="id" useGeneratedKeys="true">
insert into staff_order(tenant_id, institution_id, s_order_id, p_order_id, staff_id, staff_name,
staff_type, order_model,
staff_phone, provider_id,
order_status,
order_start_time, depart_start_time, service_start_time, service_stop_time, start_img,
start_lat,
start_longitude, start_address, stop_img, stop_lat, stop_longitude, stop_address,
description, status,order_no)
values (#{tenantId}, #{institutionId}, #{sOrderId}, #{pOrderId}, #{staffId}, #{staffName}, #{staffType},
#{orderModel}, #{staffPhone},
#{providerId}, #{orderStatus},
#{orderStartTime}, #{departStartTime}, #{serviceStartTime}, #{serviceStopTime}, #{startImg},
#{startLat},
#{startLongitude}, #{startAddress}, #{stopImg}, #{stopLat}, #{stopLongitude}, #{stopAddress},
#{description},
#{status},#{orderNo})
</insert>
二、selectKey 标签
<insert id="insertStaffOrder" parameterType="com.baidu.mine.common.entity.po.StaffOrder">
<selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER">
select last_insert_id();
</selectKey>
insert into staff_order(tenant_id, institution_id, s_order_id, p_order_id, staff_id, staff_name,
staff_type, order_model,
staff_phone, provider_id,
order_status,
order_start_time, depart_start_time, service_start_time, service_stop_time, start_img,
start_lat,
start_longitude, start_address, stop_img, stop_lat, stop_longitude, stop_address,
description, status)
values (#{tenantId}, #{institutionId}, #{sOrderId}, #{pOrderId}, #{staffId}, #{staffName}, #{staffType},
#{orderModel}, #{staffPhone},
#{providerId}, #{orderStatus},
#{orderStartTime}, #{departStartTime}, #{serviceStartTime}, #{serviceStopTime}, #{startImg},
#{startLat},
#{startLongitude}, #{startAddress}, #{stopImg}, #{stopLat}, #{stopLongitude}, #{stopAddress},
#{description},
#{status})
</insert>