整合mybatis-plus
基本使用
导入依赖
<!--mybatisPlus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
配置
mybatis-plus:
configuration:
#日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启驼峰命名
map-underscore-to-camel-case: true
global-config:
db-config:
#逻辑删除字段
logic-delete-field: delFlag
#代表已经删除得值
logic-delete-value: 1
#代表未删除得值
logic-not-delete-value: 0
#主键自增
id-type: auto
配置类
package com.jjking.handler.mybatisplus;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.jjking.utils.SecurityUtils;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @author 35238
* @date 2023/7/26 0026 20:52
*/
@Component
//这个类是用来配置mybatis的字段自动填充。用于'发送评论'功能,由于我们在评论表无法对下面这四个字段进行插入数据(原因是前端在发送评论时,没有在
//请求体提供下面四个参数,所以后端在往数据库插入数据时,下面四个字段是空值),所有就需要这个类来帮助我们往下面这四个字段自动的插入值,
//只要我们更新了评论表的字段,那么无法插入值的字段就自动有值了
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
//只要对数据库执行了插入语句,那么就会执行到这个方法
public void insertFill(MetaObject metaObject) {
Long userId = null;
try {
//获取用户id
userId = SecurityUtils.getUserId();
} catch (Exception e) {
e.printStackTrace();
userId = -1L;//如果异常了,就说明该用户还没注册,我们就把该用户的userid字段赋值d为-1
}
//自动把下面四个字段新增了值。
this.setFieldValByName("createTime", new Date(), metaObject);
this.setFieldValByName("createBy",userId , metaObject);
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName("updateBy", userId, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("updateTime", new Date(), metaObject);
this.setFieldValByName(" ", SecurityUtils.getUserId(), metaObject);
}
}
主启动类要加上mapperscan
/**
* 前台启动类
* @author jjking
* @date 2023-12-01 22:41
*/
@SpringBootApplication
@MapperScan(basePackages = "com.sky.mapper")
@EnableScheduling
@EnableSwagger2
public class BlogApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApplication.class,args);
}
}
这里的mapperscan我换成
**@MapperScan(basePackages = “com.sky.mapper”)**才能起作用,不然还是没作用
各类上的注解
mapper
/**
* @author jjking
* @date 2023-12-02 12:01
*/
@Mapper
public interface ArticleMapper extends BaseMapper<Article> {
}
service
/**
* 文章表(Article)表服务接口
*
* @author makejava
* @since 2023-12-02 11:59:42
*/
public interface ArticleService extends IService<Article> {}
service实现类
@Service("articleService")
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article> implements ArticleService {
}
自动填充功能设置
一共两步
填写注解
public class User {
// 注意!这里需要标记为填充字段
@TableField(fill = FieldFill.INSERT)
private String fillField;
....
}
这里的fill有四个注解,看字面意思也很好理解
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
最后写一个这样的配置类就行,一个是写插入的逻辑,一个是写新增的逻辑
改进自动填充功能
每次我们都要在字段上写注解,这是一件很费时间的事,我在
https://blog.csdn.net/weixin_44684272/article/details/126067864
他写的全局设置注解,中看到的操作,这就是我想要的操作
特此记录一下
写配置类
先在mybaitisconfig里边写一个配置
@Configuration
public class MybatisPlusConfig {
/**
* 添加更新拦截器,加入全局插入字段
* @return
*/
@Bean
public UpdateInterceptor updateInterceptor() {
return new UpdateInterceptor();
}
}
然后创建一个拦截器,似乎是mybatisplus的sql注入器,什么的,我的理解像是拦截器,在我们写sql的时候,他拦截一下,我门就能在要执行sql的时候,写入我们自己的逻辑
package com.sky.handler.mybatis;
import com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler;
import com.sky.context.BaseContext;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import java.lang.reflect.Field;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
/**
* @author : [LiuYanQiang]
* @version : [v1.0]
* @className : UpdateInterceptor
* @description : [自动给创建时间个更新时间加值]
* @createTime : [2022/1/12 9:09]
* @updateUser : [LiuYanQiang]
* @updateTime : [2022/1/12 9:09]
* @updateRemark : [描述说明本次修改内容]
*/
@Intercepts(value = {@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class UpdateInterceptor extends AbstractSqlParserHandler implements Interceptor {
/**
* 创建时间
*/
private static final String CREATE_TIME = "createTime";
/**
* 更新时间
*/
private static final String UPDATE_TIME = "updateTime";
/**
* 创建时间
*/
private static final String CREATE_USER = "createUser";
/**
* 更新user
*/
private static final String UPDATE_USER = "updateUser";
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
// SQL操作命令,是什么操作,update或者insert
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
// 获取新增或修改的对象参数
Object parameter = invocation.getArgs()[1];
// 获取对象中所有的私有成员变量(对应表字段)
Field[] declaredFields = parameter.getClass().getDeclaredFields();
if (parameter.getClass().getSuperclass() != null) {
Field[] superField = parameter.getClass().getSuperclass().getDeclaredFields();
declaredFields = ArrayUtils.addAll(declaredFields, superField);
}
// mybatis plus判断
boolean plus= parameter.getClass().getDeclaredFields().length == 1 && parameter.getClass().getDeclaredFields()[0].getName().equals("serialVersionUID");
//兼容mybatis plus
if (plus) {
Map<String, Object> updateParam = (Map<String, Object>) parameter;
Class<?> updateParamType = updateParam.get(updateParam.keySet().iterator().next()).getClass();
declaredFields = updateParamType.getDeclaredFields();
if (updateParamType.getSuperclass() != null) {
Field[] superField = updateParamType.getSuperclass().getDeclaredFields();
declaredFields = ArrayUtils.addAll(declaredFields, superField);
}
}
//获得所有列
String fieldName = null;
for (Field field : declaredFields) {
//获得字段名
fieldName = field.getName();
//设置创建时间字段
if (Objects.equals(CREATE_TIME, fieldName)) {
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
field.setAccessible(true);
field.set(parameter, LocalDateTime.now());
}
}
//设置创建user
if (Objects.equals(CREATE_USER, fieldName)) {
if (SqlCommandType.INSERT.equals(sqlCommandType)) {
field.setAccessible(true);
field.set(parameter, BaseContext.getCurrentId());
}
}
//设置更新的时间
if (Objects.equals(UPDATE_TIME, fieldName)) {
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
field.setAccessible(true);
//兼容mybatis plus的update
if (plus) {
Map<String, Object> updateParam = (Map<String, Object>) parameter;
field.set(updateParam.get(updateParam.keySet().iterator().next()), LocalDateTime.now());
} else {
field.set(parameter, LocalDateTime.now());
}
}
}
//设置更新人
if (Objects.equals(UPDATE_USER, fieldName)) {
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
field.setAccessible(true);
//兼容mybatis plus的update
if (plus) {
Map<String, Object> updateParam = (Map<String, Object>) parameter;
field.set(updateParam.get(updateParam.keySet().iterator().next()), BaseContext.getCurrentId());
} else {
field.set(parameter, BaseContext.getCurrentId());
}
}
}
}
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
if (target instanceof Executor) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
我在他的基础之上,再加上了updateUser 和 createUser,其实这个代码看起来很难,但其实不是特别难理解,虽然我也不是很懂他这些事从哪里知道的,我估计要去深入mybatis-plus的源码才能搞懂,我这里特别记录一下我自己的理解
首先我们的目标有两个,一个是我们要知道,此时的sql是insert还是update,还是其他的什么的,也就是我们要知道此时是增删改查的哪一种,也就是我们这里的SqlCommandType
第二个,就是我们要得到操作的到底是哪些字段,得到字段值,我们才可以去匹配是不是我们要写公共字段,也就是这里的Field[] declaredFields
然后得到这两样之后,就先遍历表的字段,然后判断此时是什么操作,如果是update或者insert,那么就要判断是不是我们要的字段
这里的逻辑还是很简单的,但是其中用的类,得等我有时间去挖掘mybatis,我再来这里更新一下
分页功能设置
写配置
/**
* @author jjking
* @date 2024-01-22 22:39
*/
@Configuration
public class MybatisPlusConfig {
/**
* 添加分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
return interceptor;
}
}
使用示例
@Override
public PageResult pageQuery(EmployeePageQueryDTO employeePageQueryDTO) {
LambdaQueryWrapper<Employee> employeeWrapper = new LambdaQueryWrapper<>();
employeeWrapper.like(Objects.nonNull(employeePageQueryDTO.getName()), Employee::getName, employeePageQueryDTO.getName());
//分页
Page<Employee> page = new Page<>(employeePageQueryDTO.getPage(), employeePageQueryDTO.getPageSize());
page(page, employeeWrapper);
PageResult pageResult = new PageResult(page.getTotal(), page.getRecords());
return pageResult;
}
这里使用LambdaQueryWrapper,可以简写一些代码
这里的pageResult可以自定义,一个自定义类,因为前端页面需要count,也就是总共的记录数
还有就是records就是实际的数据
/**
* 封装分页查询结果
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult implements Serializable {
private long total; //总记录数
private List records; //当前页数据集合
}
逻辑删除设置
如下实现逻辑删除
首先我们得再数据库中的表加上逻辑删除字段
特别要注意这里的问题,如果我们在配置中写了驼峰命名的规则的化,那么我们在前端的字段应该是delFlag
在实体类Category中
//删除标志(0代表未删除,1代表已删除)
private Integer delFlag;
然后我们在业务中进行删除
我测试过了,无论是Service的removeByid,还是mapper的deleteByid都能触发逻辑删除字段
@ApiOperation("删除分类")
@DeleteMapping
public Result delete(Long id) {
log.info("删除分类 参数为{}",id);
categoryService.removeById(id);
return Result.success();
}
我们实现了逻辑删除的功能,我们就得想.那要如何实现真正的删除呢,我们总要有这个功能把,不能真的不给删除吧,所以,再次记录真正删除的方法
这里mybtisplus并没有实现这个功能,我看了很久,没有实现的,那么就只能物理删除,自己写mapper,写一个sql,虽然这也不难,但是也蛮麻烦的,还是希望官方能实现这个功能,懒得写sql
我们得现在mapper中写一个方法
@Mapper
public interface CategoryMapper extends BaseMapper<Category> {
/**
* 实现物理删除
* @param id
* @return
*/
int delete(@Param("id") Long id);
}
在生成的xml中
<?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.sky.mapper.CategoryMapper">
<delete id="delete">
delete from category where id = #{id}
</delete>
</mapper>
直接删除
这样就能实现真正的物理删除了
小问题
插入一条数据后,需要得到id
有的时候,我们有这样的需求,我门调用了save之后,需要得到save之后的id,我就去找怎么得到这个id,后来发现,mybatis-plus 已经设置好了,就在原来的实体类上,已经设置好了
也就是save(实体类),会在这个实体类上如果有id的化,会设置好的
我估计底层是设置主键的值,应该不是按照名字来回显这个值的,这样才合理一点