1. 程式人生 > >Springboot2(22)Mybatis攔截器實現

Springboot2(22)Mybatis攔截器實現

原始碼地址

springboot2教程系列

MyBatis提供了一種外掛(plugin)的功能,雖然叫做外掛,但其實這是攔截器功能

MyBatis 允許攔截的介面

MyBatis 允許你在已對映語句執行過程中的某一點進行攔截呼叫。預設情況下,MyBatis 允許使用外掛來攔截的方法呼叫包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

Executor介面的部分方法,比如update,query,commit,rollback等方法,還有其他介面的一些方法等。

總體概括為:

  1. 攔截執行器的方法
  2. 攔截引數的處理
  3. 攔截結果集的處理,為sql執行之後的結果攔截過濾
  4. 攔截Sql語法構建的處理,為sql執行之前的攔截進行sql封裝

MyBatis攔截器的介面定義

一共有三個方法interceptpluginsetProperties

setProperties()

方法主要是用來從配置中獲取屬性。

如果是使用xml式配置攔截器,可在Mybatis配置檔案中新增如下節點,屬性可以以如下方式傳遞

<plugins>
	<plugin interceptor="tk.mybatis.simple.plugin.XXXInterceptor">
		<property name="propl" value="valuel" />
		<property name="prop2" value="value2" />
	</plugin>
</plugins>

如果在Spring boot中使用,則需要單獨寫一個配置類,如下:

@Configuration
public class MybatisInterceptorConfig {
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
        ExecutorInterceptor executorInterceptor = new ExecutorInterceptor();
        Properties properties = new Properties();
        properties.setProperty("prop1","value1");
        executorInterceptor.setProperties(properties);
        return "interceptor";
    }
}

如果說不需要配置屬性,則在spring boot中,不需要去編寫配置類,只需要像我一樣在攔截器上加個@Component即可。

plugin()

方法用於指定哪些方法可以被此攔截器攔截。

intercept()

方法是用來對攔截的sql進行具體的操作。

註解實現

MyBatis攔截器用到了兩個註解:@Intercepts@Signature

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

type的值與類名相同,method方法名相同,為了避免方法過載,args中指定了各個引數的型別和個數,可通過invocation.getArgs()獲取引數陣列。

Executor 攔截器實現

@Intercepts({
        @Signature(
        type= Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
@Slf4j
@Component
public class ExecutorInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String sql = ExecutorPluginUtils.getSqlByInvocation(invocation);
        //可以對sql重寫
        log.error("攔截器ExecutorInterceptor:"+sql);
        //sql = "SELECT id from BUS_RECEIVER where id = ? ";
        ExecutorPluginUtils.resetSql2Invocation( invocation,  sql);
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }


}

ParameterHandler 攔截器實現

/**
 * @Author: ynz
 * @Date: 2018/12/23/023 12:07
 */
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
@Component
@Slf4j
public class ParamInterceptor  implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        log.error("攔截器ParamInterceptor");
        //攔截 ParameterHandler 的 setParameters 方法 動態設定引數
        if (invocation.getTarget() instanceof ParameterHandler) {
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            PreparedStatement ps = (PreparedStatement) invocation.getArgs()[0];

            // 反射獲取 BoundSql 物件,此物件包含生成的sql和sql的引數map對映
            Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
            boundSqlField.setAccessible(true);
            BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);
            // 反射獲取 引數對像
            Field parameterField = 
                    parameterHandler.getClass().getDeclaredField("parameterObject");
            parameterField.setAccessible(true);
            Object parameterObject = parameterField.get(parameterHandler);

            if (parameterObject instanceof Map) {
                //將引數中的name值改為2
                ((Map) parameterObject).put("name","2");
            }

            // 改寫的引數設定到原parameterHandler物件
            parameterField.set(parameterHandler, parameterObject);
            parameterHandler.setParameters(ps);


            log.error(JSON.toJSONString(boundSql.getParameterMappings()));
            log.error(JSON.toJSONString(parameterObject));
        }
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

ResultSetHandler 攔截器實現

@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args={Statement.class})
})
@Component
@Slf4j
public class ResultInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.error("攔截器ResultInterceptor");
       // ResultSetHandler resultSetHandler1 = (ResultSetHandler) invocation.getTarget();
        //通過java反射獲得mappedStatement屬性值
        //可以獲得mybatis裡的resultype
        Object result = invocation.proceed();
        if (result instanceof ArrayList) {
            ArrayList resultList = (ArrayList) result;
            for (int i = 0; i < resultList.size(); i++) {
                Object oi = resultList.get(i);
                Class c = oi.getClass();
                Class[] types = {String.class};
                Method method = c.getMethod("setAddress", types);
                // 呼叫obj物件的 method 方法
                method.invoke(oi, "china");
            }
        }
        return result;
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

StatementHandler 攔截器實現

/**
 * @Author: ynz
 * @Date: 2018/12/23/023 14:22
 */
@Intercepts(
        {@Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {Connection.class, Integer.class}
                )
        })
@Component
@Slf4j
public class StatementInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = 
                (StatementHandler) PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        MappedStatement mappedStatement = 
                (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        //只攔截select方法
        if (!SqlCommandType.SELECT.equals(mappedStatement.getSqlCommandType())) {
            return invocation.proceed();
        }
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        //獲取到sql
        String originalSql = boundSql.getSql();
        //可以對originalSql進行改寫
        log.error("攔截器StatementInterceptor:"+originalSql);
        metaStatementHandler.setValue("delegate.boundSql.sql", originalSql);
        Object parameterObject = boundSql.getParameterObject();
        return invocation.proceed();
    }

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

    @Override
    public void setProperties(Properties properties) {

    }
}

Spring Boot整合

方法1:手寫一個配置類

@Configuration
public class MybatisInterceptorConfig {
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory) {
        ExecutorInterceptor executorInterceptor = new ExecutorInterceptor();
        Properties properties = new Properties();
        properties.setProperty("prop1","value1");
        executorInterceptor.setProperties(properties);
        sqlSessionFactory.getConfiguration().addInterceptor(executorInterceptor);
        sqlSessionFactory.getConfiguration().addInterceptor(new ParamInterceptor());
        sqlSessionFactory.getConfiguration().addInterceptor(new ResultInterceptor());
        return "interceptor";
    }
}

方法2:在攔截器上加@Component註解