使用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日

相关文章

  • Windows系统安装Redis的详细图文教程

    下面是Windows系统安装Redis的详细步骤。 确认系统环境 在开始安装Redis前,需要先确认自己的系统是否支持Redis,同时需要确认自己已经安装了Visual C++ 2015 redistributable package,这是Redis运行所必须的前置条件。 下载Redis 从Redis官网的下载页面中,选择最新的稳定版本下载,这里以redis…

    database 2023年5月22日
    00
  • Redis Eval Script

    简介 从Redis 2.6 版本开始,内嵌支持 Lua 环境。通过使用EVAL或EVALSHA命令可以使用 Lua 解释器来执行脚本。 EVAL和EVALSHA的使用是差不多的(下面有讲区别)。 EVAL命令 语法: EVAL script numkeys key [key …] arg [arg …] 。 script:Lua脚本 。numkeys…

    Redis 2023年4月13日
    00
  • 详解springboot+atomikos+druid 数据库连接失效分析

    下面是详解“详解springboot+atomikos+druid数据库连接失效分析”的完整攻略。 1. 背景 在使用SpringBoot、Atomikos、Druid等技术栈进行开发时,有可能会遇到数据库连接失效的问题,导致应用程序无法连接数据库,这将会对应用的正常运行造成很大的影响。本文将介绍针对这个问题的解决方案和攻略。 2. 问题分析 当Spring…

    database 2023年5月18日
    00
  • SQL Server中with as使用介绍

    SQL Server中的WITH AS语法是一种数据查询语言中常用的功能,在操作大量数据时非常便捷,本文将对其进行详细介绍。 一、什么是WITH AS语法 WITH AS是SQL Server中的常用查询语句,其作用是先创建一个临时的数据结果集,然后再对这个结果集进行操作。其基本的语法格式如下: WITH CTEName AS( — SELECT stat…

    database 2023年5月21日
    00
  • mysql高级、索引

    1.视图 # 引子 select * from emp left join dep on emp.dep_id = dep.id union select * from emp right join dep on emp.dep_id = dep.id; create view temp(emp_id,emp_name,salary,dep_id,dep_i…

    MySQL 2023年4月12日
    00
  • 如何在Python中插入MySQL数据库中的数据?

    以下是在Python中插入MySQL数据库中的数据的完整使用攻略。 使用MySQL数据库的前提条件 在使用Python连接MySQL数据库之前,确保已经安装了MySQL数据库,并且已经创建了使用的数据库和表。同时,还需要安装Python的驱动程序,例如mysql-connector-python。 步骤1:导入模块 在Python中,使用mysql.conn…

    python 2023年5月12日
    00
  • redis基本安装判断、启动使用方法示例

    下面是关于Redis基本安装、判断、启动和使用的攻略: Redis基本安装 下载Redis官方源码文件(官网下载地址),解压到目标文件夹位置。 在解压目录中打开终端,使用以下命令执行编译:make 编译完成后,使用以下命令执行安装:make install Redis安装完成后,可以使用以下命令检查Redis是否安装成功:redis-server –ver…

    database 2023年5月22日
    00
  • MySQL百万级数据量分页查询方法及其优化建议

    MySQL百万级数据量分页查询方法及其优化建议 分页查询是常用的数据库操作之一,但当数据量达到百万级以上时,如何优化分页查询成为了开发者们面临的难题。本文将详细讲解如何处理MySQL百万级数据量的分页查询,以及相关的优化建议。 传统分页查询方法 传统的分页查询方法通常是使用LIMIT和OFFSET来控制返回结果的数量和排列顺序,例如: SELECT * FR…

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