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