Bootstrap

(mybatis拦截器)统一增加表字段,mybatis拦截器统一修改表公共字段

1.powerdesigner设置表公共字段

powerdesigner:这是一个还没有进行添加公共字段的数据库模型(pdm)
在这里插入图片描述
接下来我们需要在每一个表中添加公共属性,需要用到vbs脚本 统一添加字段脚本下载 下载完成后在powerdesigner中点击 工具->Execute Commands->Edit/Run Script…(或者可以使用快捷键 Ctrl+Shift+X)在弹出的框中选择文件打开下载好的vbs脚本,run之后会动态生成。

2.pdm生成数据库脚本

统一增加公共属性后,点击顶部DataBase->Generate DataBase,弹出框选择要创建sql脚本的路径和文件名称
在这里插入图片描述
点击确定后生成脚本。
在这之前可以通过Preview查看将要生成的sql
在这里插入图片描述
生成sql后,可以直接通过脚本创建库表。

通过Mybatis拦截器做公共属性的自动赋值

mybatis拦截器是在sql执行之前后将sql进行拦截,修改sql
具体内容👇

package com.yunyu.base.plugin;

import com.yunyu.base.constant.SessionKey;
import com.yunyu.base.dto.ILoginDto;
import com.yunyu.base.entity.AbstractEntity;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
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;
import javax.servlet.http.HttpSession;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.*;

/**
 * @create 2018-03-24 0:11
 *
 * @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
 * @Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class}),
 * @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 })
 */
@Component
@Intercepts({
		@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
		@Signature(type = Executor.class, method = "createCacheKey", args = {MappedStatement.class, Object.class, RowBounds.class, BoundSql.class})
})
public class MyBatisInterceptor implements Interceptor {

	private static final Logger	LOGGER	= LoggerFactory.getLogger(MyBatisInterceptor.class);

	/** Mybatis全局公共参数变量名 */
	private static final String COMMON_FIELDS_KEY = "commonFields";

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object[] args = invocation.getArgs();
		MappedStatement ms = (MappedStatement) args[0];
		Object parameter = args[1];
		Executor executor = (Executor) invocation.getTarget();

		if (args.length == 2) {
			// 插入、更新、删除时拦截处理
			invokeUpdate(ms, parameter);

			return executor.update(ms, parameter);
		} else {
			// 查询时拦截处理
			RowBounds rowBounds = (RowBounds) args[2];
			BoundSql boundSql = (BoundSql) args[3];
			invokeQuery(parameter, boundSql);

			return executor.createCacheKey(ms, parameter, rowBounds, boundSql);
		}
	}

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

	@Override
	public void setProperties(Properties properties) {
	}

	/**
	 * 执行INSERT、UPDAT、DELETEE时填充公共字段
	 * @param ms Mapper映射
	 * @param parameter 入参对象
	 */
	private void invokeUpdate(MappedStatement ms, Object parameter) throws InvocationTargetException, IllegalAccessException {

		if (parameter != null && parameter instanceof AbstractEntity) {	// Mapper操作
			AbstractEntity entity = (AbstractEntity) parameter;

			// 提取公共参数并填充
			Map<String, Object> commonFieldsMap = buildCommonFields();
			SqlCommandType sqlCommandType = ms.getSqlCommandType();
			if (sqlCommandType == sqlCommandType.INSERT) {
				entity.setAddAction((String) commonFieldsMap.get("action"));
				entity.setAddTermIp((String) commonFieldsMap.get("termIp"));
				entity.setAddUserId((Long) commonFieldsMap.get("userId"));
				entity.setAddDt((Long) commonFieldsMap.get("dt"));
			} else if (sqlCommandType == sqlCommandType.UPDATE) {
				entity.setVersion((entity.getVersion() != null ? entity.getVersion() : 1) + 1);
				entity.setUpdAction((String) commonFieldsMap.get("action"));
				entity.setUpdTermIp((String) commonFieldsMap.get("termIp"));
				entity.setUpdUserId((Long) commonFieldsMap.get("userId"));
				entity.setUpdDt((Long) commonFieldsMap.get("dt"));
			}
		} else {	// Dao操作
			BoundSql boundSql = ms.getBoundSql(parameter);
			invokeQuery(parameter, boundSql);
		}
	}

	/**
	 * 执行SELECT时填充公共字段
	 * @param parameter 入参对象
	 * @param boundSql 参数绑定关系
	 */
	private void invokeQuery(Object parameter, BoundSql boundSql) throws InvocationTargetException, IllegalAccessException {

		// 若参数映射没有包含的key直接返回
		List<String> paramNames = new ArrayList<>();
		boolean hasKey = hasParamKey(paramNames, boundSql.getParameterMappings());
		if (!hasKey) {
			return;
		}

		// 改写参数
		processParams(parameter, paramNames);
	}

	/**
	 * 通过Dao设置参数时
	 * @param invocation 目标方法
	 */
	private void invokeSetParameter(Invocation invocation) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, SQLException {
		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);

		// 若参数映射没有包含的key直接返回
		List<String> paramNames = new ArrayList<>();
		boolean hasKey = hasParamKey(paramNames, boundSql.getParameterMappings());
		if (!hasKey) {
			return;
		}

		// 反射获取 参数对像
		Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
		parameterField.setAccessible(true);
		Object parameterObject = parameterField.get(parameterHandler);

		// 改写参数
		parameterObject = processParams(parameterObject, paramNames);

		// 改写的参数设置到原parameterHandler对象
		parameterField.set(parameterHandler, parameterObject);
		parameterHandler.setParameters(ps);
	}

	/**
	 * 判断已生成SQL参数映射中是否包含commonFields
	 * @param paramNames 参数名列表
	 * @param parameterMappings 参数映射表
	 * @return true:已包含,false:未包含
	 */
	private boolean hasParamKey(List<String> paramNames, List<ParameterMapping> parameterMappings) {
		boolean hasKey = false;
		for (ParameterMapping parameterMapping : parameterMappings) {
			if (parameterMapping.getProperty().startsWith(COMMON_FIELDS_KEY)) {
				hasKey = true;
			} else {
				paramNames.add(parameterMapping.getProperty());
			}
		}
		return hasKey;
	}

	/**
	 * 对原参数对象做处理,植入公共参数信息
	 * @param paramObj 原参数对象
	 * @param paramNames 参数名列表
	 * @return 新参数对象
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 */
	private Object processParams(Object paramObj, List<String> paramNames) throws InvocationTargetException, IllegalAccessException {
		// 提取公共字段信息
		Map<String, Object> commonFields = buildCommonFields();

		if (paramObj == null) {
			// 无参数时,添加Map参数对象
			Map<String, Object> paramMap = new MapperMethod.ParamMap<>();
			paramMap.put(COMMON_FIELDS_KEY, commonFields);
			paramObj = paramMap;

		} else if ((ClassUtils.isPrimitiveOrWrapper(paramObj.getClass())
				|| String.class.isAssignableFrom(paramObj.getClass())
				|| Number.class.isAssignableFrom(paramObj.getClass()))
				&& paramNames.size() == 1) {
			// 单参数时,将参数转为Map
			Map<String, Object> paramMap = new MapperMethod.ParamMap<>();
			paramMap.put(paramNames.iterator().next(), paramObj);
			paramMap.put(COMMON_FIELDS_KEY, commonFields);
			paramObj = paramMap;

		} else if (paramObj instanceof Map) {
			// 原参数为Map,且Map的key中没有commonFields,添加到参数Map中
			((Map) paramObj).putIfAbsent(COMMON_FIELDS_KEY, commonFields);

		} else {
			// 参数是Bean,反射设置值
			PropertyDescriptor ps = BeanUtils.getPropertyDescriptor(paramObj.getClass(), COMMON_FIELDS_KEY);
			if (ps != null && ps.getReadMethod() != null && ps.getWriteMethod() != null) {
				Object value = ps.getReadMethod().invoke(paramObj);
				if (value == null) {
					ps.getWriteMethod().invoke(paramObj, commonFields);
				}
			}
		}

		return paramObj;
	}

	/**
	 * 提取公共字段信息
	 * @return 公共字段信息
	 */
	private Map<String, Object> buildCommonFields() {
		Map<String, Object> commonFieldsMap = new HashMap<>();
		String action = null;
		Long userId = null;
		String termIp = "127.0.0.1";
		Long mchId = null;

		// web环境下
		RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
		if (requestAttributes != null) {
			// 提取请求来源、终端IP
			HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
			action = request.getRequestURI().toString();
			termIp = request.getRemoteHost();
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(String.format("客户端IP地址:%s, 客户端请求URI:%s", termIp, action));
			}

			// 提取来访用户信息
			HttpSession session = request.getSession();
			ILoginDto loginInfo = (ILoginDto) session.getAttribute(SessionKey.LOGIN_INFO);
			if (loginInfo != null) {	// 已登录
				userId = loginInfo.getUserId();
				mchId = loginInfo.getMchId();
			}
		}
		
		commonFieldsMap.put("action", action);					// 来访URL
		commonFieldsMap.put("userId", userId);					// 会话用户ID
		commonFieldsMap.put("termIp", termIp);					// 来访终端IP
		commonFieldsMap.put("dt", System.currentTimeMillis());	// 当前时间
		commonFieldsMap.put("mchId", mchId);					// 会话商户ID
		return commonFieldsMap;
	}

}

PageHelper分页就是通过mybatis的拓展点(拦截器)做的分页,通过拦截器还可以做数据权限的控制……

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;