实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于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. windows 2003 IIS 设置 FTP被动模式

    IIS FTP 将21端口更改为xx123端口: 更改数据端口: cd c:/Inetpub/AdminScripts cscript.exe adsutil.vbs set /MSFTPSVC/Pa ...

  2. UOJ #390. 【UNR #3】百鸽笼

    UOJ #390. [UNR #3]百鸽笼 题目链接 看这道题之前先看一道相似的题目 [PKUWC2018]猎人杀. 考虑类似的容斥: 我们不妨设处理\(1\)的概率. 我们令集合\(T\)中的所有鸽 ...

  3. cpu的组成及分工

    控制单元是上帝:掌控一切: 运算单元只负责算术和逻辑运算,运算的指令由控制单元提供,数据由寄存器提供: 存储单元:一方面给运算单元提供输入输出,另一方面在控制单元的控制下和内存通信: 控制单元使用运算 ...

  4. MyBatis+Hibernate+JDBC对比分析

    MyBatis目前作为持久层,用的最多,因为它符合互联网开发的变动性,实际开发中需求总会有这样的,那样的变动,MyBatis虽然没有Hibernate那么全自动化,而且对于开发人员的sql能力要求比较 ...

  5. nodeJS---URL相关模块用法(url和querystring)

    nodeJS---URL相关模块用法(url和querystring) 一: URL模块: URL模块用于解析和处理URL的字符串,提供了如下三个方法: 1. parse 2. format 3. r ...

  6. PAT A1016 Phone Bills (25 分)——排序,时序

    A long-distance telephone company charges its customers by the following rules: Making a long-distan ...

  7. 【Codeforces 1106E】 Lunar New Year and Red Envelopes

    Codeforces 1106 E 题意:有\(k\)个红包,第\(i\)个红包可以在\(s_i\)到\(t_i\)的时间内抢,同时获得\(w_i\)的钱,但是抢完以后一直到\(d_i\)都不可以继续 ...

  8. Subversion 1.8.9 ( SVN Client ) 安装最新版本的svn客户端

    For CentOS7 Users: [WandiscoSVN] name=Wandisco SVN Repo baseurl=http://opensource.wandisco.com/cento ...

  9. 如何在Skyline中加载ArcGISServer发布的WMS和WMTS服务

    如何在Skyline中加载ArcGISServer发布的WMS和WMTS服务? 我这里的测试环境是ArcGISServer10.1和TerraExplorer Pro7.0,主要过程截图如下,

  10. 三、java三大特性--多态

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...