自定义Mybatis自动填充插件----mybatis-plus 自动填充失效、分页插件不向下传递拦截链

自定义Mybatis自动填充插件----mybatis-plus 自动填充失效、分页插件不向下传递拦截链,第1张

实战业务优化方案总结—主目录https://blog.csdn.net/grd_java/article/details/124346685

问题如标题所示,mybatis自动填充不生效,使用分页插件发现这个插件没有向下传递拦截链。

解决方案

  1. 选择使用Mybatis-plus的分页插件,满足向下传递拦截链的要求。
  2. 仿造MyBatis-plus的自动填充使用,自定义自动填充插件。解决Mybatis-plus自动填充时而有效时而失效的情况。

自动填充效果展示



文章目录
  • 1. 分页插件
  • 2. 自定义mybatis
  • 3. 实现自动填充

1. 分页插件
1. 使用Mybatis-plus的分页插件,满足向下传递拦截链的要求
  1. 很简单,MyBatis配置文件中,配置一下
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
 * MyBatis相关配置
 */
@Configuration
@EnableTransactionManagement
public class MyBatisConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}
2. 自定义mybatis
2. 这里推荐,自己决定使用哪些插件,包括配置DataSource,事务管理器等等
  1. 很简单,创建一个配置类配置SqlSession就可以了(这个对象保存了MyBatis很多东西,其中拦截器就在这里面)
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages = "cn.xx.mapper",sqlSessionTemplateRef = "sqlOneSqlSessionTemplate")
public class DataSourceConfigOne {

    @Autowired
    private MyBatisConfig myBatisConfig;

    @Bean(name = "sqlOneDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.sqlone")
    @Primary
    public DataSource sqlOne1DataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "sqlOneSqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlOneSql1SessionFactory(@Qualifier("sqlOneDataSource") DataSource dataSource) throws Exception{
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/sqlone/*.xml"));
        bean.setPlugins(myBatisConfig.paginationInterceptor(),myBatisConfig.myPluginAutoFill());//将自定义自动填充拦截器添加到插件中
        return bean.getObject();
    }

    @Bean(name = "sqlOneTransactionManager")
    @Primary
    public DataSourceTransactionManager sqlOne1TransactionManager(@Qualifier("sqlOneDataSource") DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "sqlOneSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlOne1SqlSessionTemplate(@Qualifier("sqlOneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception{
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}
3. 实现自动填充
3. 自定义自动填充插件(仿照mybatis-plus的自动填充),需要对MyBatis源码有深入了解。
  1. 注解用来标识字段是否需要自动填充,Fill标识字段在哪些情况下被填充

import java.lang.annotation.*;

/**
 * 2022-04-14===>>>yinzhipeng
 * 添加此注解的字段,可以被自动填充拦截器扫描到。
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface MyTableField {
    //数据库字段值, 不需要配置该值的情况:
    String value() default "";
    //字段自动填充策略
    MyFieldFill fill() default MyFieldFill.DEFAULT;
}
/**
 * 2022-04-14===>>>yinzhipeng
 * 字段填充策略枚举类,需要配合MyPluginAutoFill类进行指定
 */
public enum MyFieldFill {
    /**
     * 默认不处理
     */
    DEFAULT,
    /**
     * 插入时填充字段
     */
    INSERT,
    /**
     * 更新时填充字段
     */
    UPDATE,
    /**
     * 插入和更新时填充字段
     */
    INSERT_UPDATE
}
  1. 在需要自动填充的字段上,添加注解
  2. 编写自定义插件(我们选择拦截ParameterHandler的setParameters方法),拦截后,会对INSERT和UPDATE *** 作的parameterObject 进行设置值,就达到自动填充的效果了。
import com.baomidou.mybatisplus.core.MybatisDefaultParameterHandler;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Objects;

/**
 * 2022-04-14===>>>yinzhipeng
 * 根据目前实验,MyBatis plus对于不继承BaseMapper的接口有可能不生效,无法保证百分百自动填充
 * 自定义MyBatis拦截器,实现自动填充,方便日后扩展功能
 */
//@Component //因为在DataSourceConfigOne,自定义了SqlSessionFactory,并指定拦截链,所以这里不需要用注解
@Intercepts({//可以定义多个@Singnature,对多个地方拦截,都用这个拦截器
        @Signature(type = ParameterHandler.class,//指定拦截ParameterHandler接口
                method = "setParameters",//指定要拦截这个接口(指定拦截ParameterHandler接口)的具体方法名,必须写对
                args={PreparedStatement.class}//拦截方法的参数列表,必须和你要拦截的方法一一对应
        )
})
//指定在某个类加载后再加载,防止其它的拦截器,比如分页拦截器不invocation.proceed()向下传递拦截链
//因为越往后的优先级越高,所以要在其它拦截器后面加载,这样它的优先级会更高
//@AutoConfigureAfter(PaginationInterceptor.class) //因为在DataSourceConfigOne,自定义了SqlSessionFactory,并指定拦截链,所以这里不需要
public class MyPluginAutoFill implements Interceptor {
    @Autowired
    private LoginUserUtil loginUserUtil;//用来获取当前登录的用户信息

    //insert *** 作时,需要填充的字段
    //只填充有@MyTableField(fill = MyFieldFill.INSERT),和@MyTableField(fill = MyFieldFill.INSERT_UPDATE)注解的字段
    //字段的自动填充,需要满足两个条件,在下面指定字段,并且在需要填充字段的地方,加上上面的注解,只满足其中一个条件,不会进行填充
    private void insertBefore(Object o) throws NoSuchFieldException, IllegalAccessException {
        // 属性名称,不是字段名称
        this.doBefore("createTime",MyFieldFill.INSERT,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
        this.doBefore("updateTime",MyFieldFill.INSERT,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
        this.doBefore("createUser",MyFieldFill.INSERT,loginUserUtil.getCurrentAdmin().getUsername(),o);
        this.doBefore("updateUser",MyFieldFill.INSERT,loginUserUtil.getCurrentAdmin().getUsername(),o);
    }
    //update *** 作时,需要填充的字段
    //只填充有@MyTableField(fill = MyFieldFill.UPDATE),和@MyTableField(fill = MyFieldFill.INSERT_UPDATE)注解的字段
    private void updateBefore(Object o) throws NoSuchFieldException, IllegalAccessException {
        this.doBefore("updateTime",MyFieldFill.UPDATE,new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()),o);
        this.doBefore("updateUser",MyFieldFill.UPDATE,loginUserUtil.getCurrentAdmin().getUsername(),o);
    }

    //每次执行 *** 作,都会进入此拦截
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //递归获取MybatisDefaultParameterHandler对象,因为可能是多层代理
        MybatisDefaultParameterHandler mybatisDefaultParameterHandler = realTarget(invocation.getTarget());
        //反射mappedStatement对象出来
        Field mappedStatementField = mybatisDefaultParameterHandler.getClass().getDeclaredField("mappedStatement");
        mappedStatementField.setAccessible(true);//此属性是private的,需要强制
        //获取MappedStatement
        MappedStatement mappedStatement =(MappedStatement)mappedStatementField.get(mybatisDefaultParameterHandler);
        // 如果不是insert或update,就不需要自动填充,直接传递拦截链
        if(mappedStatement.getSqlCommandType()!=SqlCommandType.INSERT && mappedStatement.getSqlCommandType()!=SqlCommandType.UPDATE)
            return invocation.proceed();//原方法执行,并返回

        // 获取parameterObject对象,用于设置参数
        Object parameterObject = realTarget(mybatisDefaultParameterHandler.getParameterObject());
        // 如果是insert或update,就执行对应的自动填充
        if(mappedStatement.getSqlCommandType()==SqlCommandType.INSERT){
            insertBefore(parameterObject);
        }else if (mappedStatement.getSqlCommandType()==SqlCommandType.UPDATE){
            updateBefore(parameterObject);
        }
        return invocation.proceed();//原方法执行,并返回,传递拦截链,不传递的话,后面的拦截器将无法获取
    }
    /**
     * 获得真正的处理对象,可能多层代理.
     */
    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
                MetaObject metaObject = SystemMetaObject.forObject(target);
                return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }

    /**
     * 真正做填充的方法。填充时,只填充有特定注解的字段
     * @param fieldName 要填充的字段名
     * @param operationType 需要的注解
     * @param fieldVal 要填充的值
     * @param metaObject 要填充的对象
     */
    private void doBefore(String fieldName,MyFieldFill operationType,Object fieldVal,Object metaObject) throws NoSuchFieldException, IllegalAccessException {
        //获取对象的指定Field属性
        Field declaredField = metaObject.getClass().getDeclaredField(fieldName);
        //如果填充的值存在,属性存在,并且有指定注解
        if (Objects.nonNull(fieldVal) && declaredField!=null && declaredField.isAnnotationPresent(MyTableField.class)) {
            //获取注解
            MyTableField annotation = declaredField.getAnnotation(MyTableField.class);
            //如果注解是规定的注解就进行填充
            if (annotation.fill() == MyFieldFill.INSERT_UPDATE||
                    annotation.fill()==operationType){
                declaredField.setAccessible(true);
                declaredField.set(metaObject,fieldVal);
            }
        }
    }

//        /**
//         * 将当前拦截器,生成代理对象放在拦截链中
//         * @param target 目标对象,被代理对象,被拦截对象
//         * @return 代理对象
//         */
//        @Override
//        public Object plugin(Object target) {
//                System.out.println("为"+target+"生成代理");
//                return Plugin.wrap(target,this);
//        }
//        //获取配置文件属性,xml中配置的属性,从Properties获取,插件初始化时调用。只调用一次,插件配置的属性从这里设置
//        @Override
//        public void setProperties(Properties properties) {
//                System.out.println("插件配置的初始化参数:"+properties);
//        }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/719800.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-25
下一篇 2022-04-25

发表评论

登录后才能评论

评论列表(0条)

保存