使用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技术站