通過mybatis攔截器修改sql內容,實現不同的內容存入相同的字段中

場景

系統需要記錄日誌然後保存到數據庫,保存的字段需要從請求信息中提取字段字段,不同的請求需要提取的字段不一樣

實現

請求a 的請求信息 {“aaa”:“aaa”,“bbbb”:“bbbb”},需要提取的字段爲 aaa,代碼實體中屬性名也爲aaa,
存入擴展字段一中,擴展字段的名爲 PARAM1,則代碼中需要將aaa與PARAM1對應起來
正常邏輯可以這麼做:

1.插入數據的時候可以 insert into log(PARAM1) values (#{aaa})
2.查詢的時候 select * from log where PARAM1 like “%XXX%” ,然後編寫 resultMap 進行字段映射
這樣的弊端:當有多個字段時,對應起來會讓人奔潰,多個擴展字段與抽取字段對應操作繁瑣,很容易出錯

解決辦法:

1.在屬性 aaa 上加入自定義註解 ,註解中標明擴展字段名爲 PARAM1
2.mybatis攔截器中對sql語句中aaa內容替換成註解上註明的PARAM1
效果:
1.插入數據的時候可以 insert into log(aaa) values (#{aaa})
2.查詢的時候 select * from log where aaa like “%XXX%” , 然後編寫 resultMap 進行字段映射
需要注意的是:查詢時sql語句不需要寫擴展字段名,但是 要寫 resultMap 進行字段映射

註解

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtendColumnName {
    String value() ;
}

數據庫定義

CREATE TABLE "RJGF_KMS"."KMS_REQUEST_LOG" 
   (	"ID" NUMBER, 
	"REQUEST_URL" VARCHAR2(100), 
	"REQUEST_DATA" VARCHAR2(1000), 
	"IP" VARCHAR2(20), 
	"RESPONSE_DATA" VARCHAR2(1000), 
	"TYPE_CODE" VARCHAR2(50), 
	"RECORD_TIME" TIMESTAMP (6), 
	"PARAM1" VARCHAR2(255), 
	"PARAM2" VARCHAR2(255), 
	"PARAM3" VARCHAR2(255), 
	"PARAM4" VARCHAR2(255), 
	"PARAM5" VARCHAR2(255)
   ) SEGMENT CREATION IMMEDIATE 
  PCTFREE 10 PCTUSED 40 INITRANS 1 MAXTRANS 255 NOCOMPRESS LOGGING
  STORAGE(INITIAL 65536 NEXT 8192 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT FLASH_CACHE DEFAULT CELL_FLASH_CACHE DEFAULT)

實體

public class PsamRequestLogEntity extends KmsRequestLog  {

    @ExtendColumnName("PARAM1")
    private String psamNo;

    @ExtendColumnName("PARAM2")
    private String code;

    @ExtendColumnName("PARAM3")
    private String lanNo;
}

攔截器

@Component
@Intercepts(
        {
                @Signature(type = Executor.class, method = "query",
                        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
                @Signature(type = Executor.class, method = "query",
                        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "update",
                        args = {MappedStatement.class, Object.class})
        }
)
public class RequestLogExtendColumnInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 獲取sql
        String sql = getSqlByInvocation(invocation);
        if (StringUtils.isBlank(sql)) {
            return invocation.proceed();
        }

        // 找出需要替換的字段
        Map<String, ExtendColumnName> extendColumnNames = getExtendColumn(invocation);

        if (extendColumnNames.size() == 0){
            return invocation.proceed();
        }

        // sql交由處理類處理  對sql語句進行處理
        String newSql = getExtendSql(extendColumnNames,sql);

        // 包裝sql後,重置到invocation中
        resetSql2Invocation(invocation, newSql);

        return invocation.proceed();
    }

    private String getExtendSql(Map<String, ExtendColumnName> extendColumnNames, String sql) {
        String retVal = sql;
        for (Map.Entry<String, ExtendColumnName> stringExtendColumnNameEntry : extendColumnNames.entrySet()) {
            String key = stringExtendColumnNameEntry.getKey();
            ExtendColumnName value = stringExtendColumnNameEntry.getValue();
            // 將sql中的屬性名 替換成 註解上的名字
            retVal = retVal.replace(key,value.value());
        }
        return retVal;
    }

    /**
     * 獲取需要替換字段名的屬性
     * @param invocation
     * @return
     */
    private Map<String, ExtendColumnName> getExtendColumn(Invocation invocation) {
        Map<String, ExtendColumnName> retVal = new HashMap<>();

        Object arg = invocation.getArgs()[1];
        // 當接口的方法有多個參數時,arg[1] 的對象爲 MapperMethod.ParamMap,只有一個參數時,arg[1] 就是參數本身
        if (arg instanceof MapperMethod.ParamMap){
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) arg;
            Collection values = paramMap.values();
            for (Object value : values) {
                if (value != null) {
                    getExtendColumn(retVal, value);
                }
            }
        } else {
            getExtendColumn(retVal,arg);
        }

        return retVal;
    }

    private void getExtendColumn(Map<String, ExtendColumnName> retVal, Object arg) {
        Field[] declaredFields = getAllFields(arg);
        for (Field declaredField : declaredFields) {
            ExtendColumnName annotation = declaredField.getAnnotation(ExtendColumnName.class);
            if (null != annotation){
                // 如果屬性上有 ExtendColumnName 該註解,就放入返回值中
                retVal.put(declaredField.getName(),annotation);
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {

    }

    /**
     * 獲取sql語句
     * @param invocation
     * @return
     */
    private String getSqlByInvocation(Invocation invocation) {
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        return boundSql.getSql();
    }

    /**
     * 包裝sql後,重置到invocation中
     * @param invocation
     * @param sql
     * @throws SQLException
     */
    private void resetSql2Invocation(Invocation invocation, String sql) throws SQLException {
        final Object[] args = invocation.getArgs();
        MappedStatement statement = (MappedStatement) args[0];
        Object parameterObject = args[1];
        BoundSql boundSql = statement.getBoundSql(parameterObject);
        MappedStatement newStatement = newMappedStatement(statement, new BoundSqlSqlSource(boundSql));
        MetaObject msObject =  MetaObject.forObject(newStatement, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(),new DefaultReflectorFactory());
        msObject.setValue("sqlSource.boundSql.sql", sql);
        args[0] = newStatement;

        // 如果參數個數爲6,還需要處理 BoundSql 對象
        if (6 == args.length){
            BoundSql boundSqlArg = (BoundSql) args[5];
            // 該對象沒有提供對sql屬性的set方法,只能通過反射進行修改
            Class<? extends BoundSql> aClass = boundSql.getClass();
            try {
                Field field = aClass.getDeclaredField("sql");
                field.setAccessible(true);
                field.set(boundSqlArg,sql);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    private MappedStatement newMappedStatement(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();
    }

    public static Field[] getAllFields(Object object){
        Class clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null){
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }


}

//    定義一個內部輔助類,作用是包裝sq
class BoundSqlSqlSource implements SqlSource {
    private BoundSql boundSql;
    public BoundSqlSqlSource(BoundSql boundSql) {
        this.boundSql = boundSql;
    }
    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        return boundSql;
    }
}

xml

<resultMap id="PsamRequestLog" type="net.rjgf.bg.modules.sys.entity.bean.PsamRequestLogEntity">
        <id column="id" property="id"></id>
        <result column="record_time" property="recordTime"></result>
        <result column="ip" property="ip"></result>
        <result column="request_data" property="requestData"></result>
        <result column="response_data" property="responseData"></result>
        <result column="request_url" property="requestUrl"></result>
        <result column="type_code" property="typeCode"></result>
        <result column="PARAM1" property="psamNo"></result>
        <result column="PARAM2" property="code"></result>
        <result column="PARAM3" property="lanNo"></result>
    </resultMap>

    <!--查詢-->
    <select id="selectPSAMLogByConditions" parameterType="net.rjgf.bg.modules.sys.entity.so.PsamRequestLogSo" resultMap="PsamRequestLog">
        select * from KMS_REQUEST_LOG
        <where>
            <if test="so.ip != null and so.ip != ''">
                and ip like concat('%',concat(#{so.ip},'%'))
            </if>
            <if test="so.typeCode != null and so.typeCode != ''">
                and type_code like concat('%',concat(#{so.typeCode},'%'))
            </if>
            <if test="so.psamNo != null and so.psamNo != ''">
                and psamNo like concat('%',concat(#{so.psamNo},'%'))
            </if>
            <if test="so.code != null and so.code != ''">
                and code like concat('%',concat(#{so.code},'%'))
            </if>
            <if test="so.lanNo != null and so.lanNo != ''">
                and lanNo like concat('%',concat(#{so.lanNo},'%'))
            </if>
            <if test="so.startTime != null and so.startTime != ''">
                and record_time >= to_date(#{so.startTime},'yyyy-mm-dd')
            </if>
            <if test="so.endTime != null and so.endTime != ''">
                and to_date(#{so.endTime},'yyyy-mm-dd') >= record_time
            </if>
            <if test="so.requestUrl != null and so.requestUrl != ''">
                and request_url like concat('%',concat(#{so.requestUrl},'%'))
            </if>
        </where>
    </select>
    
<!--新增Psam請求日誌-->
    <insert id="insertPsamLog" parameterType="net.rjgf.kms.forward.entity.bean.PsamRequestLogEntity">
        INSERT INTO KMS_REQUEST_LOG(ID,REQUEST_URL, REQUEST_DATA, IP, RESPONSE_DATA, TYPE_CODE, RECORD_TIME, psamNo,lanNo,code)
        VALUES ( KMS_REQUEST_LOG_SEQ.NEXTVAL,#{requestUrl,jdbcType=VARCHAR}, #{requestData, jdbcType=VARCHAR}, #{ip, jdbcType=VARCHAR}, #{responseData, jdbcType=VARCHAR}, #{typeCode, jdbcType=VARCHAR}, #{recordTime, jdbcType=VARCHAR}, #{psamNo, jdbcType=VARCHAR}, #{lanNo, jdbcType=VARCHAR}, #{code, jdbcType=VARCHAR})
    </insert>

效果

這樣就不需要管數據庫「擴展字段名」與代碼實體類中「屬性名」的對應關係了

攔截器中需要注意的地方

sql修改後要更新兩個對象 「MappedStatement」與「BoundSql」都修改下最好,因爲源碼中有時候用到了BoundSql對象來獲取sql語句

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章