Mybatis攔截器
可攔截的目標對象有四個(前面是可被攔截的對象,後面括號中是對象中可被攔截的方法)
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
- ResultSetHandler (handleResultSets, handleOutputParameters)
場景
數據庫中每一張表都有如下6個通用字段來記錄表的操作日誌:
- 創建時間:createTime
- 創建人id:createById
- 創建人姓名:createBy
- 更新時間:updateTime
- 更新人id:updateById
- 更新人姓名:updateBy
在執行insert操作時,需要設置這6個字段的值,執行update操作時只需要更新後3個字段的值,在業務代碼中對這六個字段進行設值肯定是可以實現的,但是會導致代碼重複,而且容易漏寫,非常不方便,如果用到Mybatis則可以通過Mybatis攔截器來實現。
實現步驟
- 實現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;
}
}
- 配置mybatis攔截器
攔截器是以插件的形式配置的,mybatis配置文件增加如下plugin即可
<plugins>
<plugin interceptor="com.xxx.OperateRecordInterceptor"></plugin>
</plugins>