Mybatis 自定义拦截器与插件开发,java面试算法整理

MetaObject metaObject = configuration.newMetaObject(parameterObject);

for (ParameterMapping parameterMapping : parameterMappings) {

String propertyName = parameterMapping.getProperty();

if (metaObject.hasGetter(propertyName)) {

Object obj = metaObject.getValue(propertyName);

String parameterValue = obj.toString();

sql = sql.replaceFirst(“\?”, parameterValue);

sb.append(parameterValue).append(“(”).append(obj.getClass().getSimpleName()).append(“),”);

} else if (boundSql.hasAdditionalParameter(propertyName)) {

Object obj = boundSql.getAdditionalParameter(propertyName);

String parameterValue = obj.toString();

sql = sql.replaceFirst(“\?”, parameterValue);

sb.append(parameterValue).append(“(”).append(obj.getClass().getSimpleName()).append(“),”);

}

}

}

sb.deleteCharAt(sb.length()-1);

}

log.info(“==> SQL:”+sql);

log.info(sb.toString());

log.info(“==> SQL TIME:”+time+" ms");

}

}

执行代码,日志输出如下:

在上面的代码中,通过 Executor 拦截器获取到了 BoundSql 对象,进一步获取到sql的执行参数,从而实现了对sql执行的监控与统计。

StatementHandler

================

下面的例子中,通过改变 StatementHandler 对象的属性,动态修改sql语句的分页:

@Intercepts({

@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})})

public class StatementPlugin implements Interceptor {

@Override

public Object intercept(Invocation invocation) throws Throwable {

StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

metaObject.setValue(“delegate.rowBounds.offset”, 0);

metaObject.setValue(“delegate.rowBounds.limit”, 2);

return invocation.proceed();

}

}

MetaObject 是mybatis提供的一个用于方便、优雅访问对象属性的对象,通过将实例对象作为参数传递给它,就可以通过属性名称获取对应的属性值。虽然说我们也可以通过反射拿到属性的值,但是反射过程中需要对各种异常做出处理,会使代码中堆满难看的 try/catch ,通过 MetaObject 可以在很大程度上简化我们的代码,并且它支持对 Bean 、 Collection 、 Map 三种类型对象的操作。

对比执行前后:

可以看到这里通过改变了分页对象 RowBounds 的属性,动态地修改了分页参数。

ResultSetHandler

================

ResultSetHandler 会负责映射sql语句查询得到的结果集,如果在生产环境中存在一些保密数据,不想在外部系统中展示,那么可能就需要在查询到结果后做一下数据的脱敏处理,这时候就可以使用 ResultSetHandler 对结果集进行改写。

@Intercepts({

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

public class ResultSetPlugin implements Interceptor {

@Override

public Object intercept(Invocation invocation) throws Throwable {

System.out.println(“Result Plugin 拦截 :”+invocation.getMethod());

Object result = invocation.proceed();

if (result instanceof Collection) {

Collection objList= (Collection) result;

List resultList=new ArrayList<>();

for (Object obj : objList) {

resultList.add(desensitize(obj));

}

return resultList;

}else {

return desensitize(result);

}

}

//脱敏方法,将加密字段变为星号

private Object desensitize(Object object) throws InvocationTargetException, IllegalAccessException {

Field[] fields = object.getClass().getDeclaredFields();

for (Field field : fields) {

Confidential confidential = field.getAnnotation(Confidential.class);

if (confidential==null){

continue;

}

PropertyDescriptor ps = BeanUtils.getPropertyDescriptor(object.getClass(), field.getName());

if (ps.getReadMethod() == null || ps.getWriteMethod() == null) {

continue;

}

Object value = ps.getReadMethod().invoke(object);

if (value != null) {

ps.getWriteMethod().invoke(object, “***”);

}

}

return object;

}

}

运行上面的代码,查看执行结果:

{“id”:1358041517788299266,“orderNumber”:“***”,“money”:122.0,“status”:3,“tenantId”:2}

在上面的例子中,在执行完sql语句得到结果对象后,通过反射扫描结果对象中的属性,如果实体的属性上带有自定义的 @Confidential 注解,那么在脱敏方法中将它转化为星号再返回结果,从而实现了数据的脱敏处理。

ParameterHandler

================

mybatis可以拦截 ParameterHandler 注入参数,下面的例子中我们将结合前面介绍的其他种类的对象,通过组合拦截器的方式,实现一个简单的多租户拦截器插件,实现多租户下的查询逻辑。

@Intercepts({

@Signature(type = Executor.class,method = “query”, args = { MappedStatement.class, Object.class,RowBounds.class, ResultHandler.class }),

@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class}),

@Signature(type = ParameterHandler.class, method = “setParameters”, args = PreparedStatement.class),

})

public class TenantPlugin implements Interceptor {

private static final String TENANT_ID = “tenantId”;

@Override

public Object intercept(Invocation invocation) throws Throwable {

Object target = invocation.getTarget();

String methodName = invocation.getMethod().getName();

if (target instanceof Executor && methodName.equals(“query”) && invocation.getArgs().length==4) {

return doQuery(invocation);

}

if (target instanceof StatementHandler){

return changeBoundSql(invocation);

}

if (target instanceof ParameterHandler){

return doSetParameter(invocation);

}

return null;

}

private Object doQuery(Invocation invocation) throws Exception{

Executor executor = (Executor) invocation.getTarget();

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

Object paramObj = invocation.getArgs()[1];

RowBounds rowBounds = (RowBounds) invocation.getArgs()[2];

if (paramObj instanceof Map){

MapperMethod.ParamMap paramMap= (MapperMethod.ParamMap) paramObj;

if (!paramMap.containsKey(TENANT_ID)){

Long tenantId=1L;

paramMap.put(“param”+(paramMap.size()/2+1),tenantId);

paramMap.put(TENANT_ID,tenantId);

paramObj=paramMap;

}

}

//直接执行query,不用proceed()方法

return executor.query(ms, paramObj,rowBounds,null);

}

private Object changeBoundSql(Invocation invocation) throws Exception {

StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

PreparedStatementHandler preparedStatementHandler = (PreparedStatementHandler) metaObject.getValue(“delegate”);

String originalSql = (String) metaObject.getValue(“delegate.boundSql.sql”);

metaObject.setValue(“delegate.boundSql.sql”,originalSql+ " and tenant_id=?");

return invocation.proceed();

}

private Object doSetParameter(Invocation invocation) throws Exception {

ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];

MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);

BoundSql boundSql= (BoundSql) metaObject.getValue(“boundSql”);

List parameterMappings = boundSql.getParameterMappings();

boolean hasTenantId=false;

for (ParameterMapping parameterMapping : parameterMappings) {

if (parameterMapping.getProperty().equals(TENANT_ID)) {

hasTenantId=true;

}

}

//添加参数

if (!hasTenantId){

Configuration conf= (Configuration) metaObject.getValue(“configuration”);

ParameterMapping parameterMapping= new ParameterMapping.Builder(conf,TENANT_ID,Long.class).build();

parameterMappings.add(parameterMapping);

}

parameterHandler.setParameters(ps);

return null;

}

}

在上面的过程中,拦截了sql执行的三个阶段,来实现多租户的逻辑,逻辑分工如下:

  • Executor query Map Map Bean

  • StatementHandler prepare BoundSql

  • 拦截 ParameterHandler 的 setParameters 方法,动态设置参数,将租户id添加到要设置到参数列表中

最终通过拦截不同执行阶段的组合,实现了基于租户的条件拦截。

总结

==

总的来说,mybatis拦截器通过对 Executor 、 StatementHandler 、 ParameterHandler 、 ResultSetHandler 这4种接口中的方法进行拦截,并生成代理对象,在执行方法前先执行代理对象的逻辑,来实现我们自定义的逻辑增强。从上面的例子中,可以看到通过灵活使用mybatis拦截器开发插件能够帮助我们解决很多问题,但是同样它也是一把双刃剑,在实际工作中也不要滥用插件、定义过多的拦截器,因为通过学习我们知道mybatis插件在执行中使用到了代理模式和责任链模式,在执行sql语句前会经过层层代理,如果代理次数过多将会消耗额外的性能,并增加响应时间。

作者:码农参上

原文链接:https://mp.weixin.qq.com/s/8zAvIsjZNgG1tH4FN6UQSA
如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

711708893899)]
[外链图片转存中…(img-Idk7GFbp-1711708893900)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-RUlxKQTr-1711708893900)]

最后

文章中涉及到的知识点我都已经整理成了资料,录制了视频供大家下载学习,诚意满满,希望可以帮助在这个行业发展的朋友,在论坛博客等地方少花些时间找资料,把有限的时间,真正花在学习上,所以我把这些资料,分享出来。相信对于已经工作和遇到技术瓶颈的朋友们,在这份资料中一定都有你需要的内容。

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值