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

    一.前言 该博客里面的方法均是看着苹果官方的API来解释的,一般都是常用的方法如有问题,请指出. 二.简介: 该类集成的是其本身,大家可以从任何一个类去向上追溯,都会发现最终的父类都是NSObject ...

  2. Alpha冲刺 - 事后诸葛亮

    Alpha冲刺 - 事后诸葛亮 Alpha完成情况表 Stardust(安卓端) 模块 预期计划 现实进展 完成度 登录/注册 登录时,从服务器拉取的数据并同步数据库.获取的数据有:用户名.密码.记录 ...

  3. java基础面试题(Servlet生命周期)

    Servlet运行在Servlet容器中,其生命周期由容器来管理.Servlet的生命周期通过javax.servlet.Servlet接口中的init().service()和destroy()方法 ...

  4. 【转】ICCAVR TAB键设置

    转载于: http://blog.163.com/liuyunqian@yeah/blog/static/7039584320099159545292/ 在使用ICCAVR C编译器的时候会发现TAB ...

  5. WebStorm如何分配运行内存?The IDE is running low on memory...

    vue项目做的后台管理系统做得差不多了,安装的依赖包也越来越大,就在春节放假的前两天,突然发现我的电脑居然带不动WebStorm了,查改一些代码,WebStorm运行迟钝,鼠标滑动严重“掉帧”,让我非 ...

  6. Python 使用 xlwings 往 excel中写入一列数据的两种方法

    1.准备一个二维列表,然后再range后面不指定任何选项,可以输出该二维列表中数据在一列中显示,如下代码: # -*- coding:utf-8 -*- import xlwings as xw li ...

  7. esp8266(1) 手机+Arduino+esp8266通信

    ESP8266 Android与Arduino通信 功能描述: 1 Arduino上电,它通过软串口(Arduino的 2号和3号脚)发送命令,配置espson8266为 AP模式,wifi名 DDD ...

  8. Python高级网络编程系列之第二篇

    在上一篇中,我们深入探讨了TCP/IP协议的11种状态,理解这些状态对我们编写服务器的时候有很大的帮助,但一般写服务器都是使用C/Java语言,因为这些语言对高并发的支持特别好.我们写的这些简单的服务 ...

  9. Linux系统--命令行安装weblogic10.3.6

    Linux下命令行安装weblogic10.3.6 一.安装前准备工作: 1.创建用户useradd weblogic;创建用户成功linux系统会自动创建一个和用户名相同的分组,并将该用户分到改组中 ...

  10. vmware 12中安装苹果系统

    我用的系统是win10... 一.所需软件: 1.下载并安装VMware Workstation Pro 12 密码:7ybc和序列号 密码是:bwm0 2.下载unlocker 203(for OS ...