mybaits拦截器+自定义注解

实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于mybaits语句查询得出结果集后)

项目环境 :springboot+mybaits

实现步骤:自定义注解——自定义实现mybaits拦截器——注册mybaits拦截器

一、自定义注解

1.1  代码示例

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD})//
@Retention(RetentionPolicy.RUNTIME)//该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Inherited//允许子类继承父类的注解。 (子类中可以获取并使用父类注解)
@Documented//指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值。
/**
* 该自定义注解用于所查询语句中字段包含字典表主键 并需要将主键同时对照成字典表对应的名称
* 将该注解放置在名称列,参数为字典表主键存储列的名字
* @ClassName: DictReplace 
* 描述: TODO  用于字典名称字段默认为空,则空则认为字典id字段名为 字典名称字典.substring(0,length()-4) 若不为空则认定字典id字段名称为参数值
* 作者cy
* 时间 2019年3月26日 上午9:02:47 
*
*/
public @interface DictReplace {

String dictIdFieldName() default "";    

}

@Target 注解

功能:指明了修饰的这个注解的使用范围,即被描述的注解可以用在哪里。

ElementType的取值包含以下几种: 

  • TYPE:类,接口或者枚举
  • FIELD:域,包含枚举常量
  • METHOD:方法
  • PARAMETER:参数
  • CONSTRUCTOR:构造方法
  • LOCAL_VARIABLE:局部变量
  • ANNOTATION_TYPE:注解类型
  • PACKAGE:包

@Retention 注解

功能:指明修饰的注解的生存周期,即会保留到哪个阶段。

RetentionPolicy的取值包含以下三种:

  • SOURCE:源码级别保留,编译后即丢弃。
  • CLASS:编译级别保留,编译后的class文件中存在,在jvm运行时丢弃,这是默认值。
  • RUNTIME: 运行级别保留,编译后的class文件中存在,在jvm运行时保留,可以被反射调用。

@Documented 注解

功能:指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值。

@Inherited注解

功能:允许子类继承父类中的注解。

 

1.2  使用场景

        @TableField("runtime_platform")
        private Integer runtimePlatform;
        
        @DictReplace//字典替换注解
        @TableField(exist = false)        
        private String runtimePlatformName;        

 

 

二、自定义mybaits拦截器并注册

  2.1  代码示例

 

import java.util.List;
import java.util.Properties;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import com.msunsoft.base.common.factory.ConstantFactory;
import com.msunsoft.base.common.interceptor.annotation.DictReplace;
import com.msunsoft.base.spring.SpringContextHolder;
import com.msunsoft.base.util.ToolUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import java.lang.reflect.Field;
import java.sql.Statement;
/**
 * 字典替换拦截器,当注解方法被执行后拦截并修改查询后的结果
* @ClassName: DictReplaceInteceptor 
* 描述: TODO
* 作者
* 时间 2019年3月25日 下午7:23:41 
*
 */
@Intercepts({    
    @Signature(type = ResultSetHandler.class,method = "handleResultSets", args = { Statement.class }) 
    })  
public class DictReplaceInteceptor implements Interceptor{
    private Properties properties; 
    private SpringContextHolder spring;//实现  ApplicationContextAware 接口的类包含获取spring容器中的bean的静态方法    
    
    @Override
    @SuppressWarnings(value = {"all"})
    public Object intercept(Invocation invocation) throws Throwable {
            //因为 handleResultSets  方法执行结束后可以收到一个list类型的数据结果集,所以虽然该方法的目的是用于结束本次拦截,执行预定方法(handleResultSets)方便下次拦截
            List<Object> results = (List<Object>)invocation.proceed();
            try{
          //自定义方法用于判断对象是否为空
if(ToolUtil.isNotEmpty(results)){
            //ConstantFactory 是自定义的包含常用方法的一个类,现在用到的是它包含在其中的通过字典主键获取字典名称的方法 ConstantFactory constantFactory
= spring.getBean(ConstantFactory.class); Class<?> cls = results.get(0).getClass(); Field[] fields = cls.getDeclaredFields();// 获取private修饰的成员变量 获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。 for(Object result:results){ for (Field field : fields) { //获取我们自定义的注解 DictReplace dictReplace = field.getAnnotation(DictReplace.class); if(dictReplace!=null){//如果存在这个注解 我们在执行后续方法 String dictIdFieldName = dictReplace.dictIdFieldName();//获取注解属性值 Field idField = null; if(ToolUtil.isNotEmpty(dictIdFieldName)){ idField = cls.getDeclaredField(dictIdFieldName);//获取实体类对应字段 }else{ String fieldName = field.getName();//获取实体类字段名称 String idFieldName = fieldName.substring(0,fieldName.length()-4); idField = cls.getDeclaredField(idFieldName); } idField.setAccessible(true);//允许我们在用反射时访问私有变量 Object dictId = idField.get(result);//从返回值中获得字段对应的 值 field.setAccessible(true); if(ToolUtil.isNotEmpty(dictId)){ field.set(result, constantFactory.getDictName( Long.valueOf(new String(dictId.toString())) ) ); //用字典id查询出字典名称 并替换结果集中的值 } } } } } }catch (Exception e) { e.printStackTrace(); }finally{ return results; } } @Override public Object plugin(Object target) { // 读取@Signature中的配置,判断是否需要生成代理类 if (target instanceof ResultSetHandler) { return Plugin.wrap(target, this);//返回代理 } else { return target; } } @Override public void setProperties(Properties properties) { this.properties = properties; } }

2019年4月16日更新,为了使用mybaits缓存机制减少数据库负担,将部分代码改写

@Intercepts({    
    @Signature(type = ResultSetHandler.class,method = "handleResultSets", args = { Statement.class }) 
    })  
public class DictReplaceInteceptor implements Interceptor{
	private Properties properties; 
	private SpringContextHolder spring;
	 
	
	@Override
	@SuppressWarnings(value = {"all"})
	public Object intercept(Invocation invocation) throws Throwable {
			//
			List<Object> results = (List<Object>)invocation.proceed();
			SqlSessionFactory sqlSessionFactory = spring.getBean(SqlSessionFactory.class);
			SqlSession sqlSession = sqlSessionFactory.openSession();
			try{
				if(ToolUtil.isNotEmpty(results)){
					ConstantFactory constantFactory = spring.getBean(ConstantFactory.class);
					Class<?> cls = results.get(0).getClass();

					Field[] fields = cls.getDeclaredFields();// 暴力获取private修饰的成员变量  获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
	                for(Object result:results){     	 
	                	
	                	 for (Field field : fields) {
	                		
	                    	 DictReplace dictReplace = field.getAnnotation(DictReplace.class);
	                    	 if(dictReplace!=null){
	                    		 String dictIdFieldName = dictReplace.dictIdFieldName();
	                    		 Field idField = null;
	                    		 if(ToolUtil.isNotEmpty(dictIdFieldName)){
	                    			 idField = cls.getDeclaredField(dictIdFieldName);
	                    		 }else{
	                    			 String fieldName = field.getName();
	                            	 String idFieldName = fieldName.substring(0,fieldName.length()-4);
	                            	 idField = cls.getDeclaredField(idFieldName);
	                    		 }
	                    		 idField.setAccessible(true);
	                    		 Object  dictId =  idField.get(result);
	                    		 field.setAccessible(true);
	                    		 if(ToolUtil.isNotEmpty(dictId)){

						if (ToolUtil.isEmpty(dictId)) {
							return "";
						} else {
                                 //以前是直接调用方法,每次调用调用都会创建,现在通过sqlSession获取对应的mapper 避免每次都创建 DictMapper dictMapper = sqlSession.getMapper(DictMapper.class); Dict dict = dictMapper.selectById(new String(dictId.toString())); if (dict == null) { } else { field.set(result, dict.getName()); } } } } } } } }catch (Exception e) { e.printStackTrace(); }finally{ sqlSession.close(); return results; } } @Override public Object plugin(Object target) { // 读取@Signature中的配置,判断是否需要生成代理类 if (target instanceof ResultSetHandler) { return Plugin.wrap(target, this); } else { return target; } } @Override public void setProperties(Properties properties) { this.properties = properties; } }

 再举一个例子

  注解对象

@Target({ElementType.FIELD})//
@Retention(RetentionPolicy.RUNTIME)//该注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Inherited//允许子类继承父类的注解。 (子类中可以获取并使用父类注解)
@Documented//指明修饰的注解,可以被例如javadoc此类的工具文档化,只负责标记,没有成员取值。
public @interface One2One {
    String byField();
    Class resultType();
    Class mapper();
    String methodName();
}

  拦截器

import com.msunsoft.base.common.factory.ConstantFactory;
import com.msunsoft.base.common.interceptor.annotation.One2One;
import com.msunsoft.base.spring.SpringContextHolder;
import com.msunsoft.base.util.ToolUtil;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;

@Intercepts({
@Signature(type = ResultSetHandler.class,method = "handleResultSets", args = { Statement.class })
})
public class One2OneInteceptor implements Interceptor {


    private Properties properties;
    private SpringContextHolder spring;


    @Override
    @SuppressWarnings(value = {"all"})
    public Object intercept(Invocation invocation) throws Throwable {
        //
        List<Object> results = (List<Object>)invocation.proceed();
        SqlSessionFactory sqlSessionFactory = spring.getBean(SqlSessionFactory.class);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try{
            if(ToolUtil.isNotEmpty(results)){
                ConstantFactory constantFactory = spring.getBean(ConstantFactory.class);

                Class<?> cls = results.get(0).getClass();
                Field[] fields = cls.getDeclaredFields();// 暴力获取private修饰的成员变量  获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
                for(Object result:results){

                    for (Field field : fields) {
                        field.setAccessible(true);
                        One2One one2One = field.getAnnotation(One2One.class);
                        if(one2One!=null){
                            String byFieldString = one2One.byField();
                            Class resultType = one2One.resultType();
                            Class mapper = one2One.mapper();
                            String methodName = one2One.methodName();
                            Object objMaper = sqlSession.getMapper(mapper);
                            Method method = mapper.getMethod(methodName, Serializable.class);

                            Field byField = cls.getDeclaredField(byFieldString);
                            byField.setAccessible(true);
                            field.set(result, method.invoke(objMaper,byField.get(result)));

                        }
                    }
                }
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally{
            sqlSession.close();
            return results;
        }
    }

    @Override
    public Object plugin(Object target) {
        // 读取@Signature中的配置,判断是否需要生成代理类
        if (target instanceof ResultSetHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

使用实例(实体类中)

    //项目编号
    @TableField("project_id")
    private Long projectId;

    //项目信息
    @TableField(exist =  false)
    @One2One(byField = "projectId",resultType = Project.class,mapper= ProjectMapper.class,methodName = "selectById")
    private Project project;

 

   2.2  拦截器部分知识点

  2.1.1  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)

  2.1.2  MyBatis拦截器的接口定义

    一共有三个方法intercept 、plugin 、setProperties

    setProperties()

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

    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()获取参数数组。

   2.1.3  Spring Boot整合

   方法一   

    如果是使用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");
        executorInterceptor.setProperties(properties);
        return "interceptor";
    }
}

  OR

import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.msunsoft.base.common.interceptor.mybaits.DictReplaceInteceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@MapperScan("com.msunsoft.**.mapper")//Mapper接口扫描
public class DataSourceConfig {
    /**
     * 乐观锁mybatis插件
     */
   @Bean
    public OptimisticLockerInterceptor optimisticLockerInterceptor() {
        return new OptimisticLockerInterceptor();
    }

    /**
     * mybatis-plus分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
    
    @Bean
    public DictReplaceInteceptor dictReplaceInteceptor(){
        return new DictReplaceInteceptor();
    }

}

  方法三

    在拦截器上加@Component注解

 

ps:

一、引用并参考

1.《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

  https://blog.csdn.net/luanlouis/article/details/40422941      

2.关于mybatis拦截器,对结果集进行拦截

  https://www.cnblogs.com/SmallHan/articles/8127327.html

3.Springboot2(22)Mybatis拦截器实现

  https://blog.csdn.net/cowbin2012/article/details/85256360

二、涉及技术点

spring(注解、AOP) ,java反射与动态代理,mybaits(以上代码示例用的是mybaits-Plus 3.0.6.jar),

 

 

 

posted @ 2019-03-26 21:16  清风随心  阅读(2479)  评论(0编辑  收藏  举报