Skip to content

Commit 5a3f370

Browse files
authored
Add a new configuration that will format the SQL (#89)
* Add an option to format the SQL before logging * revert unwanted code formatting changes for a cleaner diff * apply PR comments
1 parent 33292e8 commit 5a3f370

File tree

9 files changed

+125
-13
lines changed

9 files changed

+125
-13
lines changed

README.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,12 @@ decorator.datasource.datasource-proxy.slow-query.logger-name=
196196
decorator.datasource.datasource-proxy.slow-query.threshold=300
197197

198198
decorator.datasource.datasource-proxy.multiline=true
199+
200+
# Formats the SQL for better readability. Uses Hibernate's formatter if present on the class path. If you opted in for a different JPA provider you need to add https://github.com/vertical-blank/sql-formatter as a runtime dependency to your app to enable this.
201+
# Mutually exclusive with json-format=true
202+
decorator.datasource.datasource-proxy.format-sql=true
199203
decorator.datasource.datasource-proxy.json-format=false
204+
200205
# Enable Query Metrics
201206
decorator.datasource.datasource-proxy.count-query=false
202207
```
@@ -293,4 +298,4 @@ public DataSourceDecorator customDecorator() {
293298

294299
If you want to disable decorating set `decorator.datasource.exclude-beans` with bean names you want to exclude.
295300
Also, you can disable decorating for `AbstractRoutingDataSource` setting property `decorator.datasource.ignore-routing-data-sources` to `true`
296-
Set `decorator.datasource.enabled` to `false` if you want to disable all decorators for all datasources.
301+
Set `decorator.datasource.enabled` to `false` if you want to disable all decorators for all datasources.

build.gradle.kts

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ subprojects {
3939

4040
extra["springBootVersion"] = "3.0.2"
4141
extra["p6SpyVersion"] = "3.9.0"
42-
extra["datasourceProxyVersion"] = "1.8.1"
42+
extra["datasourceProxyVersion"] = "1.9"
4343
extra["flexyPoolVersion"] = "2.2.3"
4444

4545
extra["release"] = listOf(
@@ -155,4 +155,4 @@ tasks {
155155
dependsOn(it.tasks.build)
156156
}
157157
}
158-
}
158+
}

datasource-decorator-spring-boot-autoconfigure/build.gradle.kts

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ dependencies {
2323

2424
compileOnly("org.springframework.boot:spring-boot-starter-actuator:${project.extra["springBootVersion"]}")
2525

26+
// optional (compileOnly) dependencies for SQL formatting
27+
compileOnly("org.hibernate:hibernate-core:6.1.6.Final") // should match the version managed by spring-boot
28+
compileOnly("com.github.vertical-blank:sql-formatter:2.0.4")
29+
2630
testImplementation("com.h2database:h2:2.1.214")
2731
testImplementation("org.springframework.boot:spring-boot-starter-test:${project.extra["springBootVersion"]}")
2832

@@ -57,4 +61,4 @@ tasks {
5761
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
5862
}
5963
}
60-
}
64+
}

datasource-decorator-spring-boot-autoconfigure/src/main/java/com/github/gavlyukovskiy/boot/jdbc/decorator/DataSourceDecoratorAutoConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ public static DataSourceDecoratorBeanPostProcessor dataSourceDecoratorBeanPostPr
6060
public DataSourceNameResolver dataSourceNameResolver(ApplicationContext applicationContext) {
6161
return new DataSourceNameResolver(applicationContext);
6262
}
63-
}
63+
}

datasource-decorator-spring-boot-autoconfigure/src/main/java/com/github/gavlyukovskiy/boot/jdbc/decorator/dsproxy/DataSourceProxyConfiguration.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceDecoratorProperties;
2020
import com.github.gavlyukovskiy.boot.jdbc.decorator.DataSourceNameResolver;
21+
import com.github.gavlyukovskiy.boot.jdbc.decorator.flexypool.FlexyPoolConfiguration;
22+
import com.github.gavlyukovskiy.boot.jdbc.decorator.p6spy.P6SpyConfiguration;
2123
import net.ttddyy.dsproxy.listener.QueryCountStrategy;
2224
import net.ttddyy.dsproxy.listener.QueryExecutionListener;
2325
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
@@ -30,6 +32,7 @@
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3234
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Import;
3336

3437
/**
3538
* Configuration for integration with datasource-proxy, allows to use define custom {@link QueryExecutionListener},
@@ -40,6 +43,10 @@
4043
// Datasource-Proxy is automatically configured by Spring Cloud Sleuth if exists
4144
@ConditionalOnMissingBean(type = "org.springframework.cloud.sleuth.autoconfig.instrument.jdbc.DataSourceProxyConfiguration")
4245
@ConditionalOnClass(ProxyDataSource.class)
46+
@Import({
47+
HibernateFormatterConfiguration.class,
48+
SqlFormatterConfiguration.class,
49+
})
4350
public class DataSourceProxyConfiguration {
4451

4552
@Autowired
@@ -67,4 +74,4 @@ public ProxyDataSourceDecorator proxyDataSourceDecorator(ProxyDataSourceBuilderC
6774
public QueryCountStrategy queryCountStrategy() {
6875
return new SingleQueryCountHolder();
6976
}
70-
}
77+
}

datasource-decorator-spring-boot-autoconfigure/src/main/java/com/github/gavlyukovskiy/boot/jdbc/decorator/dsproxy/DataSourceProxyProperties.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ public class DataSourceProxyProperties {
4646
* @see ProxyDataSourceBuilder#multiline()
4747
*/
4848
private boolean multiline = true;
49+
50+
51+
/**
52+
* Use formatted SQL for logging query.
53+
*
54+
* @see ProxyDataSourceBuilder#formatQuery(ProxyDataSourceBuilder.FormatQueryCallback)
55+
*/
56+
private boolean formatSql = false;
57+
4958
/**
5059
* Use json output for logging query.
5160
*
@@ -76,6 +85,10 @@ public boolean isMultiline() {
7685
return this.multiline;
7786
}
7887

88+
public boolean isFormatSql() {
89+
return this.formatSql;
90+
}
91+
7992
public boolean isJsonFormat() {
8093
return this.jsonFormat;
8194
}
@@ -100,6 +113,10 @@ public void setMultiline(boolean multiline) {
100113
this.multiline = multiline;
101114
}
102115

116+
public void setFormatSql(boolean formatSql) {
117+
this.formatSql = formatSql;
118+
}
119+
103120
public void setJsonFormat(boolean jsonFormat) {
104121
this.jsonFormat = jsonFormat;
105122
}
@@ -220,4 +237,4 @@ public enum DataSourceProxyLogging {
220237
COMMONS,
221238
JUL
222239
}
223-
}
240+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.github.gavlyukovskiy.boot.jdbc.decorator.dsproxy;
2+
3+
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
4+
import org.hibernate.engine.jdbc.internal.BasicFormatterImpl;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
13+
@Configuration(proxyBeanMethods = false)
14+
@ConditionalOnClass(BasicFormatterImpl.class)
15+
@ConditionalOnProperty(name = "decorator.datasource.datasource-proxy.format-sql", havingValue = "true")
16+
class HibernateFormatterConfiguration {
17+
18+
private static final Logger log = LoggerFactory.getLogger(HibernateFormatterConfiguration.class);
19+
20+
@Bean
21+
@ConditionalOnMissingBean // let users define their own
22+
public ProxyDataSourceBuilder.FormatQueryCallback hibernateFormatQueryCallback() {
23+
log.debug("{} will be used as formatter", BasicFormatterImpl.class.getName());
24+
BasicFormatterImpl hibernateFormatter = new BasicFormatterImpl();
25+
return hibernateFormatter::format;
26+
}
27+
}

datasource-decorator-spring-boot-autoconfigure/src/main/java/com/github/gavlyukovskiy/boot/jdbc/decorator/dsproxy/ProxyDataSourceBuilderConfigurer.java

+32-6
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,8 @@
3838
/**
3939
* Configurer for {@link ProxyDataSourceBuilder} based on the application context.
4040
*
41-
* @see ProxyDataSourceBuilder
42-
*
4341
* @author Arthur Gavlyukovskiy
42+
* @see ProxyDataSourceBuilder
4443
* @since 1.3.1
4544
*/
4645
public class ProxyDataSourceBuilderConfigurer {
@@ -68,6 +67,9 @@ public class ProxyDataSourceBuilderConfigurer {
6867
@Autowired(required = false)
6968
private ConnectionIdManagerProvider connectionIdManagerProvider;
7069

70+
@Autowired(required = false)
71+
ProxyDataSourceBuilder.FormatQueryCallback formatQueryCallback;
72+
7173
public void configure(ProxyDataSourceBuilder proxyDataSourceBuilder, DataSourceProxyProperties datasourceProxy) {
7274
switch (datasourceProxy.getLogging()) {
7375
case SLF4J: {
@@ -110,15 +112,30 @@ public void configure(ProxyDataSourceBuilder proxyDataSourceBuilder, DataSourceP
110112
break;
111113
}
112114
}
115+
113116
if (datasourceProxy.isMultiline() && datasourceProxy.isJsonFormat()) {
114117
log.warn("Found opposite multiline and json format, multiline will be used (may depend on library version)");
115118
}
119+
if (datasourceProxy.isFormatSql() && datasourceProxy.isJsonFormat()) {
120+
log.warn("Found opposite format-sql and json format, json format will be used (may depend on library version)");
121+
}
122+
116123
if (datasourceProxy.isMultiline()) {
117124
proxyDataSourceBuilder.multiline();
118125
}
119-
if (datasourceProxy.isJsonFormat()) {
126+
127+
if (!datasourceProxy.isMultiline() && datasourceProxy.isJsonFormat()) {
120128
proxyDataSourceBuilder.asJson();
121129
}
130+
131+
if (!datasourceProxy.isJsonFormat() && datasourceProxy.isFormatSql()) {
132+
if (formatQueryCallback != null) {
133+
proxyDataSourceBuilder.formatQuery(formatQueryCallback);
134+
} else {
135+
log.warn("formatSql requested but cannot be enabled because no formatter is present (neither Hibernate nor SqlFormatter).");
136+
}
137+
}
138+
122139
if (datasourceProxy.isCountQuery()) {
123140
proxyDataSourceBuilder.countQuery(queryCountStrategy);
124141
}
@@ -161,8 +178,7 @@ private Level toJULLogLevel(String logLevel) {
161178
}
162179
try {
163180
return Level.parse(logLevel);
164-
}
165-
catch (IllegalArgumentException e) {
181+
} catch (IllegalArgumentException e) {
166182
if (logLevel.equalsIgnoreCase("DEBUG")) {
167183
return Level.FINE;
168184
}
@@ -185,4 +201,14 @@ private CommonsLogLevel toCommonsLogLevel(String logLevel) {
185201
throw new IllegalArgumentException("Unresolved log level " + logLevel + " for apache commons logger, " +
186202
"known levels " + Arrays.toString(CommonsLogLevel.values()));
187203
}
188-
}
204+
205+
private static boolean classExists(String fullQualifiedClassName) {
206+
207+
try {
208+
Class.forName(fullQualifiedClassName);
209+
return true;
210+
} catch (ClassNotFoundException e) {
211+
return false;
212+
}
213+
}
214+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.github.gavlyukovskiy.boot.jdbc.decorator.dsproxy;
2+
3+
import com.github.vertical_blank.sqlformatter.SqlFormatter;
4+
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
9+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10+
import org.springframework.context.annotation.Bean;
11+
import org.springframework.context.annotation.Configuration;
12+
13+
@Configuration(proxyBeanMethods = false)
14+
@ConditionalOnClass(SqlFormatter.class)
15+
@ConditionalOnProperty(name = "decorator.datasource.datasource-proxy.format-sql", havingValue = "true")
16+
class SqlFormatterConfiguration {
17+
18+
private static final Logger log = LoggerFactory.getLogger(SqlFormatterConfiguration.class);
19+
20+
@Bean
21+
@ConditionalOnMissingBean // let users define their own
22+
public ProxyDataSourceBuilder.FormatQueryCallback sqlFormatterFormatQueryCallback() {
23+
log.debug("{} will be used as formatter", SqlFormatter.class.getName());
24+
return SqlFormatter::format;
25+
}
26+
}

0 commit comments

Comments
 (0)