使用SpringAop动态获取mapper执行的SQL,并保存SQL到Log表中

使用Spring AOP动态获取mapper执行的SQL并保存到Log表中,可以方便我们在程序调试和优化时快速定位问题,本攻略分为以下步骤:

步骤一:添加依赖

首先,在项目的pom.xml中添加以下依赖:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>

这里我们使用了AspectJ框架动态代理实现了AOP功能。

步骤二:定义日志实体类

在log表中要保存的信息比如SQL语句、方法名、执行时间等可以定义为一个Log实体类。如下:

public class Log {
    //id
    private Long id;
    //操作时间
    private Date operTime;
    //操作人
    private String operUser;
    //执行的方法名
    private String methodName;
    //执行的SQL语句
    private String sql;
    //执行时间
    private Long time;

    // 省略 getter 和 setter
}

步骤三:定义AOP切面

AOP切面主要是通过@Aspect注解来标记切面类,并且在类中定义对mapper接口中的每一个方法进行拦截的Advice。

@Component
@Aspect
public class LogAspect {
    @Autowired
    private LogDao logDao;

    /**
     * 切入点
     */
    @Pointcut("execution(* com.example.mapper.*.*(..))")
    public void logPointCut() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint 切入点
     */
    @Before("logPointCut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 获取方法名
        String methodName=joinPoint.getSignature().getName();
        // 获取目标类名
        String targetName=joinPoint.getTarget().getClass().getSimpleName();

        // 获取目标方法参数
        Object[] params=joinPoint.getArgs();

        // 反射获取mapper接口对应的MapperProxy类对象
        MapperProxy<?> mapperProxy=(MapperProxy<?>) ((DefaultSqlSession) sqlSession).getMapperProxy();

        // 反射获取MapperProxy类的h字段,即使用了JDK动态代理的接口实现对象
        Field hField=Proxy.class.getDeclaredField("h");
        hField.setAccessible(true);
        Object obj=hField.get(mapperProxy);

        // 反射获取委托类对象
        Field mapperInterfaceField=obj.getClass().getDeclaredField("mapperInterface");
        mapperInterfaceField.setAccessible(true);
        Class<?> mapperInterface=(Class<?>) mapperInterfaceField.get(obj);
        String mapperClassName=mapperInterface.getName();

        Method mapperMethod=getMapperMethod(mapperInterface, methodName,params);// 利用反射获取Mapper接口中的方法

        // 获取操作的SQL语句
        MappedStatement mappedStatement=getMappedStatement(sqlSession, mapperInterface, mapperMethod);
        Object parameterObject=getParameterObject(mappedStatement, params);
        BoundSql boundSql=mappedStatement.getBoundSql(parameterObject);
        String sql=boundSql.getSql();

        String operUser="";//这里可以根据实际情况获取操作人
        Log log=new Log();
        log.setOperTime(new Date());
        log.setOperUser(operUser);
        log.setMethodName(mapperClassName + "." + methodName);
        log.setSql(sql);
        log.setTime(0L);//这里可以记录查询时间

        logDao.save(log);// 保存日志到log表
    }

    private Method getMapperMethod(Class<?> mapperInterface, String methodName,Object[] params) {
        Method method=null;
        for (Method m : mapperInterface.getMethods()) {
            // 找到方法名和参数个数都匹配的方法
            if (m.getName().equals(methodName)&& m.getParameterCount() == params.length) {
                method=m;
                break;
            }
        }
        return method;
    }

    /**
     * 获取MappedStatement对象
     *
     * @param sqlSession
     * @param mapperInterface
     * @param mapperMethod
     * @return
     */
    private MappedStatement getMappedStatement(SqlSession sqlSession,Class<?> mapperInterface, Method mapperMethod) {
        String id=mapperInterface.getName() + "." + mapperMethod.getName();
        Configuration configuration=sqlSession.getConfiguration();
        MappedStatement mappedStatement=configuration.getMappedStatement(id);
        return mappedStatement;
    }

    /**
     * 获取MappedStatement的参数对象
     *
     * @param mappedStatement
     * @param params
     * @return
     */
    private Object getParameterObject(MappedStatement mappedStatement,Object[] params) {
        Object parameterObject=null;
        if (mappedStatement.getParameterMap() != null) {
            parameterObject=mappedStatement.getConfiguration().newParameterObject(mappedStatement.getParameterMap(), params);
        } else if (params != null && params.length > 1) {
            StringBuilder sb=new StringBuilder();
            for (Object obj : params) {
                sb.append(obj);
            }
            parameterObject=sb.toString();
        } else if (params != null && params.length == 1) {
            parameterObject=params[0];
        }
        return parameterObject;
    }
}

在上述代码中,注入了LogDao实例到当前类中。在doBefore方法中获取方法名、目标类名、代理对象等信息,通过反射获取对应的MapperProxy对象,最后获取到执行的SQL语句,并保存到log表中。

步骤四:配置Logger

我们可以使用log4j或logback等日志框架将SQL语句输出到日志文件中。例如在logback.xml中加如下配置:

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
</appender>

<logger name="com.example.mapper" additivity="false" level="DEBUG">
    <appender-ref ref="STDOUT" />
</logger>

这样就可以在程序控制台或其他日志格式文件中看到每次执行SQL操作时的日志信息了。

示例

这里给出两个例子,测试数据基于Spring Boot和MyBatis环境:

示例一

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;

@Test
public void test() {
    List<User> userList=userService.getUserList(2L, "Tom");

    User user=userService.getUserById(2L);

    System.out.println(user);
}
}

执行上述代码后,会在控制台和日志文件中打印出以下信息:

2021-08-31 11:12:13.889 [main] DEBUG c.e.m.UserMapper.selectByParam - ==>  Preparing: SELECT * FROM t_user WHERE id = ?
2021-08-31 11:12:13.896 [main] DEBUG c.e.m.UserMapper.selectByParam - ==> Parameters: 2(Long)
2021-08-31 11:12:13.898 [main] DEBUG c.e.m.UserMapper.selectByParam - <==      Total: 1
User(id=2, name=Tom, age=20, sex=male, createDate=Tue Aug 24 10:52:17 CST 2021, updateDate=)

示例二

@SpringBootTest
@RunWith(SpringRunner.class)
public class TestOrderService {
@Autowired
private OrderService orderService;

@Test
public void test() {
    List<Order> orderList=orderService.getOrderList(2L, "2021-08-30");

    Order order=orderService.getOrderById(2L);

    System.out.println(order);
}
}

执行上述代码后,会在控制台和日志文件中打印出以下信息:

2021-08-31 11:16:47.166 [main] DEBUG c.e.m.OrderMapper.queryOrderList - ==>  Preparing: SELECT a.id, a.user_id, a.price, a.create_time, a.update_time, b.name, b.age, b.sex FROM t_order a, t_user b WHERE a.user_id = b.id AND a.id = ?
2021-08-31 11:16:47.169 [main] DEBUG c.e.m.OrderMapper.queryOrderList - ==> Parameters: 2(Long)
2021-08-31 11:16:47.178 [main] DEBUG com.example.mapper.LogAspect - 保存日志到log表:Log(id=1, operTime=Tue Aug 31 11:16:47 CST 2021, operUser=, methodName=com.example.mapper.OrderMapper.queryOrderList, sql=SELECT a.id, a.user_id, a.price, a.create_time, a.update_time, b.name, b.age, b.sex FROM t_order a, t_user b WHERE a.user_id = b.id AND a.id = ?, time=0)
Order(id=2, userId=2, price=30.0, createTime=Mon Aug 30 10:52:17 CST 2021, updateTime=)

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:使用SpringAop动态获取mapper执行的SQL,并保存SQL到Log表中 - Python技术站

(0)
上一篇 2023年5月21日
下一篇 2023年5月21日

相关文章

  • Mysql语法、特殊符号及正则表达式的使用详解

    Mysql语法、特殊符号及正则表达式的使用详解 Mysql语法 Mysql是一款常用的关系型数据库管理系统,它支持多种查询和操作语句。以下是一些常用的语法: 数据库操作语句 创建数据库 CREATE DATABASE database_name; 删除数据库 DROP DATABASE database_name; 使用数据库 USE database_na…

    database 2023年5月21日
    00
  • 简单实现linux聊天室程序

    实现一个Linux聊天室程序的过程可以分为以下步骤: 确定聊天室的基本架构:服务器端和客户端。服务器端用于管理多个客户端的连接和消息传递。客户端则负责连接服务器、发送和接收消息。 使用Socket API实现网络连接功能。在服务器端和客户端中均需用到Socket API来创建和管理网络连接。 设计通信协议,要求在协议中包含一些关键字段,如消息类型、发送者、接…

    database 2023年5月22日
    00
  • php,redis分布式锁防并发

        解决死锁   如果只用SETNX命令设置锁的话,如果当持有锁的进程崩溃或删除锁失败时,其他进程将无法获取到锁,问题就大了。 解决方法是在获取锁失败的同时获取锁的值,并将值与当前时间进行对比,如果值小于当前时间说明锁以过期失效,进程可运用Redis的DEL命令删除该锁。 setnx的作用和memcache的add方法类似 class rediss { …

    Redis 2023年4月11日
    00
  • Redis中AOF与RDB持久化策略深入分析

    本篇文章将详细讲解Redis中AOF与RDB持久化策略的深入分析,主要包括以下内容: 什么是Redis持久化? Redis的两种持久化策略 AOF持久化 RDB持久化 两种持久化策略的比较 示例说明 总结 什么是Redis持久化? Redis是一种内存数据库,数据存储在内存中,当Redis重启或崩溃时,数据将会丢失。因此,为了在Redis发生故障时能够保留数…

    database 2023年5月22日
    00
  • 网易社招面试流程与经验总结【纯干货分享】

    我们来详细讲解一下关于“网易社招面试流程与经验总结【纯干货分享】”的完整攻略。 网易社招面试流程 在介绍攻略之前,先来了解一下网易社招的面试流程。网易社招一般分为以下几个环节: 投递简历 首先,你需要在网易招聘网站投递你的简历。如果符合要求,HR 会与你电话联系安排下一步面试。 初试 初试一般为电话面试,主要考察基本的职业素养、技能水平及工作经验等情况。 复…

    database 2023年5月22日
    00
  • 创建动态MSSQL数据库表

    创建动态MSSQL数据库表的完整攻略如下: 1. 创建表 创建表需要用到以下SQL代码: CREATE TABLE {表名} ({列名1} {数据类型1}, {列名2} {数据类型2}, …); 其中,花括号内的内容需要替换成实际的表名、列名和对应数据类型。例如,创建一个名为”students”的表,包含”id”(整数类型)、”name”(字符串类型)和…

    database 2023年5月21日
    00
  • nodejs基础知识

    Node.js基础知识攻略 什么是Node.js? Node.js是一个基于Chrome V8引擎的JavaScript运行时,使用它可以轻松构建高性能的网络应用程序。Node.js使用单线程,非阻塞I/O模型,能够处理大量并发连接以及I/O操作。 Node.js的安装 在开始使用Node.js之前,需要首先对它进行安装。安装过程中,需要注意操作系统的版本不…

    database 2023年5月22日
    00
  • MySQL带你秒懂索引下推

    MySQL带你秒懂索引下推攻略 索引下推简介 索引下推是MySQL 5.6版本新增的特性,是MySQL优化查询速度的一种手段。它的基本原理是在执行SQL语句时,尽可能地利用索引来提高查询效率,减少全表扫描的需要。 索引下推的作用 索引下推可以减少MySQL查询语句所需要的IO开销和CPU开销。其实现原理是让MySQL尽可能地使用索引,避免对表所有的数据进行扫…

    database 2023年5月22日
    00
合作推广
合作推广
分享本页
返回顶部