mybatis拦截器

mybatis拦截器基础知识

编写类继承mybatis的Interceptor接口,并实现其相关方法。mybatis的拦截器,是被动态代理类主动调用的。

  • intercept:在拦截时,需要执行的业务逻辑
  • plugin:是否代理Executor、ParameterHandler、ResultSetHandler、StatementHandler对象中的某个或某些,
    如果代理,则返回相应对象的代理对象,否则返回原对象。根据类上Intercepts注解决定是否返回代理对象。
  • setProperties:配置时,向拦截器设置相关参数配置

Intercepts注解:设置该拦截器需要代理的类型及其方法

  • Signature注解:拦截哪个类的哪个方法
  • type参数:被代理对象类型,只能是Executor、ParameterHandler、ResultSetHandler、StatementHandler这几种的一种。
  • method参数:被代理对象的方法,如Executor对象的update方法
  • args参数:方法参数,Java是支持重载,因此需要参数和方法名才能唯一确定某类的某个方法

比如:我们需要代理Executor的query方法,而查询方法有2个,一个有(CacheKey cacheKey, BoundSql boundSql)参数一个没有,如果我们要代理的是没有这2个参数的方法,那么需要在配置中的args参数里就不写这2个参数

被代理类型执行顺序:Executor -> ParameterHandler -> ResultSetHandler -> StatementHandler

/**
* 这个示例表示该拦截器需要拦截Executor类的update和query方法
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),@Signature(type = Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor{@Overridepublic Object intercept(Invocation invocation) throws Throwable {//被代理对象的被代理方法参数,如Executor的update方法,那么args[0]为MappedStatement类型对象,args[1]为类型object对象//args[1]具体为什么对象呢:当前执行查询的mapper方法对应的参数及其值//如:当前我执行的是UserMapper的fundUser(@Param("user")User oneUser),那么这里的args[1]为一个map,key为user,值为形参oneUser对应的的值Object[] args = invocation.getArgs();//return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}
}
/**
* 向配置中添加拦截器
*/
@Configuration
public class MyBatisConfig {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@PostConstructpublic void addMySqlInterceptor() {MyBatisInterceptor interceptor = new MyBatisInterceptor();sqlSessionFactory.getConfiguration().addInterceptor(interceptor);}
}

mybatis拦截器原理

https://www.jianshu.com/p/b82d0a95b2f3

mybatis拦截器结合了动态代理和责任链2个模式。

动态代理类Plugin:

  • 实现了InvocationHandler接口。
  • 属性为目标对象和拦截器,通过私有的构造方法初始化数据
  • invoke方法:调用拦截器的拦截方法。
  • wrap静态方法:参数为目标对象和某个拦截器,如果某拦截器上的配置适合被代理类,则返回代理类,否则返回原始类。

拦截器类Interceptor:

  • intercept方法:参数为Invocation,拦截器具体业务逻辑,并在合适的位置调用invocation对象的执行方法;
  • wrap方法:该拦截器是否适用于被代理对象,一般直接调用Plugin的wrap方法即可。

责任链类InterceptorChain:

  • 包含一个拦截器集合,并可以向拦截器集合添加拦截器。
  • pluginAll方法:参数为被代理对象,遍历拦截器,并调用拦截器的wrap方法,将返回的结果作为下个拦截器调用wrap方法的参数。
    假设被代理对象为A,有多个拦截器,如A1,A2,A3,且wrap都返回代理对象,那么最终的结果为A3代理了A2,A2代理了A1,A1代理了最原始的被代理对象A

被代理类信息Invocation:

  • 里面包含被代理类型,被代理方法,方法参数,以及一个执行方法(通过反射的方式调用被代理类的被代理方法)。
  • 被代理类可能是最原始的被代理对象,也可能是另外一个代理类

示例

需求:

  1. 使用mybatis实现对数据的逻辑删除。
  • 如果执行删除操作,则将表中status字段修改成1
  • 查询和修改时,需要将status=0作为where条件
  1. 对于新增和修改,如果有username和phone字段,则需要动态添加字段username_enc和phone_enc为对应字段的密文,并保存到数据库

根据上述需求,需要在mybatis拦截器中解析sql,并动态重写sql。

maven 依赖

<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><!--<version>2.2.5.RELEASE</version>--><version>1.5.5.RELEASE</version><relativePath/></parent><groupId>com.example</groupId><artifactId>test-shardingsphere</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging><description>Demo project for Spring Boot</description><dependencies><!--swagger ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><!--springboot--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></dependency><!--mybatis starter--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.41</version></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.10</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.13</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!--sql解析--><dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>3.1</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.2</version><configuration> <!--配置文件的位置--><configurationFile>src/main/resources/generatorConfig.xml</configurationFile><verbose>true</verbose><overwrite>true</overwrite></configuration><executions><execution><id>Generate MyBatis Artifacts</id><goals><goal>generate</goal></goals></execution></executions><dependencies><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.2</version></dependency></dependencies></plugin></plugins></build>
</project>

mybatis拦截器配置

@Configuration
public class MyBatisConfig {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@PostConstructpublic void addMySqlInterceptor() {MyBatisInterceptor interceptor = new MyBatisInterceptor();sqlSessionFactory.getConfiguration().addInterceptor(interceptor);}
}

自定义拦截器

/**
*
*/
@Intercepts({@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),@Signature(type = Executor.class, method = "query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
@Slf4j
public class MyBatisInterceptor implements Interceptor{@Overridepublic Object intercept(Invocation invocation) throws Throwable {// 拦截sqlObject[] args = invocation.getArgs();MappedStatement statement = (MappedStatement) args[0];Object parameterObject = args[1];BoundSql boundSql = statement.getBoundSql(parameterObject);String sql = boundSql.getSql();if (sql==null||"".equals(sql)) {return invocation.proceed();}// 重写sqlresetSql2Invocation(invocation,sql);return invocation.proceed();}private void resetSql2Invocation(Invocation invocation,String sql) throws SQLException {final Object[] args = invocation.getArgs();MappedStatement statement = (MappedStatement) args[0];MapperMethod.ParamMap parameterObject = (MapperMethod.ParamMap)args[1];final BoundSql boundSql = statement.getBoundSql(parameterObject);List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//根据不同的sql类型重新构建新的sql语句String newsql = "";MyOperationType myOperationType = null;switch (statement.getSqlCommandType()) {case INSERT:myOperationType = new MyInsertOperationType();newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);break;case UPDATE:myOperationType = new MyUpdateOperationType();newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);break;case DELETE:myOperationType = new MyDeleteOperationType();newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);break;case SELECT:myOperationType = new MySelectOperationType();newsql = myOperationType.handle(statement,parameterMappings, parameterObject, sql);break;default:break;}// 重新new一个查询语句对像BoundSql newBoundSql = new BoundSql(statement.getConfiguration(), newsql, parameterMappings,parameterObject);// 把新的查询放到statement里MappedStatement newStatement = copyFromMappedStatement(statement, new BoundSqlSqlSource(newBoundSql));// 重新设置新的参数args[0] = newStatement;System.out.println("sql语句:"+newsql);}//构造新的statementprivate MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,ms.getSqlCommandType());builder.resource(ms.getResource());builder.fetchSize(ms.getFetchSize());builder.statementType(ms.getStatementType());builder.keyGenerator(ms.getKeyGenerator());if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {StringBuilder keyProperties = new StringBuilder();for (String keyProperty : ms.getKeyProperties()) {keyProperties.append(keyProperty).append(",");}keyProperties.delete(keyProperties.length() - 1, keyProperties.length());builder.keyProperty(keyProperties.toString());}builder.timeout(ms.getTimeout());builder.parameterMap(ms.getParameterMap());builder.resultMaps(ms.getResultMaps());builder.resultSetType(ms.getResultSetType());builder.cache(ms.getCache());builder.flushCacheRequired(ms.isFlushCacheRequired());builder.useCache(ms.isUseCache());return builder.build();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {// TODO Auto-generated method stub}class BoundSqlSqlSource implements SqlSource {private BoundSql boundSql;public BoundSqlSqlSource(BoundSql boundSql) {this.boundSql = boundSql;}@Overridepublic BoundSql getBoundSql(Object parameterObject) {return boundSql;}}}

使用jsqlparser重写sql

jsqlparser基本api操作参考:https://blog.csdn.net/qq_35986709/article/details/109806402

public interface MyOperationType {String handle(MappedStatement statement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql);default boolean isNotIn(ParameterMapping parameterMapping,List<ParameterMapping> parameterMappings){if(parameterMappings!=null){for(ParameterMapping it:parameterMappings){if(it.getProperty().equals(parameterMapping.getProperty())){return false;}}}return true;}default List<String> encList(String tableName,List<Column> columns){List<String> result = new ArrayList<>();List<String> list = getEncMap().get(tableName);if(list!=null&&columns!=null){for(Column col:columns){if(list.contains(col.getColumnName())){result.add(col.getColumnName());}}}return result;}default String getColumnValue(MapperMethod.ParamMap parameterObject,List<ParameterMapping> parameterMappings,List<Column> columns,String column){if(columns!=null){for(int i=0;i<columns.size();i++){Column col = columns.get(i);if(column.equals(col.getColumnName())){ParameterMapping parameterMapping = parameterMappings.get(i);String property = parameterMapping.getProperty();Object result = parameterObject.get(property);return (String)result;}}}return null;}Map<String,List<String>> getEncMap();
}public abstract class MyDefaultOperationType implements MyOperationType{Map<String,List<String>> encMaps = new HashMap<>();/*** 假设只对t_user表的username和phone2个字段进行加密存储*/public MyDefaultOperationType(){encMaps.put("t_user", Arrays.asList("username","phone"));}@Overridepublic Map<String, List<String>> getEncMap() {return encMaps;}
}public class MyInsertOperationType extends MyDefaultOperationType{@Overridepublic String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {try {//解析sql语句Statement statement = CCJSqlParserUtil.parse(sql);//强制转换成insert对象Insert insert = (Insert) statement;//从insert中获取表名Table table = insert.getTable();//从insert中获取字段名List<Column> columns = insert.getColumns();List<String> needEncs = encList(table.getName(), columns);if (!needEncs.isEmpty()) {ExpressionList list = (ExpressionList) insert.getItemsList();for(String col:needEncs) {String newCol = col+"_enc";Column miwen = new Column(newCol);//在insert里添加一个字段columns.add(miwen);//在insert的value里添加一个占位符?list.getExpressions().add(new JdbcParameter());//将新增的字段添加到mybatis框架的字段集合中ParameterMapping parameterMapping = new ParameterMapping.Builder(mystatement.getConfiguration(),newCol,String.class).build();if(isNotIn(parameterMapping,parameterMappings)) {parameterMappings.add(parameterMapping);}//添加参数值parameterObject.put(newCol, getColumnValue(parameterObject,parameterMappings,columns,col)+"123");}insert.setItemsList(list);}return insert.toString();}catch (Exception e){throw new RuntimeException("解析sql异常",e);}}
}public class MyUpdateOperationType extends MyDefaultOperationType {@Overridepublic String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {try {Statement statement = CCJSqlParserUtil.parse(sql);Update update = (Update)statement;Table table = update.getTable();List<Column> columns = update.getColumns();List<String> needEncs = encList(table.getName(), columns);if (!needEncs.isEmpty()) {List<Expression> expressions = update.getExpressions();for(String col:needEncs) {String newCol = col+"_enc";Column miwen = new Column(table,newCol);Expression expression = new JdbcParameter();expressions.add(expression);//添加参数keyParameterMapping parameterMapping = new ParameterMapping.Builder(mystatement.getConfiguration(),newCol,String.class).build();if(isNotIn(parameterMapping,parameterMappings)) {int index = parameterMappings.size()-(parameterMappings.size()- columns.size());parameterMappings.add(index,parameterMapping);}columns.add(miwen);//添加参数值parameterObject.put(newCol, getColumnValue(parameterObject,parameterMappings,columns,col)+"123");}Expression where = update.getWhere();EqualsTo status = new EqualsTo();status.setLeftExpression(new Column(table, "status"));StringValue stringValue = new StringValue("0");status.setRightExpression(stringValue);if(where!=null) {AndExpression lastwhere = new AndExpression(where, status);update.setWhere(lastwhere);}else{update.setWhere(status);}update.setExpressions(expressions);}return update.toString();}catch (Exception e){throw new RuntimeException("解析sql异常",e);}}
}public class MyDeleteOperationType extends MyDefaultOperationType {@Overridepublic String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {try {Statement statement = CCJSqlParserUtil.parse(sql);Delete delete = (Delete) statement;Table table = delete.getTable();Update update = new Update();update.setTable(table);update.setColumns(Arrays.asList(new Column(table,"status")));update.setExpressions(Arrays.asList(new StringValue("1")));update.setWhere(delete.getWhere());return update.toString();}catch (Exception e){throw new RuntimeException("解析sql异常",e);}}
}public class MySelectOperationType extends MyDefaultOperationType {@Overridepublic String handle(MappedStatement mystatement,List<ParameterMapping> parameterMappings, MapperMethod.ParamMap parameterObject, String sql) {try {Statement statement = CCJSqlParserUtil.parse(sql);Select select = (Select) statement;PlainSelect plain = (PlainSelect) select.getSelectBody();FromItem fromItem = plain.getFromItem();String tableName = fromItem.toString();Table table = new Table();table.setAlias(fromItem.getAlias());table.setName(tableName);Expression where = plain.getWhere();EqualsTo status = new EqualsTo();status.setLeftExpression(new Column(table, "status"));StringValue stringValue = new StringValue("0");status.setRightExpression(stringValue);if(where!=null) {AndExpression lastwhere = new AndExpression(where, status);plain.setWhere(lastwhere);}else{plain.setWhere(status);}return select.toString();}catch (Exception e){throw new RuntimeException("解析sql异常",e);}}
}

mybatis拦截器,并使用jsqlparser重写sql相关推荐

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

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

  2. Mybatis 拦截器介绍

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

  3. by mybatis 自定义order_springboot2结合mybatis拦截器实现主键自动生成

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

  4. SpringBoot2整合Mybatis拦截器,拦截mapper接口的某个方法

    需求: 在执行某个动态sql时,where 子句,希望通过用户进行自定义查询条件,比如用户可以传入 "id > 100011 and name = '张三'" 的多条件表达式 ...

  5. 《转载》Mybatis 拦截器介绍

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

  6. mybatis拦截器实现权限管理

    框架设计 基于Spring Security+JWT技术实现登录认证和访问授权,基于Mybatis 拦截器实现数据权限的控制.由于已有前文介绍Spring Security如有兴趣可以查看,在此将重点 ...

  7. Mybatis拦截器实现限制查询条数

    Mybatis拦截器实现限制查询条数 问题:查询结果过大的sql导致服务慢,系统不稳定? 解决思路:拦截sql,对sql进行修改,添加limit条件,限制查询结果的条数. 实现: 1.使用Mybati ...

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

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

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

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

最新文章

  1. Android Studio编译好的apk放在哪里?
  2. Pair Programming 1 (Casual Game) 曹竹 杨牧 贪吃蛇游戏扩展 Blog Report
  3. MacOS系统下的图形化工具
  4. org.hibernate.transientobjectexception:The given object has a null identifier: com.gxuwz.check.entit
  5. wordpress php7 mysql_WordPress可以使用PHP7的MySQLi扩展
  6. 讲讲 group by 的plus版
  7. 前端学习(1899)vue之电商管理系统电商系统之渲染添加用户的表单
  8. 从零开始构建HTML 5 Web页面
  9. plsql提示列快捷键_plsql常用快捷键
  10. java数组转对象_java将对象数组列表转换为POJO
  11. 【UML】部署图Deployment diagram(实现图)(转)
  12. asp php 对照表,asp 与php中常用函数对比
  13. javascript--返回顶部效果
  14. python大文件去重_python3 大文件去重
  15. Stack栈后进先出
  16. WINDOWS10 自带校验工具
  17. 提示用户输入一个高考_高考报名用户名密码显示错误怎么办
  18. 2021年中国化学纤维行业发展现状分析,循环再利用或为产品未来发展方向「图」
  19. RAID设备的数据恢复
  20. GL-Studio 4.3-1 安装

热门文章

  1. 1-2-07:打印ASCII码
  2. CSS中font-weight属性
  3. 音视频文件的码率与大小计算
  4. Flair:一款简单但技术先进的NLP库!
  5. el-table表单一键展开折叠,展开部分后一键全部展开或折叠
  6. java如何刷新输出_如何刷新打印功能的输出?
  7. Mac os 查看PDM文件
  8. Eclipse生成构造方法快捷键
  9. gsonformat安装怎么使用_Intellij IDEA 插件 GsonFormat 快速创建JSON格式JavaBean
  10. 解密中国移动推进TD科学发展策略