实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于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. ArcEngine中加载ArcGIS Server地图服务

    代码如下: private void addMapServerLayer(object sender, EventArgs e)  {             IActiveView pActiveV ...

  2. 手把手实战:eclipse 搭建 SpringMvc 框架环境

    环境准备 eclipse jdk1.8 tomcat 7 步骤总纲       a.准备好开发环境     b.下载相关jar包     c.在eclipse 中创建好工程     d.引进下载的ja ...

  3. css3属性中background-clip与background-origin的用法释疑

    困惑在哪里? background-clip 与 background-origin是css3中引入的两个跟元素背景相关的属性,它们有相同的可选值,即border.padding.content 三种 ...

  4. dirty_background_ration 与 /proc/sys/vm/dirty_ratio

    wappiness的值的大小对如何使用swap分区是有着很大的联系的.swappiness=0的时候表示最大限度使用物理内存,然后才是 swap空间,swappiness=100的时候表示积极的使用s ...

  5. day20 Python 高阶函数,函数,嵌套,闭包 装饰器

    高阶函数定义 1.函数接收的参数是一个函数名 2.函数的返回值是一个函数名 3.满足上述条件任意一个都可以称之为高阶函数 一.函数的接收参数是一个函数名 import time def foo(): ...

  6. mysql数据库的test类型

    文章参考自 window系统参考:http://blog.sina.com.cn/s/blog_46f7bb6d0102vde3.html linux 参考:http://www.linuxeye.c ...

  7. (转)yum提示Another app is currently holding the yum lock; waiting for it to exit...

    文章转自 yum 下载东西突然卡主了,我直接ctrl+c退出,然后再次下载时候出现 Another app is currently holding the yum lock; waiting for ...

  8. 【spring-boot神器】第一篇:拦截器,过滤器,监听器,控制器,消息转换器,AOP执行顺序

    整理一下这几天学习的资料和代码 第一部分.上代码 1.spring各种器的实现,idea搭建spring-boot的教程在这里http://www.jianshu.com/p/9082a533fa3c ...

  9. matlab:inv,pinv逆与伪逆

    对于方阵A,如果为非奇异方阵,则存在逆矩阵inv(A)对于奇异矩阵或者非方阵,并不存在逆矩阵,但可以使用pinv(A)求其伪逆   inv:   inv(A)*B实际上可以写成A\BB*inv(A)实 ...

  10. 如何用css实现"等高布局"。

    有时候为了让网页实现美观,在不知道高度的情况下,我们要用css实现等高布局效果,传统的方法, 我们可以用javascript实现,但是由于需求决定或者其他的情况下,我们只能用css实现,其方法主要是采 ...