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();
}
}