MyBatis Executor Interceptor Implementation Guide

Bảng nội dung

Giới thiệu

Giai đoạn thực thi của interceptor

Cấu hình interceptor

Interceptor Executor

Chỉ chặn các thao tác insert và update

Chỉ xử lý các bảng cụ thể

Ví dụ mã hoàn chỉnh

Giới thiệu

Bài viết trước chúng ta đã tìm hiểu về việc sử dụng interceptor ParameterHandler trong MyBatis. Tuy nhiên, trong quá trình thực thi của interceptor này, đối tượng ngữ cảnh Invocation invocation có rất ít thông tin có thể lấy được. Nếu chúng ta muốn kiểm soát tốt hơn quy trình và logic chặn như thực hiện thao tác trên bảng chỉ định và bỏ qua các bảng khác, chúng ta cần sử dụng interceptor Executor mạnh mẽ hơn.

Giai đoạn thực thi của interceptor

Quy trình thực thi MyBatis:

Tải file cấu hình (Configuration)

Tạo SqlSessionFactory

Lấy SqlSession

Lấy đối tượng proxy Mapper

Thực hiện thao tác cơ sở dữ liệu

├── Interceptor Executor

├── Interceptor ParameterHandler

├── Interceptor StatementHandler

└── Interceptor ResultSetHandler

Cấu hình interceptor

Xin vui lòng tham khảo tài liệu liên quan để biết cách cấu hình cơ bản.

Interceptor Executor

Interceptor Executor là một cơ chế chặn can thiệp sớm nhất trong giai đoạn thực thi truy vấn SQL, chủ yếu được sử dụng để giám sát, sửa đổi, kiểm soát toàn bộ quá trình thực thi truy vấn. Logic triển khai như sau.

Chỉ chặn các thao tác insert và update

Interceptor chặn toàn cục, tất cả các câu lệnh SQL đều đi qua, @Signature được cấu hình để chặn theo phương thức update, thực tế sẽ chặn cả insert, update, delete.

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "update", 
                args = {MappedStatement.class, Object.class}
        )
})

Nhưng chương trình không cần chặn thao tác delete, nên có thể lọc thêm bằng mã nguồn:

@Override
public Object intercept(Invocation invocation) throws Throwable {
    try {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        
        SqlCommandType commandType = statement.getSqlCommandType();
        if (commandType != SqlCommandType.INSERT && commandType != SqlCommandType.UPDATE) {
            return invocation.proceed();
        }
        
        // Tiếp tục xử lý...
    } catch (Throwable throwable) {
        System.err.println("DatabaseOperationInterceptor#intercept error:" + throwable.getMessage());
        throw throwable;
    }
}

Chỉ xử lý các bảng tương ứng

@Override
public Object intercept(Invocation invocation) throws Throwable {
    try {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        
        SqlCommandType commandType = statement.getSqlCommandType();
        if (commandType != SqlCommandType.INSERT && commandType != SqlCommandType.UPDATE) {
            return invocation.proceed();
        }
        
        BoundSql boundSql = statement.getBoundSql(invocation.getArgs()[1]);
        String sqlText = boundSql.getSql().toLowerCase();
        String table = extractTableNameFromSQL(sqlText, statement.getSqlCommandType());
        
        if (table == null || table.isEmpty()) {
            System.err.println("DatabaseOperationInterceptor#intercept table name is null! sql=" + sqlText);
            return invocation.proceed();
        }
        
        if ("customer_info".equals(table)) {
            // Logic xử lý
        }
        return invocation.proceed();
    } catch (Throwable throwable) {
        System.err.println("DatabaseOperationInterceptor#intercept error:" + throwable.getMessage());
        throw throwable;
    }
}

Xác định bảng đang sử dụng dựa trên câu lệnh SQL hiện tại

/**
 * Phân tích tên bảng
 *
 * @param sqlText Câu lệnh SQL đang thực thi
 * @param commandType Loại SqlCommandType được ràng buộc
 */
private String extractTableNameFromSQL(String sqlText, SqlCommandType commandType) {
    Pattern pattern;
    switch (commandType) {
        case INSERT:
            pattern = Pattern.compile("insert\\s+into\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
            break;
        case UPDATE:
            pattern = Pattern.compile("update\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
            break;
        case DELETE:
            pattern = Pattern.compile("delete\\s+from\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
            break;
        case SELECT:
            pattern = Pattern.compile("from\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
            break;
        default:
            return null;
    }
    
    if (pattern == null) {
        throw new IllegalStateException("Pattern không được null!");
    }
    
    Matcher matcher = pattern.matcher(sqlText);
    if (matcher.find()) {
        return matcher.group(1).toLowerCase();
    }
    return null;
}

Ví dụ mã hoàn chỉnh

/**
 * Interceptor xử lý mã hóa dữ liệu nhạy cảm trước khi lưu vào MyBatis
 */
@Component
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "update", 
                args = {MappedStatement.class, Object.class}
        )
})
public class CustomerDataEncryptionInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
            
            SqlCommandType commandType = statement.getSqlCommandType();
            if (commandType != SqlCommandType.INSERT && commandType != SqlCommandType.UPDATE) {
                return invocation.proceed();
            }
            
            BoundSql boundSql = statement.getBoundSql(invocation.getArgs()[1]);
            String sqlText = boundSql.getSql().toLowerCase();
            String table = extractTableNameFromSQL(sqlText, statement.getSqlCommandType());
            
            if (table == null || table.isEmpty()) {
                System.err.println("CustomerDataEncryptionInterceptor#intercept table name is null! sql=" + sqlText);
                return invocation.proceed();
            }
            
            if ("customer_info".equals(table)) {
                Object paramValue = invocation.getArgs()[1];
                handleDataProtection(paramValue);
            }
            return invocation.proceed();
        } catch (Throwable throwable) {
            System.err.println("CustomerDataEncryptionInterceptor#intercept error:" + throwable.getMessage());
            throw throwable;
        }
    }

    /**
     * Phân tích tên bảng
     *
     * @param sqlText Câu lệnh SQL đang thực thi
     * @param commandType Loại SqlCommandType được ràng buộc
     */
    private String extractTableNameFromSQL(String sqlText, SqlCommandType commandType) {
        Pattern pattern;
        switch (commandType) {
            case INSERT:
                pattern = Pattern.compile("insert\\s+into\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
                break;
            case UPDATE:
                pattern = Pattern.compile("update\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
                break;
            case DELETE:
                pattern = Pattern.compile("delete\\s+from\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
                break;
            case SELECT:
                pattern = Pattern.compile("from\\s+(\\w+)", Pattern.CASE_INSENSITIVE);
                break;
            default:
                return null;
        }
        
        if (pattern == null) {
            throw new IllegalStateException("Pattern không được null!");
        }
        
        Matcher matcher = pattern.matcher(sqlText);
        if (matcher.find()) {
            return matcher.group(1).toLowerCase();
        }
        return null;
    }

    /**
     * Xử lý mã hóa tham số
     */
    private void handleDataProtection(Object paramValue) throws Exception {
        if (paramValue == null) return;

        if (paramValue instanceof Map) {
            handleMapParameters((Map) paramValue);
        } else if (paramValue instanceof Collection) {
            for (Object item : (Collection) paramValue) {
                processEntity(item);
            }
        } else if (paramValue.getClass().isArray()) {
            for (Object item : (Object[]) paramValue) {
                processEntity(item);
            }
        } else {
            processEntity(paramValue);
        }
    }

    /**
     * Xử lý tham số dạng Map
     */
    private void handleMapParameters(Map paramMap) throws Exception {
        for (Map.Entry entry : paramMap.entrySet()) {
            Object value = entry.getValue();
            if (value != null) {
                processEntity(value);
            }
        }
    }

    /**
     * Xử lý đối tượng đơn
     */
    private void processEntity(Object entity) throws Exception {
        if (entity == null) return;
        Class clazz = entity.getClass();
        for (Field field : clazz.getDeclaredFields()) {
            if (field.getName().equals("customerName")) {
                field.setAccessible(true);
                Object fieldValue = field.get(entity);
                if (fieldValue instanceof String originalValue) {
                    String encryptedValue = performEncryption(originalValue);
                    field.set(entity, encryptedValue);
                }
            }
        }
    }

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

    @Override
    public void setProperties(Properties properties) {
        // Có thể đọc thuộc tính từ file cấu hình
    }

    /**
     * Lấy chuỗi đã mã hóa
     *
     * @param input Tên khách hàng
     * @return String Giá trị sau khi mã hóa, có tiền tố Encrypted:
     */
    private String performEncryption(String input) {
        // Triển khai logic mã hóa
        return "ENCRYPTED:" + input.hashCode();
    }
}

Thẻ: mybatis Interceptor executor Java Database

Đăng vào ngày 3 tháng 6 lúc 22:04