现在有这样的需求:为了追踪数据的动向,需要对业务通过Mybatis对数据库的DDL操作进行监控并记入相应的日志。
1. 引言
在企业应用中,数据库是关键的组件,其操作的监控和管理至关重要。DDL操作,包括insert、update、delete等,能直接影响数据库的数据结构和数据内容。MyBatis作为Java中常用的持久层框架,提供了一系列灵活的操作数据库的方法。然而,对这些方法的监控通常需要额外的工作。动态代理模式提供了一种在运行时动态创建代理对象的能力,这为实现对MyBatis DDL操作的监控提供了可能。
2. 动态代理模式
动态代理是一种设计模式,它允许在运行时动态地生成一个实现了特定接口的代理类。在Java中,`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口被用来实现动态代理。通过动态代理,可以在不修改原有业务逻辑的情况下,增加额外的处理逻辑。
3. 系统设计
3.1 组件设计
系统主要由以下几个组件构成:
- **SqlSessionFactoryAdaptor**:负责装配SqlSessionFactory,并提供一个openSession方法,用于创建代理的SqlSession。
- **ProxySqlSession**:SqlSession的代理类,实现了InvocationHandler接口,用于拦截MyBatis的操作并进行监控。
- **SqlHandler**:用于执行收集和记录MyBatis操作的日志处理类。
- **SqlAspect**:切面类,用于监听SqlSessionFactoryAdaptor的openSession方法调用。
3.2 动态代理实现
利用Java的动态代理机制结合Spring框架的AOP特性,可以在不修改MyBatis原始代码的基础上,实现对DDL操作的监控。
4. 核心实现
4.1 SqlSessionFactoryAdaptor
代码语言:txt复制@Component
public class SqlSessionFactoryAdaptor {
private final SqlSessionFactory sqlSessionFactory;
@Autowired
public SqlSessionFactoryAdaptor(SqlSessionFactory sqlSessionFactory) {
this.sqlSessionFactory = sqlSessionFactory;
}
public SqlSession openSession(Object obj) {
SqlSession sqlSession = sqlSessionFactory.openSession();
return (SqlSession) Proxy.newProxyInstance(
SqlSession.class.getClassLoader(),
new Class<?>[]{SqlSession.class},
new ProxySqlSession(sqlSession, obj)
);
}
}
4.2 ProxySqlSession
代码语言:txt复制public class ProxySqlSession implements InvocationHandler {
private final SqlSession sqlSession;
private final SqlHandler sqlHandler;
private final Object obj;
private final List<String> dealList = new ArrayList<>();
public ProxySqlSession(SqlSession sqlSession, Object obj) {
this.sqlSession = sqlSession;
this.obj = obj;
this.sqlHandler = new SqlHandler();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(sqlSession, args);
String methodName = method.getName();
switch (methodName) {
case "insert":
case "update":
case "delete":
collectDDLOperation(methodName, args);
break;
case "commit":
writeLogAndClearDealList();
break;
default:
break;
}
return result;
}
private void collectDDLOperation(String type, Object[] args) {
// 收集DDL操作信息
String operationInfo = "Operation: " type ", Args: " args[0];
dealList.add(operationInfo);
}
private void writeLogAndClearDealList() {
// 写入日志
sqlHandler.writeLog(dealList);
dealList.clear();
}
}
4.3 SqlHandler
代码语言:txt复制public class SqlHandler {
public void writeLog(List<String> operations) {
operations.forEach(operation -> {
// 实际应用中,这里可以写入数据库或日志文件
System.out.println(operation);
});
}
}
4.4 SqlAspect切面
代码语言:txt复制@Aspect
@Component
public class SqlAspect {
@Around("execution(* SqlSessionFactoryAdaptor.openSession(..))")
public Object aroundOpenSession(ProceedingJoinPoint joinPoint) throws Throwable {
Object obj = joinPoint.getArgs()[0];
return joinPoint.getTarget().openSession(obj);
}
}
5. 有关问题
1.为什么直接用切面对Mybatis的SqlSession监听是无效的?它不会进入到切面方法中来?
为了很好的控制事务的独立性、安全性和避免sqlSession的锁定,我们需要用sqlSessionFactory的openSession来动态的创建sqlSession对象,而不能用Mybatis-Spring提供的sqlSessionTemplate。那么这个动态创建的sqlSession就是运行时创建的对象,它不在持久代中。而Aspect的原理是编译时织入,其监控的对象只能是在编译时创建的持久代对象中,如SpringBoot自动装配时创建的Bean对象。所以切面是监控不到这些动态的sqlSession对象的,只能通过动态代理的方式对这些对象进行监控。
2.是否可以对此功能进行扩展?
可以进一步扩展。比如操作量比较多时且不急于使用代理过程的处理结果时,可以将处理结果集通过异步线程或消息队列等方式进行处理,以提高主程序的响应速率。还有对openSession方法的兼容,可以在代理方法中通过反射对Mybatis的所有openSession重载方法进行兼容,以提高扩展性。