关于Mybatis拦截器的使用

  • 1 Mybatis拦截器的使用
    • 1 自定义拦截器
      • 1 Interceptor接口
      • 2 @Intercepts注解
      • 3 @Signature注解
    • 2 注册拦截器
    • 3 拦截器使用案列
      • 1 日志打印
      • 2 数据隔离

上一篇记录了Mybatis拦截器的说明,对于其原理功能,有了初步的了解,本次记录一下Mybatis在日常中的应用场景

1 Mybatis拦截器的使用

上一篇给到了Mybatis官方对于拦截器的使用方法, 而在日常项目中使用拦截器,只需要分成两步.

  • 1 自定义拦截器
  • 2 注册拦截器

1 自定义拦截器

根据上一篇说明可知,自定义拦截器需要实现org.apache.ibatis.plugin.Interceptor接口, 并在接口上添加@Intercepts注解.

1 Interceptor接口

public interface Interceptor {/***  拦截要执行的方法, 在这个方法中自定义的逻辑*/Object intercept(Invocation invocation) throws Throwable;/*** 拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理*  1 拦截  当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)*  2 不拦截 当返回的是当前对象的时候 就不会调用intercept方法*/Object plugin(Object target);/***  用于指定属性,注册当前拦截器的时候可以设置一些属性*/void setProperties(Properties properties);}

2 @Intercepts注解

@Intercepts注解是通过一个@Signature注解(拦截点),来指定拦截那个对象里面的某个方法

Intercepts注解的参数列表是Signature注解数组

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {// Signature注解数组Signature[] value();
}

3 @Signature注解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {/*** 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个*/Class<?> type();/*** 在定义拦截类的基础之上,在定义拦截的方法*/String method();/*** 在定义拦截方法的基础之上在定义拦截的方法对应的参数,* 因方法里面可能重载,不指定参数列表,不能确定是对应拦截的方法*/Class<?>[] args();
}

2 注册拦截器

注册拦截器,就是一个普通的Bean对象注册并交由Spring管理.

/*** mybatis拦截器配置*/
@Configuration
public class MybatisConfiguration {/*** 注册拦截器*/@Beanpublic MybatisInterceptor mybatisInterceptor() {MybatisInterceptor mybatisInterceptor = new MybatisInterceptor();Properties properties = new Properties();// 可以调用properties.setProperty方法来给拦截器设置一些自定义参数mybatisInterceptor.setProperties(properties);return mybatisInterceptor;}
}

3 拦截器使用案列

1 日志打印

在项目基本增删改查功能完成的基础上, 需要添加日志打印或日志记录入库的需求,此时可使用Mybatis拦截器.

自定义拦截器

/*** 记录打印日志*/
@Slf4j
@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}),@Signature(type = Executor.class,method = "update",args = {MappedStatement.class, Object.class})
})
public class Loginterceptor implements Interceptor {// 是否开启记录日志private Boolean enable;public Loginterceptor(Boolean enable) {this.enable = enable;}@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object param = invocation.getArgs()[1];BoundSql boundSql = mappedStatement.getBoundSql(param);long logStartTime = System.currentTimeMillis();if (enable == null || !enable) {return invocation.proceed();}// 执行sql语句Object result = invocation.proceed();long logEndTime = System.currentTimeMillis();// 组装sql语句String sql = formatSql(boundSql, mappedStatement.getConfiguration()).concat(";");// 构造日志对象LogStat sqlStat = LogStat.builder().id(mappedStatement.getId()).sqlCostTime(String.valueOf(logEndTime - logStartTime).concat(" ms")).sql(sql).build();// 打印拦截sql日志, 也可以根据不同的要求保存到数据log.info("Mybatis拦截器执行了, 拦截的数据为: {}",JSON.toJSONString(sqlStat));return result;}/*** 拼接sql*/private String formatSql(BoundSql boundSql, Configuration configuration) {String sql = boundSql.getSql();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();Object parameterObject = boundSql.getParameterObject();if (StringUtils.isBlank(sql)) {return "";}if (configuration == null) {return "";}TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();sql = beautifySql(sql);if (parameterMappings != null) {for (ParameterMapping parameterMapping : parameterMappings) {if (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}String paramValueStr = "";if (value instanceof String) {paramValueStr = "'" + value + "'";} else if (value instanceof Date) {paramValueStr = "'" + this.dateToStrTimeMill((Date) value) + "'";} else {paramValueStr = value + "";}sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(paramValueStr));}}}return sql;}private static String formatStr = "yyyy-MM-dd HH:mm:ss:SSS";/*** 时间转换*/private String dateToStrTimeMill(Date currentDate) {if (currentDate == null || formatStr == null) {return null;} else {SimpleDateFormat sdf = new SimpleDateFormat(formatStr);return sdf.format(currentDate);}}/*** 格式化sql*/private String beautifySql(String sql) {sql = sql.replaceAll("[\\s\n ]+", " ");return sql;}/*** 拦截对象属于Executor实例*/@Overridepublic Object plugin(Object target) {if (target instanceof Executor) {return Plugin.wrap(target, this);}return target;}/*** 可设置属性*/@Overridepublic void setProperties(Properties properties) {}/*** 定义日志记录内部类*/@Data@NoArgsConstructor@AllArgsConstructor@Builderstatic class LogStat {// sql语句private String sql;// 执行耗时private String sqlCostTime;// 全限定名private String id;}
}

注册拦截器

@Configuration
public class SqlConfig {@Autowiredprivate SqlSessionFactory sqlSessionFactory;// 是否开启日志记录 默认开启@Value("${sqlPrint:true}")private String enable;@PostConstructpublic void init(){sqlSessionFactory.getConfiguration().addInterceptor(new Loginterceptor(Boolean.parseBoolean(enable)));}
}

2 数据隔离

在某些多租户数据隔离项目中, 一些数据需要根据不同的租户,展示对应的数据,在一些查询中,为了安全和方便,可以将这一部分功能放到Mybatis拦截中执行.

自定义拦截器

/*** 多租户查询拦截*/
@Intercepts({@Signature(method = "prepare",type = StatementHandler.class,args = {Connection.class, Integer.class}),@Signature(method = "query",type = Executor.class,args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,CacheKey.class, BoundSql.class})
})
public class TenantInterceptor implements Interceptor {// 跳过拦截的mapperprivate final Set<String> ignore = new HashSet<>();public TenantInterceptor( String... ignore) {if (ignore != null && ignore.length > 0) {this.ignore.addAll(Arrays.asList(ignore));}}@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler handler = (StatementHandler) invocation.getTarget();// 由于mappedStatement中有我们需要的方法id,但却是protected的,所以要通过反射获取MetaObject statementHandler = SystemMetaObject.forObject(handler);MappedStatement mappedStatement = (MappedStatement) statementHandler.getValue("delegate.mappedStatement");// 不是查询类型,直接放过if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {return invocation.proceed();}String namespace = mappedStatement.getId();// 忽视拦截对象,直接放过if (ignore.contains(namespace)) {return invocation.proceed();}String className = namespace.substring(0, namespace.lastIndexOf("."));String methodName = namespace.substring(namespace.lastIndexOf(".") + 1);Class<?> clazz = Class.forName(className);Tenant tenant = null;Method[] methods = clazz.getMethods();for (Method method : methods) {if (methodName.equals(method.getName())) {tenant = method.getAnnotation(Tenant.class);break;}}// 方法没有租户注解,直接放过if (tenant == null) {return invocation.proceed();}BoundSql boundSql = handler.getBoundSql();// 获取sqlString sql = boundSql.getSql();StringBuilder whereSql = new StringBuilder();CCJSqlParserManager parserManager = new CCJSqlParserManager();Select select = (Select) parserManager.parse(new StringReader(sql));PlainSelect plain = (PlainSelect) select.getSelectBody();// 获取当前查询条件Expression where = plain.getWhere();// 租户数据隔离// 从用户上下文域中获取用户id和租户id等信息if (tenant != null) {whereSql.append("( 1 = 0 OR ");whereSql.append(addAlias(plain, tenant.userField())).append(" = '").append(BaseContextHolder.getUserId()).append("'");whereSql.append(" AND ");whereSql.append(addAlias(plain, tenant.tenantField())).append(" = '").append(BaseContextHolder.getTenantId()).append("' )");}if (where == null) {if (tenant != null) {whereSql.append(")");}if (whereSql.length() > 0) {Expression whereExpression = CCJSqlParserUtil.parseCondExpression(whereSql.toString());plain.setWhere(whereExpression);}} else {if (whereSql.length() > 0) {whereSql.append(" and ( ").append(where.toString()).append(" )");} else {whereSql.append(where.toString());}if (tenant != null) {whereSql.append(")");}Expression expression = CCJSqlParserUtil.parseCondExpression(whereSql.toString());plain.setWhere(expression);}statementHandler.setValue("delegate.boundSql.sql", select.toString());return invocation.proceed();}/*** 拦截StatementHandler实例对象     * @param target* @return*/@Overridepublic Object plugin(Object target) {if (target instanceof StatementHandler) {return Plugin.wrap(target, this);}return target;}@Overridepublic void setProperties(Properties properties) {}/*** 添加别名* @param plain* @param field* @return*/private String addAlias(PlainSelect plain, String field) {if (plain.getFromItem().getAlias() != null) {return plain.getFromItem().getAlias() + "." + field;} else {return field;}}
}

额外使用到租户注解来进行开关控制. 租户注解用在需要添加租户隔离的查询方法上或类上.

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public @interface Tenant {/*** 租户属性*/String tenantField() default "tenant_id";/*** 用户信息*/String userField() default "crt_user";
}

注册拦截器

@Configuration
public class TenantConfig {/*** 注册拦截器*/@Beanpublic TenantInterceptor tenantInterceptor() {TenantInterceptor tenantInterceptor = new TenantInterceptor();return tenantInterceptor;}
}

关于Mybatis拦截器的使用相关推荐

  1. MySQL拦截器获取xml id_关于mybatis拦截器,有谁知道怎么对结果集进行拦截,将指定字段查询结果进行格式化...

    用MyBatis结果集拦截器做过这样一个需求: 由于项目需求经常变动,项目MySQL数据库都是存放JSON字符串,例如:用户的基本信息随着版本升级可能会有变动 数据表 CREATE TABLE `ac ...

  2. list mybatis 接收 类型_基于mybatis拦截器实现的一款简易影子表自动切换插件

    近期因工作需要,小编基于mybatis拦截器开发了一款简易影子表自动切换插件,可以根据配置实现动态修改表名,即将对原source table表的操作自动切换到对target table表的操作.该插件 ...

  3. 面试官:你能说说MyBatis拦截器原理吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Format cnblogs.com/fangjian042 ...

  4. 犯罪心理解读Mybatis拦截器

    原文链接:"犯罪心理"解读Mybatis拦截器 Mybatis拦截器执行过程解析 文章写过之后,我觉得 "Mybatis 拦截器案件"背后一定还隐藏着某种设计动 ...

  5. MyBatis拦截器原理探究MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截 ...

  6. MyBatis拦截器有哪些以及分析

    MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允许你在已映射语句执行过程中 ...

  7. Mybatis拦截器 mysql load data local 内存流处理

    Mybatis 拦截器不做解释了,用过的基本都知道,这里用load data local主要是应对大批量数据的处理,提高性能,也支持事务回滚,且不影响其他的DML操作,当然这个操作不要涉及到当前所lo ...

  8. MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...

  9. insert into select 主键自增_springboot2结合mybatis拦截器实现主键自动生成

    点击上方蓝字关注我们 1 01 前言 前阵子和朋友聊天,他说他们项目有个需求,要实现主键自动生成,不想每次新增的时候,都手动设置主键.于是我就问他,那你们数据库表设置主键自动递增不就得了.他的回答是他 ...

  10. Mybatis 拦截器介绍

    Mybatis 拦截器介绍 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 拦截器的一个作用就是我们 ...

最新文章

  1. ICCV2017 | 一文详解GAN之父Ian Goodfellow 演讲《生成对抗网络的原理与应用》(附完整PPT)
  2. 取一定范围内随机小数 c_随机振动测试中的常见试验条件有哪些?
  3. windows下搭建OpenGL ES开发环境
  4. chrome浏览器上传文件延迟_UEditor chrome 点击上传文件选择框会延迟几秒才会显示...
  5. C/C++网络编程工作笔记0003---客户服务端程序说明
  6. ACM之八数码问题----BFS搜索----数独游戏的模拟(下)
  7. 圆通电子面单接口对接
  8. 计算机怎么识别ascll汉字,汉字的ASCII码表示和编码是怎样的?
  9. 《安士全书》善世第一奇书
  10. 下载谷歌浏览器官方正式(稳定)版以及历史各种版本
  11. 计算机科学导论任务书,计算机科学导论论文提纲格式范文 计算机科学导论论文提纲如何写...
  12. 昆明市盘龙区打造铸牢中华民族共同体意识盘龙江示范带
  13. 二逼青年暑假深圳面试记
  14. shineblink BH1750光照强度传感器
  15. 率土之滨服务器维护2月19日,率土之滨连发两封致歉信后,资深月卡党的我决定重新入坑...
  16. 解压缩zip文件的工具类
  17. 晶圆检测显微镜是做什么研究才用到的?
  18. Nyoj 71 独木舟上的旅行
  19. 对一名电子信息工程专业应届毕业生的建议 .
  20. stm32c8t6运行freertos

热门文章

  1. 小儿计算机编程是什么意思,小儿趣味编程是什么,幼儿学习编程的好处以及要点...
  2. JupyterLab 介绍 及简单教程
  3. iPhone折叠屏手机曝光 最快下半年现身
  4. 东大22春大学英语(四)X《大学英语(四)》在线平时作业2百分非答案
  5. 最新发布:支付宝生态小程序激励政策延期
  6. 基于JAVA社区团购管理系统
  7. android安装多个微信支付,android微信支付详解与坑
  8. Matlab的矩阵乘法
  9. 小王学JAVA 1.2计算机基本知识
  10. 《我的输入法更换轨迹》