Mybatis拦截器实现自定义需求_第1页
Mybatis拦截器实现自定义需求_第2页
Mybatis拦截器实现自定义需求_第3页
Mybatis拦截器实现自定义需求_第4页
Mybatis拦截器实现自定义需求_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第Mybatis拦截器实现自定义需求目录前言一、应用场景二、Mybatis实现自定义拦截器2.1、编写拦截器2.2、添加到Mybatis配置2.3、测试2.4、小结三、拦截器接口介绍intercept方法plugin方法setProperties方法注意四、拦截器注解介绍Executor接口ParameterHandler接口ResultSetHandler接口StatementHandler接口五、进一步思考

前言

最近在参加金石计划,在考虑写什么的时,想到自己在项目中使用过的mybatis的插件,就想趁这个机会聊一聊我们接触频繁的Mybatis.

如果是使用过Mybatis的小伙伴,那么我们接触过的第一个Mybatis的插件自然就是分页插件(MybatisHelper)啦。

你有了解过它是如何实现的吗?你有没有自己编写Mybatis插件去实现一些自定义需求呢?

插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或改变框架原有的功能。

Mybatis中也提供了插件的功能,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现的,通过拦截某些方法的调用,在执行目标逻辑之前插入我们自己的逻辑实现。另外在MyBatis的插件模块中还涉及责任链模式和JDK动态代理~

文章大纲:

一、应用场景

一些字段的自动填充SQL语句监控、打印、数据权限等数据加解密操作、数据脱敏操作分页插件参数、结果集的类型转换

这些都是一些可以使用Mybatis插件实现的场景,当然也可以使用其他的方式来实现,只不过拦截的地方不一样罢了,有早有晚。

二、Mybatis实现自定义拦截器

我们用自定义拦截器实现一个相对简单的需求,在大多数表设计中,都会有create_time和update_time等字段,在创建或更新时需要更新相关字段。

如果是使用过MybatisPlus的小伙伴,可能知道在MybatisPlus中有一个自动填充功能,通过实现MetaObjectHandler接口中的方法来进行实现(主要的实现代码在com.baomidou.mybatisplus.core.MybatisParameterHandler中).

但使用Mybatis,并没有相关的方法或API可以直接来实现。所以我们这次就用以此处作为切入点,自定义拦截器来实现类似的自动填充功能。

编写步骤

编写一个拦截器类实现Interceptor接口添加拦截注解@Intercepts在xml文件中配置拦截器或者添加到Configuration中

基础的环境我就不再贴出来啦哈,直接上三个步骤的代码

2.1、编写拦截器

packageerceptor;

importlombok.extern.slf4j.Slf4j;

importorg.apache.ibatis.executor.Executor;

importorg.apache.ibatis.executor.parameter.ParameterHandler;

importorg.apache.ibatis.executor.resultset.ResultSetHandler;

importorg.apache.ibatis.executor.statement.StatementHandler;

importorg.apache.ibatis.mapping.MappedStatement;

importorg.apache.ibatis.mapping.SqlCommandType;

importorg.apache.ibatis.plugin.Interceptor;

importorg.apache.ibatis.plugin.Intercepts;

importorg.apache.ibatis.plugin.Invocation;

importorg.apache.ibatis.plugin.Signature;

importorg.springframework.beans.factory.annotation.Value;

importjava.lang.reflect.Field;

importjava.util.*;

*@author宁在春

*@version1.0

*@description:通过实现拦截器来实现部分字段的自动填充功能

*@date2025/4/621:49

@Intercepts({

@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})

@Slf4j

publicclassMybatisMetaInterceptorimplementsInterceptor{

@Override

publicObjectintercept(Invocationinvocation)throwsThrowable{

MappedStatementmappedStatement=(MappedStatement)invocation.getArgs()[0];

StringsqlId=mappedStatement.getId();

("sqlId"+sqlId);

SqlCommandTypesqlCommandType=mappedStatement.getSqlCommandType();

Objectparameter=invocation.getArgs()[1];

("sqlCommandType"+sqlCommandType);

("拦截查询请求Executor#update方法"+invocation.getMethod());

if(parameter==null){

returnceed();

if(SqlCommandType.INSERT==sqlCommandType){

Field[]fields=getAllFields(parameter);

for(Fieldfield:fields){

(""+field.getName());

try{

//注入创建时间

if("createTime".equals(field.getName())){

field.setAccessible(true);

Objectlocal_createDate=field.get(parameter);

field.setAccessible(false);

if(local_createDate==null||local_createDate.equals("")){

field.setAccessible(true);

field.set(parameter,newDate());

field.setAccessible(false);

}catch(Exceptione){

if(SqlCommandType.UPDATE==sqlCommandType){

Field[]fields=getAllFields(parameter);

for(Fieldfield:fields){

(""+field.getName());

try{

if("updateTime".equals(field.getName())){

field.setAccessible(true);

field.set(parameter,newDate());

field.setAccessible(false);

}catch(Exceptione){

e.printStackTrace();

returnceed();

@Override

publicObjectplugin(Objecttarget){

returnInterceptor.super.plugin(target);

//稍后会展开说的

@Override

publicvoidsetProperties(Propertiesproperties){

System.out.println("=======begin");

System.out.println(properties.getProperty("param1"));

System.out.println(properties.getProperty("param2"));

Interceptor.super.setProperties(properties);

System.out.println("=======end");

*获取类的所有属性,包括父类

*@paramobject

*@return

publicstaticField[]getAllFields(Objectobject){

Classclazz=object.getClass();

ListFieldfieldList=newArrayList();

while(clazz!=null){

fieldList.addAll(newArrayList(Arrays.asList(clazz.getDeclaredFields())));

clazz=clazz.getSuperclass();

Field[]fields=newField[fieldList.size()];

fieldList.toArray(fields);

returnfields;

2.2、添加到Mybatis配置

我这里使用的JavaConfig的方式

packagecom.nzc.config;

importerceptor.*;

importorg.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

@Configuration

publicclassMyBatisConfig{

@Bean

publicConfigurationCustomizerconfigurationCustomizer(){

returnnewConfigurationCustomizer(){

@Override

publicvoidcustomize(org.apache.ibatis.session.Configurationconfiguration){

//开启驼峰命名映射

configuration.setMapUnderscoreToCamelCase(true);

MybatisMetaInterceptormybatisMetaInterceptor=newMybatisMetaInterceptor();

Propertiesproperties=newProperties();

properties.setProperty("param1","javaconfig-value1");

properties.setProperty("param2","javaconfig-value2");

mybatisMetaInterceptor.setProperties(properties);

configuration.addInterceptor(mybatisMetaInterceptor);

}

如果是xml配置的话,则是如下:property是设置拦截器中需要用到的参数

configuration

plugins

plugininterceptor="erceptor.MybatisMetaInterceptor"

propertyname="param1"value="value1"/

propertyname="param2"value="value2"/

/plugin

/plugins

/configuration

2.3、测试

测试代码:实现了一个SysMapper的增删改查

packagecom.nzc.mapper;

importcom.nzc.entity.SysUser;

importorg.apache.ibatis.annotations.Insert;

importorg.apache.ibatis.annotations.Mapper;

importorg.apache.ibatis.annotations.Select;

importorg.apache.ibatis.annotations.Update;

importjava.util.List;

*@author宁在春

*@description针对表【sys_user】的数据库操作Mapper

@Mapper

publicinterfaceSysUserMapper{

@Select("SELECT*FROMtb_sys_user")

ListSysUserlist();

@Insert("insertintotb_sys_user(id,username,realname,create_time,update_time)values(#{id},#{username},#{realname},#{createTime},#{updateTime})")

Booleaninsert(SysUsersysUser);

@Update("updatetb_sys_usersetusername=#{username},realname=#{realname},update_time=#{updateTime}whereid=#{id}")

booleanupdate(SysUsersysUser);

}

/**

*@author宁在春

*@version1.0

*@description:TODO

*@date2025/4/621:38

@Slf4j

@RunWith(SpringRunner.class)

@SpringBootTest

publicclassSysUserMapperTest{

@Autowired

privateSysUserMappersysUserMapper;

@Test

publicvoidtest1(){

System.out.println(sysUserMapper.list());

@Test

publicvoidtest2(){

SysUsersysUser=newSysUser();

sysUser.setId("1235");

sysUser.setUsername("nzc5");

sysUser.setRealname("nzc5");

System.out.println(sysUserMapper.insert(sysUser));

@Test

publicvoidtest3(){

SysUsersysUser=newSysUser();

sysUser.setId("1235");

sysUser.setUsername("nzc7");

sysUser.setRealname("nzc5");

System.out.println(sysUserMapper.update(sysUser));

当然重点不在这里,而是在我们打印的日志上,一起来看看效果吧

此处相关日志对应Interceptor中的日志打印,想要了解的更为详细的可以debug查看一番。

2.4、小结

通过这个小小的案例,我想大伙对于Mybatis中的拦截器应当是没有那般陌生了吧,接下来再来仔细聊聊吧

如果你使用过MybatisPlus的话,在读完这篇博文后,可以思考思考下面这个问题,或去看一看源码,将知识串联起来,如果可以的话,记得把答案贴到评论区啦~~~

思考:还记得这一小节开始我们聊到的MybatisPlus实现的自动填充功能吗?它是怎么实现的呢?

三、拦截器接口介绍

MyBatis插件可以用来实现拦截器接口Interceptor,在实现类中对拦截对象和方法进行处理

publicinterfaceInterceptor{

//执行拦截逻辑的方法

Objectintercept(Invocationinvocation)throwsThrowable;

//这个方法的参数target就是拦截器要拦截的对象,该方法会在创建被拦截的接口实现类时被调用。

//该方法的实现很简单,只需要调用MyBatis提供的Plug类的wrap静态方法就可以通过Java动态代理拦截目标对象。

defaultObjectplugin(Objecttarget){

returnPlugin.wrap(target,this);

//这个方法用来传递插件的参数,可以通过参数来改变插件的行为

defaultvoidsetProperties(Propertiesproperties){

//NOP

}

有点懵没啥事,一个一个展开说:

intercept方法

Objectintercept(Invocationinvocation)throwsThrowable;

简单说就是执行拦截逻辑的方法,但不得不说这句话是个高度概括~

首先我们要明白参数Invocation是个什么东东:

publicclassInvocation{

privatefinalObjecttarget;//拦截的对象信息

privatefinalMethodmethod;//拦截的方法信息

privatefinalObject[]args;//拦截的对象方法中的参数

publicInvocation(Objecttarget,Methodmethod,Object[]args){

this.target=target;

this.method=method;

this.args=args;

//get...

//利用反射来执行拦截对象的方法

publicObjectproceed()throwsInvocationTargetException,IllegalAccessException{

returnmethod.invoke(target,args);

}

联系我们之前实现的自定义拦截器上的注解:

@Intercepts({

@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})

})

target对应我们拦截的Executor对象method对应Executor#update方法args对应Executor#update#args参数

plugin方法

这个方法其实也很好说:

那就是Mybatis在创建拦截器代理时候会判断一次,当前这个类Interceptor到底需不需要生成一个代理进行拦截,如果需要拦截,就生成一个代理对象,这个代理就是一个{@linkPlugin},它实现了jdk的动态代理接口{@linkInvocationHandler},如果不需要代理,则直接返回目标对象本身加载时机:该方法在mybatis加载核心配置文件时被调用

defaultObjectplugin(Objecttarget){

returnPlugin.wrap(target,this);

}

publicclassPluginimplementsInvocationHandler{

//利用反射,获取这个拦截器MyInterceptor的注解Intercepts和Signature,然后解析里面的值,

//1先是判断要拦截的对象是哪一个

//2然后根据方法名称和参数判断要对哪一个方法进行拦截

//3根据结果做出决定,是返回一个对象呢还是代理对象

publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){

MapClass,SetMethodsignatureMap=getSignatureMap(interceptor);

Classtype=target.getClass();

//这边就是判断当前的interceptor是否包含在

Class[]interfaces=getAllInterfaces(type,signatureMap);

if(interfaces.length0){

returnProxy.newProxyInstance(

type.getClassLoader(),

interfaces,

newPlugin(target,interceptor,signatureMap));

//如果不需要代理,则直接返回目标对象本身

returntarget;

//

}

setProperties方法

在拦截器中可能需要使用到一些变量参数,并且这个参数是可配置的,这个时候我们就可以使用这个方法啦,加载时机:该方法在mybatis加载核心配置文件时被调用

defaultvoidsetProperties(Propertiesproperties){

//NOP

}

关于如何使用:

javaConfig方式设置:

@Bean

publicConfigurationCustomizerconfigurationCustomizer(){

returnnewConfigurationCustomizer(){

@Override

publicvoidcustomize(org.apache.ibatis.session.Configurationconfiguration){

//开启驼峰命名映射

configuration.setMapUnderscoreToCamelCase(true);

MybatisMetaInterceptormybatisMetaInterceptor=newMybatisMetaInterceptor();

Propertiesproperties=newProperties();

properties.setProperty("param1","javaconfig-value1");

properties.setProperty("param2","javaconfig-value2");

mybatisMetaInterceptor.setProperties(properties);

configuration.addInterceptor(mybatisMetaInterceptor);

}

通过mybatis-config.xml文件进行配置

configuration

plugins

plugininterceptor="erceptor.MybatisMetaInterceptor"

propertyname="param1"value="value1"/

propertyname="param2"value="value2"/

/plugin

/plugins

/configuration

测试效果就是测试案例上那般,通过了解拦截器接口的信息,对于之前的案例不再是那般模糊啦

接下来再接着聊一聊拦截器上面那一坨注解信息是用来干嘛的吧,

注意

当配置多个拦截器时,MyBatis会遍历所有拦截器,按顺序执行拦截器的plugin口方法,被拦截的对象就会被层层代理。

在执行拦截对象的方法时,会一层层地调用拦截器,拦截器通invocationproceed()调用下层的方法,直到真正的方法被执行。

方法执行的结果从最里面开始向外层层返回,所以如果存在按顺序配置的三个签名相同的拦截器,MyBaits会按照CBAceed()ABC的顺序执行。如果签名不同,就会按照MyBatis拦截对象的逻辑执行.

这也是我们最开始谈到的Mybatis插件模块所使用的设计模式-责任链模式。

四、拦截器注解介绍

上一个章节,我们只说明如何实现Interceptor接口来实现拦截,却没有说明要拦截的对象是谁,在什么时候进行拦截.就关系到我们之前编写的注解信息啦.

@Intercepts({

@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})

})

这两个注解用来配置拦截器要拦截的接口的方法。

@Intercepts({})注解中是一个@Signature()数组,可以在一个拦截器中同时拦截不同的接口和方法。

MyBatis允许在己映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的接口包括以下几个。

ExecutorParameterHandlerResultSetHandlerStatementHandler

@Signature注解包含以下三个属性。

type设置拦截接口,可选值是前面提到的4个接口method设置拦截接口中的方法名可选值是前面4个接口中所对应的方法,需要和接口匹配args设置拦截方法的参数类型数组通过方法名和参数类型可以确定唯一一个方法

Executor接口

下面就是Executor接口的类信息

publicinterfaceExecutor{

intupdate(MappedStatementms,Objectparameter)throwsSQLException;

EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;

EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;

ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;

ListBatchResultflushStatements()throwsSQLException;

voidcommit(booleanrequired)throwsSQLException;

voidrollback(booleanrequired)throwsSQLException;

CacheKeycreateCacheKey(MappedStatementms,ObjectparameterObject,RowBoundsrowBounds,BoundSqlboundSql);

booleanisCached(MappedStatementms,CacheKeykey);

voidclearLocalCache();

voiddeferLoad(MappedStatementms,MetaObjectresultObject,Stringproperty,CacheKeykey,ClasstargetType);

TransactiongetTransaction();

voidclose(booleanforceRollback);

booleanisClosed();

voidsetExecutorWrapper(Executorexecutor);

}

我只会简单说一些最常用的~

1、update

intupdate(MappedStatementms,Objectparameter)throwsSQLException;

该方法会在所有的INSERT、UPDATE、DELETE执行时被调用,因此如果想要拦截这类操作,可以拦截该方法。接口方法对应的签名如下。

@Intercepts({

@Signature(type=Executor.class,method="update",args={MappedStatement.class,Object.class})

})

2、query

EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,CacheKeycacheKey,BoundSqlboundSql)throwsSQLException;

EListEquery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler)throwsSQLException;

该方法会在所有SELECT查询方法执行时被调用通过这个接口参数可以获取很多有用的信息,这也是最常被拦截的方法。

@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}

)})

3、queryCursor:

ECursorEqueryCursor(MappedStatementms,Objectparameter,RowBoundsrowBounds)throwsSQLException;

该方法只有在查询的返回值类型为Cursor时被调用。接口方法对应的签名类似于之前的。

//该方法只在通过SqlSession方法调用commit方法时才被调用

voidcommit(booleanrequired)throwsSQLException;

//该方法只在通过SqlSessio口方法调用rollback方法时才被调用

voidrollback(booleanrequired)throwsSQLException;

//该方法只在通过SqlSession方法获取数据库连接时才被调用,

TransactiongetTransaction();

//该方法只在延迟加载获取新的Executor后才会被执行

voidclose(booleanforceRollback);

//该方法只在延迟加载执行查询方法前被执行

booleanisClosed();

注解的编写方法都是类似的。

ParameterHandler接口

publicinterfaceParameterHandler{

//该方法只在执行存储过程处理出参的时候被调用

ObjectgetParameterObject();

//该方法在所有数据库方法设置SQL参数时被调用。

voidsetParameters(PreparedStatementps)throwsSQLException;

}

我都写一块啦,如果要拦截某一个的话只写一个即可

@Intercepts({

@Signature(type=ParameterHandler.class,method="getParameterObject",args={}),

@Signature(type=ParameterHandler.class,method="setParameters",args={PreparedStatement.class})

})

ResultSetHandler接口

publicinterfaceResultSetHandler{

//该方法会在除存储过程及返回值类型为Cursor以外的查询方法中被调用。

EListEhandleResultSets(Statementstmt)throwsSQLException;

//只会在返回值类型为ursor查询方法中被调用

ECursorEhandleCursorResultSets(Statementstmt)throwsSQLException;

//只在使用存储过程处理出参时被调用,

voidhandleOutputParameters(CallableStatementcs)throwsSQLException;

}

@Intercepts({

@Signature(type=ResultSetHandler.class,method="handleResultSets",args={Statement.class}),

@Signature(type=ResultSetHandler.class,method="handleCursorResultSets",args={Statement.class}),

@Signature(type=ResultSetHandler.class,method="handleOutputParameters",args={CallableStatement.class})

})

StatementHandler接口

publicinterfaceStatementHandler{

//该方法会在数据库执行前被调用优先于当前接口中的其他方法而被执行

Statementprepare(Connectionconnection,

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论