【編程】Mybatis插件統一修改Sql語句

事件起因:一邊開發一邊完善需求,突然要求爲每個數據的查詢新增權限。需求新增,賬號分爲市、區、街、園賬號。每次查詢都只能查詢自己範圍以下的數據。

情況分析:已經開發的所有代碼,都需要逐個排查,分析是否需要增加查詢、或者刪修的條件。

解決方案:

1、利用Spring的web層攔截器拿到該賬號具體對應什麼權限。ThreadLocal保存數據。

2、Mybatis插件攔截一部分需要修改的Sql語句,攔截之後按某種規則修改。

3、把部分信息通過Sql註釋的方式寫在Mybatis的sqlXML文件中。保證sql在能正常執行的前提下,比較優雅的完成修改。

1、Mybatis插件+配置

package com.xx.xx.xx.xx.xx;

import com.xx.xx.xx.dto.BaseDataRangeDTO;
import com.xx.xx.xx.utils.BizInfoUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanMap;
import org.apache.ibatis.executor.statement.RoutingStatementHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Slf4j
@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 })
		@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class, Integer.class})
})
public class BaseDataRangeInterceptor implements Interceptor {
	private static final String INTERCEPTOR_PREFIX = "withAuthority";

	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();

	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();

	private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

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

		StatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
		MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
		String[] sqlids = mappedStatement.getId().split("\\.");
		String sqlid = sqlids[sqlids.length - 1];

		BaseDataRangeDTO dataRange = BizInfoUtils.getCurrentDataRange();
		log.info("[BaseDataRangeInterceptor] sqlid -> {} dataRange -> {}", sqlid, dataRange);
		if (sqlid.startsWith(INTERCEPTOR_PREFIX) && dataRange != null) {

			BoundSql boundSql = statementHandler.getBoundSql();
			List<ParameterMapping> list = boundSql.getParameterMappings();
			String sql = boundSql.getSql();

			Map<?, ?> map = new BeanMap(boundSql.getParameterObject());
			log.info("[BaseDataRangeInterceptor] ParameterMap -> {}", map);

			String endsql = "";

			switch (dataRange.getLevelRange()) {
				case 1:
					endsql = "city_code = " + dataRange.getCityCode();
					break;
				case 2:
					endsql = "district_code = " + dataRange.getDistrictCode();
					break;
				case 3:
					endsql = "department_code  = " + dataRange.getDepartmentCode();
					break;
				case 4:
					endsql = "street_code  = " + dataRange.getStreetCode();
					break;
				case 5:
					endsql = "kindergarten_code = " + dataRange.getKindergartenCode();
					break;
				default:
			}
			String mSql = sql.replaceAll("/\\*(.*)\\*/", "$1" + endsql);
			Field field = boundSql.getClass().getDeclaredField("sql");
			field.setAccessible(true);
			field.set(boundSql, mSql);
		}
		return invocation.proceed();
	}

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

	@Override
	public void setProperties(Properties properties) {

	}
}
package com.xx.xx.xx.xx.xx.xx.config;


import com.xx.xx.xx.xx.BaseDataRangeInterceptor;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class InterceptorConfig {

	@Bean
	public ConfigurationCustomizer configurationCustomizer() {
		return new ConfigurationCustomizer() {
			@Override
			public void customize(org.apache.ibatis.session.Configuration configuration) {
				configuration.setMapUnderscoreToCamelCase(true);
				configuration.addInterceptor(new BaseDataRangeInterceptor());
			}
		};
	}
}

2、Web攔截器設置權限參數

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanMap;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Configuration
@Slf4j
public class WebControllerAspect {
	//計時
	ThreadLocal<Long> localTimer = new ThreadLocal<>();

	@Pointcut("execution(* com.pingan.pcloud.lg.info.controller.web.*.*Controller.*(..))")
	public void weblog() {

	}

	@Around("weblog()")
	public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();

		String url = request.getRequestURL().toString();
		String method = request.getMethod();
		String queryString = request.getQueryString();
		localTimer.set(System.currentTimeMillis());

		//false data
		BizInfoUtils.setCurrentDataRange(new BaseDataRangeDTO(1, 0, 1, 1, 0, 0, 0, 0));
		log.info("<WebControllerAspect> [{}] -> method -> [{}] -> with params [{}]", url, method, queryString);

		Object result = pjp.proceed();
		log.info("<WebControllerAspect> [{}] -> time -> [{}] -> return data [{}]", url, System.currentTimeMillis() - localTimer.get(), new BeanMap(result));
		return result;
	}
}

3、sqlXML裏的註釋巧妙使用

<select id="withAuthorityGetOrganizationTree" parameterType="int" resultMap="organizationResp">
		select * from xxxxx o WHERE o.is_del = 0 /*and o.*/ AND o.parent_id = #{id}
</select>

 

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