在 Java 中如何添加 SQL 日志

1. 添加打印 SQL 的方式

打印 SQL 的方式有很多,比如有 idea 插件,有 mybatis 拦截器,有代理 datasource, 有代理 driver.

我比较认可的方式就是代理 driver. 这种无任何侵入性。

下面来介绍如何使用 p6spy Driver。

示例代码:

  • 使用 BeanPostProcessor 来动态扩展 bean
  • 判断 bean 是否为 DataSource 类型,并判断开发配置 dev.sql-log.enabled
  • 根据现有的 driver 配置来创建新的 datasource,并设置 url
  • 实际开启,还需要 spy.properties 配置文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Slf4j
@Configuration
public class DevDataSourceConfiguration implements BeanPostProcessor, EnvironmentAware {

	@Setter
	private Environment environment;

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if (bean instanceof DataSource && parseBoolean(environment.resolvePlaceholders("${dev.sql-log.enabled:false}"))) {
			log.info("已开启日志打印,将使用[P6SpyDriver]");
			if (environment.acceptsProfiles(Profiles.of("run"))) {
				log.warn("在生产环境一定要关闭配置[dev.sql-log.enabled]");
			}
			return proxyDataSource((DataSource) bean);
		}
		return bean;
	}

	public DataSource proxyDataSource(DataSource dataSource) {
		if (dataSource instanceof AbstractRoutingDataSource) {
			AbstractRoutingDataSource abstractRoutingDataSource = (AbstractRoutingDataSource) dataSource;

			// resolvedDataSources
			Field resolvedDataSourcesField = findField(AbstractRoutingDataSource.class, "resolvedDataSources");
			makeAccessible(resolvedDataSourcesField);

			@SuppressWarnings("unchecked")
			Map<Object, DataSource> resolvedDataSources = (Map<Object, DataSource>) getField(resolvedDataSourcesField, abstractRoutingDataSource);
			if (resolvedDataSources != null) {
				resolvedDataSources.forEach((k, v) -> resolvedDataSources.put(k, convertToProxyDataSource(v)));
			}

			// resolvedDefaultDataSource
			Field resolvedDefaultDataSourceField = findField(AbstractRoutingDataSource.class, "resolvedDefaultDataSource");
			makeAccessible(resolvedDefaultDataSourceField);

			DataSource resolvedDefaultDataSource = (DataSource) getField(resolvedDefaultDataSourceField, abstractRoutingDataSource);
			if (resolvedDefaultDataSource != null) {
				ReflectionUtils.setField(resolvedDefaultDataSourceField, abstractRoutingDataSource, convertToProxyDataSource(resolvedDefaultDataSource));
			}

			return abstractRoutingDataSource;
		}

		return convertToProxyDataSource(dataSource);
	}


	public DataSource convertToProxyDataSource(DataSource dataSource) {
		if (dataSource instanceof HikariDataSource) {
			HikariConfig oldConfig = (HikariDataSource) dataSource;

			// jdbc:h2:mem:test to  jdbc:p6spy:h2:mem:test
			String jdbcUrl = oldConfig.getJdbcUrl();
			if (!jdbcUrl.contains("p6spy")) {
				jdbcUrl = "jdbc:p6spy" + jdbcUrl.substring(4);
			}

			HikariConfig newConfig = new HikariConfig();
			newConfig.setPoolName("proxy-P6SpyDriver");
			newConfig.setDriverClassName("com.p6spy.engine.spy.P6SpyDriver");
			newConfig.setJdbcUrl(jdbcUrl);
			newConfig.setUsername(oldConfig.getUsername());
			newConfig.setPassword(oldConfig.getPassword());
			return new HikariDataSource(newConfig);
		}
		return dataSource;
	}

}

2. 代码实现位置

github 地址

0%