学会自己编写Mybatis插件(拦截器)实现自定义需求1

简介: 学会自己编写Mybatis插件(拦截器)实现自定义需求

前言

最近在参加金石计划,在考虑写什么的时,想到自己在项目中使用过的mybatis的插件,就想趁这个机会聊一聊我们接触频繁的Mybatis.

如果是使用过Mybatis的小伙伴,那么我们接触过的第一个Mybatis的插件自然就是分页插件(Mybatis-PageHelper)啦。

你有了解过它是如何实现的吗?你有没有自己编写 Mybatis 插件去实现一些自定义需求呢?

插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或改变框架原有的功能。

Mybatis 中也提供了插件的功能,虽然叫插件,但是实际上是通过拦截器( Interceptor )实现的,通过拦截某些方法的调用,在执行目标逻辑之前插入我们自己的逻辑实现。另外在 MyBatis 的插件模块中还涉及责任链模式和 JDK 动态代理~

文章大纲:

image.png

一、应用场景

  1. 一些字段的自动填充
  2. SQL语句监控、打印、数据权限等
  3. 数据加解密操作、数据脱敏操作
  4. 分页插件
  5. 参数、结果集的类型转换

这些都是一些可以使用Mybatis插件实现的场景,当然也可以使用其他的方式来实现,只不过拦截的地方不一样罢了,有早有晚。

二、Mybatis实现自定义拦截器

我们用自定义拦截器实现一个相对简单的需求,在大多数表设计中,都会有create_time和update_time等字段,在创建或更新时需要更新相关字段。

如果是使用过MybatisPlus的小伙伴,可能知道在MybatisPlus中有一个自动填充功能,通过实现MetaObjectHandler接口中的方法来进行实现(主要的实现代码在com.baomidou.mybatisplus.core.MybatisParameterHandler中).

但使用Mybatis,并没有相关的方法或 API 可以直接来实现。所以我们这次就用以此处作为切入点,自定义拦截器来实现类似的自动填充功能。

编写步骤

  1. 编写一个拦截器类实现 Interceptor 接口
  2. 添加拦截注解 @Intercepts
  3. 在xml文件中配置拦截器或者添加到Configuration中

基础的环境我就不再贴出来啦哈,直接上三个步骤的代码

2.1、编写拦截器

package com.nzc.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.beans.factory.annotation.Value;
import java.lang.reflect.Field;
import java.util.*;
/**
 * @author 宁在春
 * @version 1.0
 * @description: 通过实现拦截器来实现部分字段的自动填充功能
 * @date 2023/4/6 21:49
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
@Slf4j
public class MybatisMetaInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        log.info("------sqlId------" + sqlId);
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        Object parameter = invocation.getArgs()[1];
        log.info("------sqlCommandType------" + sqlCommandType);
        log.info("拦截查询请求 Executor#update 方法" + invocation.getMethod());
        if (parameter == null) {
            return invocation.proceed();
        }
        if (SqlCommandType.INSERT == sqlCommandType) {
            Field[] fields = getAllFields(parameter);
            for (Field field : fields) {
                log.info("------field.name------" + field.getName());
                try {
                    // 注入创建时间
                    if ("createTime".equals(field.getName())) {
                        field.setAccessible(true);
                        Object local_createDate = field.get(parameter);
                        field.setAccessible(false);
                        if (local_createDate == null || local_createDate.equals("")) {
                            field.setAccessible(true);
                            field.set(parameter, new Date());
                            field.setAccessible(false);
                        }
                    }
                } catch (Exception e) {
                }
            }
        }
        if (SqlCommandType.UPDATE == sqlCommandType) {
            Field[] fields = getAllFields(parameter);
            for (Field field : fields) {
                log.info("------field.name------" + field.getName());
                try {
                    if ("updateTime".equals(field.getName())) {
                        field.setAccessible(true);
                        field.set(parameter, new Date());
                        field.setAccessible(false);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }
     
    // 稍后会展开说的
    @Override
    public void setProperties(Properties properties) {
        System.out.println("=======begin");
        System.out.println(properties.getProperty("param1"));
        System.out.println(properties.getProperty("param2"));
        Interceptor.super.setProperties(properties);
        System.out.println("=======end");
    }
    /**
     * 获取类的所有属性,包括父类
     *
     * @param object
     * @return
     */
    public static Field[] getAllFields(Object object) {
        Class<?> clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }
}

2.2、添加到Mybatis配置

我这里使用的JavaConfig的方式

package com.nzc.config;
import com.nzc.interceptor.*;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisConfig {
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return new ConfigurationCustomizer() {
            @Override
            public void customize(org.apache.ibatis.session.Configuration configuration) {
                // 开启驼峰命名映射
                configuration.setMapUnderscoreToCamelCase(true);
                MybatisMetaInterceptor mybatisMetaInterceptor = new MybatisMetaInterceptor();
                Properties properties = new Properties();
                properties.setProperty("param1","javaconfig-value1");
                properties.setProperty("param2","javaconfig-value2");
                mybatisMetaInterceptor.setProperties(properties);
                configuration.addInterceptor(mybatisMetaInterceptor);
            }
        };
    }
}

如果是xml配置的话,则是如下: property 是设置 拦截器中需要用到的参数

<configuration>
    <plugins>
        <plugin interceptor="com.nzc.interceptor.MybatisMetaInterceptor"> 
            <property name="param1" value="value1"/>
            <property name="param2" value="value2"/>
        </plugin>
    </plugins>
</configuration>    

2.3、测试

测试代码:实现了一个SysMapper的增删改查

package com.nzc.mapper;
import com.nzc.entity.SysUser;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
 * @author 宁在春
 * @description 针对表【sys_user】的数据库操作Mapper
 */
@Mapper
public interface SysUserMapper {
    @Select("SELECT * FROM tb_sys_user")
    List<SysUser> list();
    @Insert("insert into tb_sys_user(id,username,realname,create_time,update_time) values (#{id}, #{username}, #{realname}, #{createTime}, #{updateTime})")
    Boolean insert(SysUser sysUser);
    @Update("update tb_sys_user set  username=#{username} , realname=#{realname},update_time=#{updateTime}  where id=#{id}")
    boolean update(SysUser sysUser);
}
/**
 * @author 宁在春
 * @version 1.0
 * @description: TODO
 * @date 2023/4/6 21:38
 */
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class SysUserMapperTest {
    @Autowired
    private SysUserMapper sysUserMapper;
    @Test
    public void test1(){
        System.out.println(sysUserMapper.list());
    }
    @Test
    public void test2(){
        SysUser sysUser = new SysUser();
        sysUser.setId("1235");
        sysUser.setUsername("nzc5");
        sysUser.setRealname("nzc5");
        System.out.println(sysUserMapper.insert(sysUser));
    }
    @Test
    public void test3(){
        SysUser sysUser = new SysUser();
        sysUser.setId("1235");
        sysUser.setUsername("nzc7");
        sysUser.setRealname("nzc5");
        System.out.println(sysUserMapper.update(sysUser));
    }
}

当然重点不在这里,而是在我们打印的日志上,一起来看看效果吧

image.png

此处相关日志对应Interceptor中的日志打印,想要了解的更为详细的可以debug查看一番。


2.4、小结

通过这个小小的案例,我想大伙对于Mybatis中的拦截器应当是没有那般陌生了吧,接下来再来仔细聊聊吧

如果你使用过MybatisPlus的话,在读完这篇博文后,可以思考思考下面这个问题,或去看一看源码,将知识串联起来,如果可以的话,记得把答案贴到评论区啦~~~

思考:还记得这一小节开始我们聊到的MybatisPlus实现的自动填充功能吗?它是怎么实现的呢?

学会自己编写Mybatis插件(拦截器)实现自定义需求2:https://developer.aliyun.com/article/1394846

目录
相关文章
|
4月前
|
SQL XML Java
8、Mybatis-Plus 分页插件、自定义分页
这篇文章介绍了Mybatis-Plus的分页功能,包括如何配置分页插件、使用Mybatis-Plus提供的Page对象进行分页查询,以及如何在XML中自定义分页SQL。文章通过具体的代码示例和测试结果,展示了分页插件的使用和自定义分页的方法。
8、Mybatis-Plus 分页插件、自定义分页
|
4月前
|
SQL Java 测试技术
3、Mybatis-Plus 自定义sql语句
这篇文章介绍了如何在Mybatis-Plus框架中使用自定义SQL语句进行数据库操作。内容包括文档结构、编写mapper文件、mapper.xml文件的解释说明、在mapper接口中定义方法、在mapper.xml文件中实现接口方法的SQL语句,以及如何在单元测试中测试自定义的SQL语句,并展示了测试结果。
3、Mybatis-Plus 自定义sql语句
|
1月前
|
SQL Java 数据库连接
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
224 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
|
4月前
|
SQL Java Kotlin
MybatisPlus怎么拓展自定义BaseMapper
通过扩展Mybatis-Plus的`BaseMapper`,可以自定义SQL模板以满足特定业务需求。例如,当遇到唯一键冲突而不希望抛出异常时,可使用`INSERT IGNORE`语法。首先,创建`InsertIgnore`类继承`AbstractMethod`并定义`insertIgnore`方法及其SQL模板。接着,在自定义的`UltraBaseMapper`接口中声明`insertIgnore`方法,并让业务Mapper继承此接口。最后,通过`UltraSqlInjector`类将`InsertIgnore`方法注册到Mybatis-Plus插件中。
193 1
|
1月前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
1月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
3月前
|
SQL Java 数据库连接
解决mybatis-plus 拦截器不生效--分页插件不生效
本文介绍了在使用 Mybatis-Plus 进行分页查询时遇到的问题及解决方法。依赖包包括 `mybatis-plus-boot-starter`、`mybatis-plus-extension` 等,并给出了正确的分页配置和代码示例。当分页功能失效时,需将 Mybatis-Plus 版本改为 3.5.5 并正确配置拦截器。
1010 6
解决mybatis-plus 拦截器不生效--分页插件不生效
|
3月前
|
SQL XML Java
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用
|
4月前
|
Java 数据库 Spring
MyBatisPlus分页插件在SpringBoot中的使用
这篇文章介绍了如何在Spring Boot项目中配置和使用MyBatis-Plus的分页插件,包括创建配置类以注册分页拦截器,编写测试类来演示如何进行分页查询,并展示了测试结果和数据库表结构。
MyBatisPlus分页插件在SpringBoot中的使用
|
5月前
|
SQL Java 数据库连接
idea中配置mybatis 映射文件模版及 mybatis plus 自定义sql
idea中配置mybatis 映射文件模版及 mybatis plus 自定义sql
110 3