mybatis拦截器,并使用jsqlparser重写sql

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{

    @Override
    public 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();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
/**
* 向配置中添加拦截器
*/
@Configuration
public class MyBatisConfig {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    @PostConstruct
    public 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.0
	http://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 {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @PostConstruct
    public 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{

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 拦截sql
        Object[] 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();
        }

        // 重写sql
        resetSql2Invocation(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);
    }
      //构造新的statement
    private 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();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub

    }

    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public 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"));
    }

    @Override
    public Map<String, List<String>> getEncMap() {
        return encMaps;
    }
}

public class MyInsertOperationType extends MyDefaultOperationType{
    @Override
    public 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 {
    @Override
    public 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);

                    //添加参数key
                    ParameterMapping 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 {
    @Override
    public 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 {
    @Override
    public 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);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值