如何使用SpringAop动态获取mapper执行的SQL并保存SQL到Log表中
更新时间:2023-10-30什么是Spring AOP?
Spring AOP (Aspect-Oriented Programming)是Spring Framework中的一个重要特性,它是基于AOP思想实现的。它提供了一种使应用程序代码横向切割对应关注点的方式,主要用于统一处理应用程序中的横切关注点(Crosscutting Concerns)。
Spring AOP采用代理模式实现对JavaBean的增强,可以在程序员定义的位置(连接点)织入增强代码(advise),而不需要代码动态修改。
// 示例一:定义一个切面 public aspect LogAspect{ //切点,可以根据需要设置不同的切点 pointcut executeDao(): execution(* com.example.*.mapper.*.*(..)); //advice,定义在切点执行前后需要增强的操作,例如打印SQL语句 before():executeDao(){ Signature sig = thisJoinPointStaticPart.getSignature(); String className = sig.getDeclaringType().getName(); String methodName = sig.getName(); String sql = ""; Object[] args = thisJoinPoint.getArgs(); if(args != null && args.length>0 && args[0] instanceof org.apache.ibatis.mapping.BoundSql){ org.apache.ibatis.mapping.BoundSql boundSql = (org.apache.ibatis.mapping.BoundSql)args[0]; sql = boundSql.getSql(); System.out.println("[LogAspect] "+className+"."+methodName+"() method SQL:"+sql); //此处可以将SQL插入到数据库或者日志文件中 } } }
什么是Mybatis?
Mybatis是一款优秀的开源持久层框架,它支持定制化SQL、存储过程以及高级映射。Mybatis避免了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装,使得开发人员可以专注于SQL的开发本身而不是对象的映射。
对于Mybatis框架,主要的配置文件是mybatis-config.xml,映射文件一般是以Mapper.xml结尾。
如何在Mybatis加载插件?
Mybatis提供了插件机制,在其中可以通过自定义Interceptor拦截StatementHandler、ParameterHandler、ResultSetHandler、Executor等对象的方法调用。
// 示例三:定义一个Mybatis插件 @Intercepts({ @Signature( type=StatementHandler.class, method="prepare", args={Connection.class, Integer.class} ) }) public class MybatisPlugin implements Interceptor{ @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler)invocation.getTarget(); MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler); //检索到日志的方式与示例一类似,不再重复 return invocation.proceed(); } //在Mybatis配置文件中配置插件}
如何整合上述功能?
我们可以结合示例一和示例三实现保存SQL语句的功能:
// 示例四:整合Spring AOP和Mybatis插件 @Aspect @Component public class LogAspect { @Autowired private SqlSessionTemplate sqlSessionTemplate; @Pointcut("execution(* com.example.*.mapper.*.*(..))") public void executeDao(){} @Before("executeDao()") public void doBefore(JoinPoint joinPoint){ Signature signature = joinPoint.getSignature(); String className = signature.getDeclaringType().getSimpleName(); String methodName = signature.getName(); Object[] args = joinPoint.getArgs(); if(args != null && args.length>0 && args[0] instanceof org.apache.ibatis.mapping.BoundSql){ org.apache.ibatis.mapping.BoundSql boundSql = (org.apache.ibatis.mapping.BoundSql)args[0]; String sql = boundSql.getSql(); String mapperName = className + "." + methodName; String sessionId = SqlSessionUtils.getSqlSession(sqlSessionTemplate.getSqlSessionFactory(), sqlSessionTemplate.getExecutorType(), sqlSessionTemplate.getPersistenceExceptionTranslator()).toString(); //将SQL插入到数据库表中 sqlSessionTemplate.insert("com.example.mapper.LogMapper.insertLog", new Log(sessionId, mapperName, sql)); } } } //Log类定义 public class Log { private String sessionId; //每次sqlSession的唯一标识 private String mapperName; //Mapper的完整类名和方法名 private String sql; //mapper执行的SQL语句 public Log(String sessionId, String mapperName, String sql) { this.sessionId = sessionId; this.mapperName = mapperName; this.sql = sql; } //getter and setter... } //log表的DDL CREATE TABLE log ( id bigint(20) unsigned NOT NULL AUTO_INCREMENT, session_id varchar(64) NOT NULL COMMENT '每次sqlSession的唯一标识', mapper_name varchar(256) NOT NULL COMMENT 'Mapper的完整类名和方法名', sql_statement varchar(1024) NOT NULL COMMENT 'mapper执行的SQL语句', create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', PRIMARY KEY (id), INDEX idx_create_time (create_time) USING BTREE COMMENT '根据时间戳为日志表建立索引' ) COMMENT='系统SQL执行日志表';
总结
在本文中,我们介绍了如何使用Spring AOP和Mybatis插件实现动态获取mapper执行的SQL并保存SQL到Log表中。通过定义切面和插件,可以实现对Mybatis进行拓展和增强,达到日志记录的目的。需要注意的是,日志记录应该避免计算敏感信息(例如用户密码等),同时应该加强数据安全(例如使用加密算法加密敏感信息)。