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

yizhihongxing

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

相关文章

  • 各种路由器的默认密码

    路由器是连接你的设备和互联网的交点,每个路由器都会有一个管理界面,需要输入账号和密码才能登录管理。然而,很多用户未曾更改默认的账号和密码就直接使用,这样会给黑客留下可乘之机。以下是关于各种路由器默认密码的攻略,希望对大家有所帮助。 一、如何找到路由器默认密码 查找路由器的默认用户名和密码通常可以在路由器的文档中找到,或者通过到路由器制造商的网站搜索找到。各大…

    database 2023年5月22日
    00
  • linux AS3 oracle9i 安装指南

    Linux AS3 Oracle 9i 安装指南 本文旨在提供 Linux AS3 操作系统上 Oracle 9i 数据库的安装过程,并提供两个安装示例说明。 系统要求 Linux AS3 操作系统 256MB 内存及以上 2GB 或以上磁盘空间 安装前准备工作 在进行 Oracle 9i 数据库的安装之前,需要完成以下准备工作: 安装必要的软件包 使用以下…

    database 2023年5月22日
    00
  • Mysql中时间戳转为Date的方法示例

    Mysql中存储时间戳和日期时间类型的数据,不同的数据类型在不同的场景下有不同的用途。如果需要将存储的时间戳转换为日期格式,可以利用Mysql中的日期函数来完成,下面就是将此完成的方法的详细攻略。 一、时间戳转化为日期格式的函数 MySQL提供了from_unixtime函数和date_format函数来进行时间戳的转化。前者可以将UNIX时间戳转化为标准的…

    database 2023年5月22日
    00
  • DDoS攻击原理是什么?DDoS攻击原理及防护措施介绍

    DDoS攻击原理是什么? DDoS攻击,全称分布式拒绝服务攻击(Distributed Denial of Service),是一种利用多台计算机对某个特定的服务器发起攻击,使该服务器无法正常工作的网络攻击行为。DDoS攻击原理是对目标服务器进行大量的流量攻击,使服务器无法处理合法请求,导致正常用户无法访问网站,从而达到攻击者的目的。 DDoS攻击通过网络上…

    database 2023年5月21日
    00
  • MongoDB 中聚合统计计算–$SUM表达式

    下面就MongoDB中聚合统计计算中的$SUM表达式进行详细讲解。 什么是$SUM表达式? $SUM表达式是MongoDB中聚合管道阶段操作符之一,用于对某个字段进行求和操作,通常在$group阶段中使用。 $SUM的语法格式 $sum表达式的基本语法格式如下: $sum: <expression> 其中,<expression>代表…

    database 2023年5月21日
    00
  • centos7 安装mysql5.7(源码安装)

    Centos7将默认数据库mysql替换成了Mariadb 在接下来的mysql安装过程中,请一定保证自己当前所在目录是正确的!  e g: [root@localhost ~]# 表示当前目录为~ [root@localhost mysql]# 表示当前目录为mysql 一、安装MySQL 1、下载安装包mysql-5.7.17-linux-glibc2.…

    MySQL 2023年4月13日
    00
  • MySQL timestamp自动更新时间分享

    当我们需要在MySQL中自动更新一个表的最后修改时间,可以使用timestamp数据类型,它会在表中每次更新记录时自动更新为当前时间。下面是介绍如何使用MySQL的timestamp类型自动更新时间的方法: 1. 创建带有timestamp的表 在创建表时,可以使用类似下面的语句创建一个带有timestamp类型的列: CREATE TABLE my_tab…

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

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

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