Mybatis拦截器结合ThreadLocal实现数据库updateTime等操作字段的更新

Mybatis拦截器

可拦截的目标对象有四个(前面是可被拦截的对象,后面括号中是对象中可被拦截的方法)

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

场景

数据库中每一张表都有如下6个通用字段来记录表的操作日志:

  1. 创建时间:createTime
  2. 创建人id:createById
  3. 创建人姓名:createBy
  4. 更新时间:updateTime
  5. 更新人id:updateById
  6. 更新人姓名:updateBy

在执行insert操作时,需要设置这6个字段的值,执行update操作时只需要更新后3个字段的值,在业务代码中对这六个字段进行设值肯定是可以实现的,但是会导致代码重复,而且容易漏写,非常不方便,如果用到Mybatis则可以通过Mybatis拦截器来实现。



实现步骤

  1. 实现Interceptor接口
    org.apache.ibatis.plugin.Interceptor
    拦截器上的@Intercepts注解用于配置需要拦截的对象和方法,
    这里我们通过拦截 Executor 的 update 方法来实现数据表的这六个字段的更新。
    //Intercepts设置拦截的目标对象为 Executor的update方法  
    @Intercepts({
    		@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
    })
    public class OperateRecordInterceptor implements Interceptor {
    	private static final Logger log = LoggerFactory.getLogger(OperateRecordInterceptor.class);
    
    	/**
    	 * 执行更新的六个字段名
    	 */
    	public static String FIELD_CREATE_TIME="createTime";
    	public static String FIELD_UPDATE_TIME="updateTime";
    	public static String FIELD_CREATE_ID="createById";
    	public static String FIELD_CREATE_NAME="createBy";
    	public static String FIELD_UPDATE_ID="updateById";
    	public static String FIELD_UPDATE_NAME="updateBy";
    
    	@Override
    	public Object intercept(Invocation invocation) throws Throwable {
    		try {
    		    //获取当前操作的用户
    			Operator operator=OperatorHolder.getOperator();
    			
    			//第一个参数为MappedStatement, Executor的update方法的内置参数
    			MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
    			//第二个参数才是我们需要的处理的数据库Entity对象,比如通过updateByExample(parameter)中的参数    
    			Object parameter = invocation.getArgs()[1];
    			//得到sql类型
    			SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
    
    			Date date=new Date();
    			//SQL插入动作
    			if (SqlCommandType.INSERT.equals(sqlCommandType)) {
    				//插入数据库时设置六个字段
    				setOperateTime(parameter, FIELD_CREATE_TIME,date);
    				setOperateId(parameter,FIELD_CREATE_ID,operator);
    				setOperateBy(parameter,FIELD_CREATE_NAME,operator);
    				setOperateTime(parameter, FIELD_UPDATE_TIME,date);
    				setOperateId(parameter,FIELD_UPDATE_ID,operator);
    				setOperateBy(parameter,FIELD_UPDATE_NAME,operator);
    			}
    			//SQL更新动作
    			else if (SqlCommandType.UPDATE.equals(sqlCommandType)) {
    				//更新数据库时设置后三个字段
    				setOperateTime(parameter, FIELD_UPDATE_TIME,date);
    				setOperateId(parameter,FIELD_UPDATE_ID,operator);
    				setOperateBy(parameter,FIELD_UPDATE_NAME,operator);
    			}
    
    		} catch (Exception e) {
    		    //这里需要捕获异常,为了保证主流程不会异常阻断  
    			log.error("Mybatis OperateRecordInterceptor 拦截器异常",e);
    		}
    		
    		//继续执行该执行的方法  
    		return invocation.proceed();
    	}
    
    	private void setOperateTime(Object parameter, String operateTimeFieldName,Date date) throws IllegalAccessException {
    		Field field = getField(parameter, operateTimeFieldName);
    		setFieldValue(field,parameter,date);
    	}
    
    	private void setOperateId(Object parameter, String operateFieldName, Operator operator) throws IllegalAccessException {
    		Field field = getField(parameter, operateFieldName);
    		Object value=operator!=null ? operator.getOperatorId() : null;
    		setFieldValue(field,parameter,value);
    	}
    
    	private void setOperateBy(Object parameter, String operateFieldName, Operator operator) throws IllegalAccessException {
    		Field field = getField(parameter, operateFieldName);
    		Object value=operator!=null ? operator.getOperatorName() : null;
    		setFieldValue(field, parameter,value);
    	}
    
    	private void setFieldValue(Field field,Object obj,Object value) throws IllegalAccessException {
    		if(field!=null){
    			field.setAccessible(true);
    			field.set(obj, value);
    		}
    	}
    
    	private Field getField(Object object, String fieldName){
    		Field field= null;
    		try {
    			field = object.getClass().getDeclaredField(fieldName);
    		} catch (NoSuchFieldException e) {
    			log.error("从[{}]中获取[{}]字段发生异常:{}:{}",object.getClass().getName(),fieldName,e.getClass().getName(),e.getMessage());
    		}
    		return field;
    	}
    
    	@Override
    	public Object plugin(Object target) {
    		return Plugin.wrap(target, this);
    	}
    
    	@Override
    	public void setProperties(Properties properties) {
    
    	}
    }
    

OperatorHolder用于获取当前操作的用户,比如可以通过SpringSecurity往里面设置当前登录的用户信息,
通过ThreadLocal实现 。
关于ThreadLocal使用需注意的问题详见:
java中的ThreadLocal
ThreadLocal在线程池中被串用

public class OperatorHolder {

	private static final ThreadLocal<Operator> operatorThreadLocal = new ThreadLocal();

    //调用此方法在SpringSecurity中设置当前登录的用户信息    
	public static void setOperator(Integer operatorId,String operatorName){
		operatorThreadLocal.set(new Operator(operatorId,operatorName));
	}

	public static Operator getOperator() {
		return operatorThreadLocal.get();
	}

	public 	static void clearOperator() {
		operatorThreadLocal.remove();
	}

}

Operator为操作员对象

public class Operator {
    //操作人的id
	private Integer operatorId;
	//操作人姓名
	private String operatorName;

	public Operator(Integer operatorId, String operatorName) {
		this.operatorId = operatorId;
		this.operatorName = operatorName;
	}

	public Integer getOperatorId() {
		return operatorId;
	}

	public void setOperatorId(Integer operatorId) {
		this.operatorId = operatorId;
	}

	public String getOperatorName() {
		return operatorName;
	}

	public void setOperatorName(String operatorName) {
		this.operatorName = operatorName;
	}
}
  1. 配置mybatis拦截器
    拦截器是以插件的形式配置的,mybatis配置文件增加如下plugin即可
<plugins>
    <plugin interceptor="com.xxx.OperateRecordInterceptor"></plugin>    
</plugins>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章