实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于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),

mybaits拦截器+自定义注解的更多相关文章

  1. SpringVC 拦截器+自定义注解 实现权限拦截

    1.springmvc配置文件中配置 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns= ...

  2. spring拦截器和注解处理日志操作

    整体思想:通过拦截器拦截所有的请求,处理含有自定义注解的方法,通过request得到需要的参数. 拦截器代码: package com.zktx.platform.log2; import java. ...

  3. MyBatis拦截器自定义分页插件实现

    MyBaits是一个开源的优秀的持久层框架,SQL语句与代码分离,面向配置的编程,良好支持复杂数据映射,动态SQL;MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyB ...

  4. 基于SpringMVC拦截器和注解实现controller中访问权限控制

    SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法. preHandle在业务处理器 ...

  5. SpringMVC之八:基于SpringMVC拦截器和注解实现controller中访问权限控制

    SpringMVC的拦截器HandlerInterceptorAdapter对应提供了三个preHandle,postHandle,afterCompletion方法. preHandle在业务处理器 ...

  6. Struts2知识点小结(四)--拦截器与注解开发

    一.Struts2的拦截器(interceptor) 作用:当请求进入struts2框架后(进入之前可以用filter进行拦截),想对请求进行拦截操作(功能增强.权限控制),需要拦截器组件 1.str ...

  7. SpringMVC(AbstractController,拦截器,注解)

    1.Controller接口及其实现类 Controller是控制器/处理器接口,只有一个方法handleRequest,用于进行请求的功能处理(功能处理方法),处理完请求后返回ModelAndVie ...

  8. struts2拦截器-自定义拦截器,放行某些方法(web.xml配置)

    一.web.xml配置 <filter> <filter-name>encodingFilter</filter-name> <filter-class> ...

  9. Struts2配置拦截器自定义栈时抛异常:Unable to load configuration. - interceptor-ref - file:/D:/tomcat_install/webapps/crm/WEB-INF/classes/struts.xml

    代码如下: <interceptors>  <!-- 注册自定义拦截器 -->   <interceptor name="LoginInterceptor&qu ...

随机推荐

  1. January 31st, 2018 Week 05th Wednesday

    Real love is not just instinct, but intent. 真正的爱不是身体上的一见钟情,而是要用心去经营. What is real love? Honestly, I ...

  2. 一步步教你上架iOS APP

    注意,注意,注意:一定要设置Enable Xombie Objects为不勾选.在Edit Scheme中有一个Run,然后选择Diagnostics,然后取消勾选Enable Zombie Obje ...

  3. 二: python基础数据类型(int,

    一.什么是数据类型?2018-12-20   20:57:3õ # (3) num = 0 while num < 10: num += 1 if num == 7: num += 1 # 7执 ...

  4. Java设计模式之九 ----- 解释器模式和迭代器模式

    前言 在上一篇中我们学习了行为型模式的责任链模式(Chain of Responsibility Pattern)和命令模式(Command Pattern).本篇则来学习下行为型模式的两个模式, 解 ...

  5. C#的深拷贝和浅拷贝

    也许会有人这样解释C# 中浅拷贝与深拷贝区别: 浅拷贝是对引用类型拷贝地址,对值类型直接进行拷贝. 不能说它完全错误,但至少还不够严谨.比如:string 类型咋说? 其实,我们可以通过实践来寻找答案 ...

  6. 解决jqueryeasyUI dialog 弹出窗体超出浏览器,导致不能关闭的bug

    使用panel的onMove事件攻克了panel,dialog以及window组件在被拖动时,会超出浏览器边界而无法拖回的情况. 当窗体被拖出浏览器有边界时.$(document).width();会 ...

  7. centos7下安装docker(2镜像)

    docker最小的镜像——hello-world 下载镜像  docker pull docker pull hello-world 查看镜像  docker images docker images ...

  8. SA-题目

    SA的题目 差异:https://lydsy.com/JudgeOnline/problem.php?id=3238 题意概述:给定一个长度为 $n$ 的字符串 $S$,令 $T_ i$ 表示它从第 ...

  9. Semaphore实现的生产者消费者程序

    Semaphore:Semaphores are often used to restrict the number of threads than can access some (physical ...

  10. Ros使用Arduino 1安装Arduino IDE

    安装Arsuino IDE sudo apt-get install arduino 设置库文件路径 在使用ROS的库文件时,必须在代码的开头包括: #include <ros.h> 接下 ...