利用动态代理模式实现对Mybatis的DDL操作的监控

2024-07-27 16:39:51 浏览数 (2)

现在有这样的需求:为了追踪数据的动向,需要对业务通过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重载方法进行兼容,以提高扩展性。

0 人点赞