Mybatis自动拦截器

MyBatis 允许拦截的接口

        MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

Executor接口的部分方法,比如update,query,commit,rollback等方法,还有其他接口的一些方法等。

总体概括为:

  1. 拦截执行器的方法
  2. 拦截参数的处理
  3. 拦截结果集的处理,为sql执行之后的结果拦截过滤
  4. 拦截Sql语法构建的处理,为sql执行之前的拦截进行sql封装

MyBatis拦截器的接口定义

一共有三个方法interceptpluginsetProperties

setProperties()

方法主要是用来从配置中获取属性。

如果是使用xml式配置拦截器,可在Mybatis配置文件中添加如下节点,属性可以以如下方式传递

<plugins>
	<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
		<property name="propl" value="valuel" />
		<property name="prop2" value="value2" />
	</plugin>
</plugins>

如果在Spring Boot 中使用配置类,则需要单独写一个配置类 ,如下:

@Configuration
public class MybatisInterceptorConfig{
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory){
        ExecutorInterceptor executorInterceptor = new ExecutorInterceptor();
        Properties properties = new Properties();
        properties.setProperty("prop1","value1");
        executorInterpecetor.setProperties(properties);
        return "interceptor";
    }
}

如果说不需要配置属性,则在spring boot中,不需要去编写配置类,只需要像我一样在拦截器上加个@Component即可。

plugin() 方法用于指定哪些方法可以被此拦截器拦截。
intercept() 方法是用来对拦截的sql进行具体的操作。

注解实现

MyBatis拦截器用到了两个注解:@Intercepts@Signature

@Intercepts(
        { 
   
                @Signature(type = Executor.class, method = "query",
                        args = { 
   MappedStatement.class, Object.class,
                                RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query",
                        args = { 
   MappedStatement.class, Object.class, RowBounds.class,
                                ResultHandler.class, CacheKey.class, BoundSql.class}),
        }
)

type的值与类名相同,method方法名相同,为了避免方法重载,args中指定了各个参数的类型和个数,可通过invocation.getArgs()获取参数数组。

Executor 拦截器实现
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class AutoFillInterceptor implements Interceptor {

    private static final String CREATE_BY = "createBy";
    private static final String UPDATE_BY = "updateBy";

    private static final String CREATE_TIME = "createTime";
    private static final String UPDATE_TIME = "updateTime";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        // 获取用于描述SQL语句的映射信息
        MappedStatement ms = (MappedStatement) args[0];
        SqlCommandType sqlCommandType = ms.getSqlCommandType();

        // 获取sql参数实体 ParamMap
        Object parameter = args[1];

        if (parameter != null && sqlCommandType != null) {
            // 获取用户ID
            Long userId = loadUserId();

            if (SqlCommandType.INSERT.equals(sqlCommandType)) {
                // 插入操作
                if (parameter instanceof MapperMethod.ParamMap) {
                    // 批量插入的情况
                    MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameter;
                    ArrayList list = (ArrayList) paramMap.get("list");
                    list.forEach(v -> {
                        // 设置创建人和创建时间字段值
                        setFieldValByName(CREATE_BY, userId, v);
                        setFieldValByName(CREATE_TIME, LocalDateTime.now(), v);
                        setFieldValByName(UPDATE_TIME, LocalDateTime.now(), v);
                    });
                    paramMap.put("list", list);
                } else {
                    // 单条插入的情况
                    // 设置创建人和创建时间字段值
                    setFieldValByName(CREATE_BY, userId, parameter);
                    setFieldValByName(CREATE_TIME, LocalDateTime.now(), parameter);
                    setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
                }
            } else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
                // 更新操作
                // 设置更新人和更新时间字段值
                setFieldValByName(UPDATE_BY, userId, parameter);
                setFieldValByName(UPDATE_TIME, LocalDateTime.now(), parameter);
            }
        }

        // 继续执行原始方法
        return invocation.proceed();
    }

    /**
     * 通过反射设置实体的字段值
     * @param fieldName 字段名
     * @param fieldVal  字段值
     * @param parameter 实体对象
     */
    private void setFieldValByName(String fieldName, Object fieldVal, Object parameter) {
        MetaObject metaObject = SystemMetaObject.forObject(parameter);
        if (fieldName.equals(CREATE_BY)) {
            Object value = metaObject.getValue(fieldName);
            if (ObjectUtil.isNotEmpty(value)) {
                return;
            }
        }

        if (metaObject.hasSetter(fieldName)) {
            metaObject.setValue(fieldName, fieldVal);
        }
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            // 对目标对象进行包装,返回代理对象
            return Plugin.wrap(target, this);
        }
        // 非 Executor 类型的对象,直接返回原始对象
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
        // 读取配置文件中的属性,此处没有使用
    }

    /**
     * 获取当前用户的ID,用于填充创建人和更新人字段的值
     *
     * @return 当前用户ID
     */
    public static Long loadUserId() {
        // 从 ThreadLocal 中获取用户ID
        Long userId = UserThreadLocal.getUserId();
        // 如果 ThreadLocal 中不存在用户ID,则从管理用户ID中获取
        if (ObjectUtil.isNotEmpty(userId)) {
            return userId;
        }
        userId = UserThreadLocal.getMgtUserId();
        // 如果管理用户ID也不存在,则默认返回ID为1的用户
        if (!EmptyUtil.isNullOrEmpty(userId)) {
            return userId;
        }
        return 1L;
    }
}

这段代码是一个 MyBatis 的拦截器(Interceptor),用于实现自动填充实体对象中的创建人、更新人、创建时间和更新时间字段的值。下面是代码的主要逻辑解释:

  1. 在 AutoFillInterceptor 类中实现了 Interceptor 接口,并在 @Intercepts 注解中指定了拦截 Executor 类中的 update 方法,参数为 MappedStatement.class 和 Object.class

  2. 在 intercept 方法中,通过拦截器的 Invocation 对象获取方法参数,并判断 SQL 命令类型(插入或更新)。

  3. 根据 SQL 命令类型,分别对参数进行处理:

    • 对于插入操作,根据参数类型(单条插入或批量插入),设置创建人、创建时间和更新时间字段的值。
    • 对于更新操作,设置更新人和更新时间字段的值。
  4. 使用反射方式设置实体对象的字段值,通过 MetaObject 和 SystemMetaObject 类实现字段值的设置。

  5. plugin 方法用于包装目标对象,这里对 Executor 对象进行包装并返回代理对象。

  6. setProperties 方法用于设置配置属性,这里没有使用。

  7. loadUserId 方法用于获取当前用户的ID,从 UserThreadLocal 中获取用户ID,如果不存在则返回默认值 1。

这段代码主要实现了在数据库操作时自动填充实体对象的特定字段,提高了开发效率和代码复用性。通过拦截器的方式,实现了对数据库操作的统一处理,避免了重复的代码编写。

  • 11
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值