1. About
1.1. What is datasource-proxy?
datasource-proxy provides a simple API for intercepting JDBC interactions and lets you add your own logic
before or after query execution or method calls.
The proxy works as a javax.sql.DataSource and takes listeners.
Predefined listeners support query logging, slow query detection, query execution statistics, interaction
tracing, and more. You can also easily add custom listeners to the listener chain.
1.2. Built-in Support
- Query and Parameter Logging
-
You can log executed database queries and parameters using your preferred logging framework (Commons Logging, SLF4J, Java Util Logging, System.out, or your own implementation). The output can be formatted as JSON.
- Slow Query Detection and Logging
-
You can register callbacks for queries that exceed a configured threshold. Slow query loggers are available out of the box.
- Connection ID
-
Each connection is assigned a unique ID, implemented as a sequentially increasing number by default. The connection ID is included in the log entry.
- Query Metrics
-
You can collect statistics for executed queries, such as total time and the number of
SELECT,INSERT,UPDATE, andDELETEstatements. The output can be formatted as JSON. - Custom Logic Injection for Query and Method Execution
-
You can write your own
QueryExecutionListenerimplementations and have them invoked before or after query execution.MethodExecutionListeneris also invoked for each JDBC API interaction. - Web Application Support
-
ProxyDataSourcecan be configured via JNDI. Query metrics are available on a per-web-request basis (request-response lifecycle). For JSP-based UIs, a custom tag for accessing metrics (<dsp:metrics/>) is also available. - Query and Parameter Replacement
-
QueryTransformerandParameterTransformerallow you to modify the executing query and parameters just before they are sent to the database. - Method Tracing
-
Intercept JDBC API interactions such as obtaining a connection, creating prepared statements, setting parameters, and executing queries.
1.3. Jakarta Namespace
The javax.sql.* classes are part of the Java SE APIs, so they are not affected by the namespace change in
Java EE APIs to Jakarta. For example, the javax.sql.DataSource class remains in the javax.sql package.
In datasource-proxy, there are a few usages of the javax namespace in JNDI and query count support via
ServletRequestListener.
These are not used frequently and are expected to be removed or separated in the next major version release.
If you require these features in the Jakarta namespace, it is recommended that you create your own classes based on the existing implementations.
1.4. Sample Log Output
Query execution (single line):
Name:MyProxy, Connection:1, Time:1, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["CREATE TABLE users(id INT, name VARCHAR(255))"], Params:[]
Name:MyProxy, Connection:2, Time:5, Success:True, Type:Prepared, Batch:True, QuerySize:1, BatchSize:2, Query:["INSERT INTO users (id, name) VALUES (?, ?)"], Params:[(1,foo),(2,bar)]
Query execution (multiple lines):
Name:MyProxy, Connection:1, Time:3, Success:True
Type:Callable, Batch:True, QuerySize:1, BatchSize:2
Query:["{call getEmployer (?, ?)}"]
Params:[(id=100,name=foo),(id=200,name=bar)]
JSON output:
{"name":"MyProxy", "connection":1, "time":1, "success":true, "type":"Statement", "batch":false, "querySize":1, "batchSize":0, "query":["CREATE TABLE users(id INT, name VARCHAR(255))"], "params":[]}
{"name":"MyProxy", "connection":2, "time":0, "success":true, "type":"Prepared", "batch":true, "querySize":1, "batchSize":3, "query":["INSERT INTO users (id, name) VALUES (?, ?)"], "params":[["1","foo"],["2","bar"],["3","baz"]]}
Query metrics:
Name:"MyProxy", Time:6, Total:1, Success:1, Failure:0, Select:1, Insert:0, Update:0, Delete:0, Other:0
// JSON output
{"name":"MyProxy", "time":10, "total":3, "success":3, "failure":0, "select":1, "insert":2, "update":0, "delete":0, "other":0}
Method tracing:
[1][success][0ms][conn=1] ProxyDataSource#getConnection()
[2][success][1ms][conn=1] JDBCConnection#prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)")
[3][success][0ms][conn=1] JDBCPreparedStatement#setString(2,"FOO")
[4][success][0ms][conn=1] JDBCPreparedStatement#setInt(1,100)
[5][success][0ms][conn=1] JDBCPreparedStatement#addBatch()
[6][success][0ms][conn=1] JDBCPreparedStatement#setInt(1,200)
[7][success][0ms][conn=1] JDBCPreparedStatement#setString(2,"BAR")
[8][success][0ms][conn=1] JDBCPreparedStatement#addBatch()
[9][success][1ms][conn=1] JDBCPreparedStatement#executeBatch()
[10][success][0ms][conn=1] JDBCPreparedStatement#close()
[11][success][0ms][conn=1] JDBCConnection#close()
2. Installation
2.1. Dependency
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>[LATEST_VERSION]</version>
</dependency>
There is no required dependency for using datasource-proxy.
If you choose to use logging support for a specific logging library, such as Commons Logging or SLF4J,
you need to add that dependency explicitly.
datasource-proxy does not bring in any libraries via transitive dependencies.
2.1.1. Snapshot
Snapshots are available from the OSS Sonatype snapshot repository.
To download snapshot JARs, enable the Sonatype snapshot repository:
<repositories>
<repository>
<id>sonatype-snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
3. How to use
ProxyDataSource implements javax.sql.DataSource and serves as the entry point for all interceptor logic.
You can directly instantiate ProxyDataSource, or use the ProxyDataSourceBuilder builder class to create it.
Once ProxyDataSource is set up, you can pass it to your application as a datasource.
3.1. Creating ProxyDataSource
ProxyDataSourceBuilder provides a fluent API for constructing a ProxyDataSource instance.
It also provides builder methods for registering built-in and custom listeners.
DataSource dataSource =
ProxyDataSourceBuilder.create(actualDataSource) // pass the original datasource
.logQueryByCommons(INFO) // logQueryBySlf4j(), logQueryByJUL(), logQueryToSysOut()
.loggingFilter(myFilter) // filter which queries to log
.countQuery() // enable query count metrics
.logSlowQueryByCommons(10, TimeUnit.MINUTES) // also by slf4j, jul, system out
.proxyResultSet() // enable proxying ResultSet
.listener(myListener) // register a custom listener
.afterMethod(executionContext -> { // register a custom listener with lambda
...
})
.build(); // returns a ProxyDataSource instance
3.1.1. Creating a proxied DataSource
Starting from datasource-proxy v1.10, the new buildProxy() method returns a pure DataSource proxy instance.
The difference between the ProxyDataSource returned by build() and the DataSource proxy returned by
buildProxy() is as follows:
-
ProxyDataSourceis a concrete implementation class. This class provides method callbacks only for thegetConnectionmethods. -
The
DataSourceproxy is a pure proxy instance. It provides method callbacks for all methods.
DataSource dataSource =
ProxyDataSourceBuilder.create(actualDataSource) // pass the original datasource
...
.buildProxy(); // returns DataSource
3.1.2. Retrieving the original JDBC object
All proxied JDBC objects implement the ProxyJdbcObject interface, which defines getTarget() to retrieve
the original JDBC object.
if (connection instanceof ProxyJdbcObject) {
Connection original = (Connection) ((ProxyJdbcObject) connection).getTarget();
....
}
3.1.3. DriverManager and Connection Support
From an instance of java.sql.Connection, you can create a proxy that behaves the same way as the one
returned by ProxyDataSource.
// obtain a connection
Class.forName("org.hsqldb.jdbcDriver");
Connection realConnection = DriverManager.getConnection("jdbc:hsqldb:mem:aname");
ProxyConfig proxyConfig = ProxyConfig.Builder.create()
// configure more
.build();
JdbcProxyFactory jdbcProxyFactory = new JdkJdbcProxyFactory();
Connection proxyConnection = jdbcProxyFactory.createConnection(realConnection, proxyConfig);
...
3.1.4. Spring XML Configuration Support
Because ProxyConfig is created using a builder-style API, it is not straightforward to create its bean in
an XML-based Spring configuration.
ProxyConfigSpringXmlSupport provides setters to build a ProxyConfig specifically for use in XML-based
Spring configuration files.
<bean id="dataSource" primary="true" class="net.ttddyy.dsproxy.support.ProxyDataSource">
<property name="dataSource" ref="actualDataSource"/>
<property name="proxyConfig" ref="proxyConfig"/>
<!--
Alternatively, you can use SpEL to invoke the create() method to get the ProxyConfig bean.
<property name="proxyConfig" value="#{proxyConfigSupport.create()}"/>
-->
</bean>
<bean id="proxyConfig"
factory-bean="proxyConfigSupport"
factory-method="create"/>
<bean id="proxyConfigSupport" class="net.ttddyy.dsproxy.support.ProxyConfigSpringXmlSupport">
<property name="dataSourceName" value="my-ds"/>
<property name="queryListener" ref="queryListener"/>
<property name="methodListener" ref="methodListener"/>
</bean>
<bean id="queryListener" class="net.ttddyy.dsproxy.listener.ChainListener">
<property name="listeners">
<list>
<bean class="net.ttddyy.dsproxy.listener.logging.SystemOutQueryLoggingListener"/>
</list>
</property>
</bean>
<bean id="methodListener" class="net.ttddyy.dsproxy.listener.CompositeMethodListener">
<property name="listeners">
<list>
<bean class="net.ttddyy.dsproxy.listener.TracingMethodListener"/>
</list>
</property>
</bean>
3.1.5. JNDI Support
<Resource name="jdbc/global/myProxy"
auth="Container"
type="net.ttddyy.dsproxy.support.ProxyDataSource"
factory="net.ttddyy.dsproxy.support.jndi.ProxyDataSourceObjectFactory"
description="ds"
listeners="commons,count"
proxyName="MyProxy"
dataSource="[REFERENCE_TO_ACTUAL_DATASOURCE_RESOURCE]" <!-- ex: java:jdbc/global/myDS -->
/>
The datasource-proxy JAR and your chosen logging library (Commons, SLF4J, etc.) must be accessible from the container.
| Parameter | Description |
|---|---|
dataSource (required) |
Reference to the actual datasource resource, for example: |
proxyName |
ProxyDataSource name |
logLevel |
Log level for Commons Logging or SLF4J, for example: |
loggerName |
Logger name (since v1.3.1) |
listeners |
Fully qualified class name of a |
queryTransformer |
Fully qualified class name of a |
parameterTransformer |
Fully qualified class name of a |
| Parameter | Description |
|---|---|
sysout |
Alias to |
commons |
Alias to |
slf4j |
Alias to |
count |
Alias to |
x.y.z.MyQueryExecutionListener |
Fully qualified class name of a |
| Parameter | Description |
|---|---|
json |
Set the logging output format to JSON |
See Tomcat configuration examples in the datasource-proxy-examples project:
Also see the ProxyDataSourceObjectFactory Javadoc.
3.2. Generated Keys
3.2.1. Auto-retrieve generated keys
When 1) generated keys are available (for example, Statement#RETURN_GENERATED_KEYS is specified when a
statement is executed or when a prepared/callable statement is created) and 2) query execution methods
(execute, executeUpdate, executeBatch, etc.) are called, datasource-proxy automatically calls
getGeneratedKeys() and makes the returned ResultSet available via ExecutionInfo#getGeneratedKeys() in
QueryExecutionListener.
To enable auto-retrieval of generated keys:
builder
.autoRetrieveGeneratedKeys(...)
.build();
When getGeneratedKeys() is called:If a cached auto-retrieved generated key result set is still open, it is returned. If there is no cached generated key result set, or the cached one has already been closed, datasource-proxy performs the actual getGeneratedKeys() call on the underlying Statement object, caches the result, and then
returns it.
|
If you want to always retrieve generated keys without changing application code that may be executing a
query without Statement#RETURN_GENERATED_KEYS, see Replace the invoking method and parameters.
You can modify query executions to set RETURN_GENERATED_KEYS.
For batch execution: executeBatch() and executeLargeBatch()
According to the JDBC specification, returning getGeneratedKeys() for executeBatch() and
executeLargeBatch() is implementation-specific:
Note – It is implementation-defined as to whether Statement.getGeneratedKeys will return generated values after invoking the executeBatch or executeLargeBatch methods.
In datasource-proxy, whether to auto-retrieve generated keys for batch execution is configurable through
ProxyDataSourceBuilder#retrieveGeneratedKeysForBatch().
builder
.autoRetrieveGeneratedKeys(...)
.retrieveGeneratedKeysForBatch(false, true) // first argument is for Statement,
// second is for Prepared/Callable
.build();
Default behavior for batch executions:
| Type | Auto Retrieval |
|---|---|
Statement |
Not retrieved |
Prepared/Callable |
Retrieved |
3.2.2. Auto-close generated keys
When auto-close is enabled, the auto-retrieved generated keys are closed
after all QueryExecutionListener#afterQuery() callbacks have been processed.
This option is used with auto retrieve generated keys feature.
builder
.autoRetrieveGeneratedKeys(true) // specify `true` or `false` to auto-close the `ResultSet`
.build();
If you want to reuse the auto-retrieved generated-keys in upper layer, such as OR mapper or application, turn
off this auto-close feature; so that, cached generated-keys will be returned when getGeneratedKeys() is called.
Based on jdbc-driver implementation, calling getGeneratedKeys() might be limited to once.
When you decided to reuse generated-keys, you might need to support repeatable read on the ResultSet. see
Proxying generated keys for how to support repeatable read on generated-keys.
When getGeneratedKeys() is called, the returned ResultSet will NOT be closed by this feature since it is
not auto-retrieved generated-keys.
|
3.2.3. Proxying generated keys
Create a proxy for generated-keys' ResultSet.
This applies to both auto-retrieved generated-keys and result of
getGeneratedKeys().
Usecase
You have a QueryExecutionListener that reads values from generated-keys' ResultSet. Also, in upper layer, such as
ORM or application logic, it needs to use the same generated-keys' ResultSet.
Based on ResultSet implementation, reading values may be limited to only once; therefore, when
QueryExecutionListener reads values from generated-keys' ResultSet and upper layer tries to read values from it,
the ResultSet may not allow the repeated read.
To allow multiple reads, you can proxy the generated-keys' ResultSet with RepeatableReadResultSetProxyLogic.
builder
// specify proxy factory for generated-keys' result-set
.autoRetrieveGeneratedKeys(false, new RepeatableReadResultSetProxyLogicFactory());
// equivalent of above
// .autoRetrieveGeneratedKeysWithRepeatableReadProxy(false);
.build();
3.3. Proxying ResultSet
By default, datasource-proxy does NOT proxy ResultSet. However, in some case, you want to return a proxied
ResultSet - for example, apply MethodExecutionListener on ResultSet.
To enable ResultSet proxy, ProxyDataSourceBuilder has proxyResultSet() method.
builder
// configure listeners
.proxyResultSet() // enable proxying result set
.build();
In addition, the proxyResultSet() method takes ResultSetProxyLogicFactory to add some logic on
interacting with ResultSet. Default logic is no-op.
This proxying does NOT apply to the ResultSet from getGeneratedKeys(). To proxy the result of
getGeneratedKeys(), see Proxying generated keys.
|
3.3.1. Repeatable read ResultSet
RepeatableReadResultSetProxyLogic allows ResultSet to be consumed more than once.
builder
// configure listeners
.repeatableReadResultSet()
// .proxyResultSet(new RepeatableReadResultSetProxyLogicFactory()) // same as above
.build();
3.3.2. Repeatable read ResultSet with CachedRowSetResultSetProxyLogic
New result set proxy, CachedRowSetResultSetProxyLogic is introduced at version 1.4.7.
CachedRowSetResultSetProxyLogic uses javax.sql.rowset.CachedRowSet as underlying implementation, which is a
sub-interface of ResultSet and provides cursor scrollability in disconnected fashion.
Default CachedRowSet implementation is com.sun.rowset.CachedRowSetImpl for oracle JDK (note for com.sun package),
which you can change via RowSetFactory (please see details on JDK documentation).
To use CachedRowSetResultSetProxyLogic for ResultSet proxy, simply supply its proxy-logic factory,
CachedRowSetResultSetProxyLogicFactory, to ProxyDataSourceBuilder when enabling result set proxy.
builder
// proxy for regular ResultSet
.proxyResultSet(new RepeatableReadResultSetProxyLogicFactory())
// proxy for generated-keys
.autoRetrieveGeneratedKeys(false, new RepeatableReadResultSetProxyLogicFactory());
.build();
3.3.3. Using a custom ResultSet proxy
The proxyResultSet and autoRetrieveGeneratedKeys methods on the ProxyDataSourceBuilder can take a ResultSetProxyLogicFactory factory implementation. If you want to provide custom logic for handling the ResultSet object, you can provide a custom implementation by providing a custom factory implementation.
For example, this code in the datasource-proxy pull request #98 provides a Spring’s SqlRowSet based ResultSet proxy implementation to cover some shortcomings from the com.sun.rowset.CachedRowSetImpl.
4. Listeners
Currently, datasource-proxy provides the following listeners:
-
QueryExecutionListener -
MethodExecutionListener -
JdbcLifecycleEventListener
4.1. QueryExecutionListener
QueryExecutionListener is called when query execution methods are invoked
(for example: execute, executeBatch, executeQuery, and so on).
QueryExecutionListener:void beforeQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList);
void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList);
ExecutionInfo contains contextual information about query execution (statement type, batch, connection ID,
and so on), and QueryInfo contains the actual query string and parameters.
For the afterQuery callback, ExecutionInfo contains additional information: the query result, the time it
took to execute the query, and any thrown exception.
The number of queries and parameters depends on the type of query execution.
| Execution Type | # of Query | # of Query Parameters |
|---|---|---|
Statement |
1 |
empty |
Statement (batch) |
N |
empty |
Prepared Statement |
1 |
1 |
Prepared Statement (batch) |
1 |
N |
Callable Statement |
1 |
1 |
Callable Statement (batch) |
1 |
N |
4.2. MethodExecutionListener
MethodExecutionListener intercepts JDBC API calls for the following types:
-
Connection,Statement,PreparedStatement, andCallableStatement: all methods -
ResultSet: all methods when the result set proxy is enabled -
ProxyDataSource:getConnection()method
4.2.1. Replace the invoking method and parameters
In MethodExecutionListener#beforeMethod(), you can change the invoking method and its parameters on the
target JDBC object.
For example, in application code, it may call Statement#execute(String). However, to leverage
generated-keys retrieval, you may want to call Statement#execute(String, int) instead
without changing application code.
To update or replace the method and parameters, set the new method or arguments via
MethodExecutionContext#setMethod() and MethodExecutionContext#setMethodArgs() inside
MethodExecutionListener#beforeMethod().
For the above use case:
private static final Method EXECUTE_METHOD = Statement.class.getMethod("execute", String.class);
private static final Method EXECUTE_WITH_INT_METHOD = Statement.class.getMethod("execute", String.class, int.class);
@Override
public void beforeMethod(MethodExecutionContext executionContext) {
// replace "Statement#execute(sql)" with "Statement#execute(sql, RETURN_GENERATED_KEYS)"
if (EXECUTE_METHOD.equals(executionContext.getMethod())) {
Object[] originalArgs = executionContext.getMethodArgs();
Object[] newArgs = new Object[]{originalArgs[0], Statement.RETURN_GENERATED_KEYS};
executionContext.setMethod(EXECUTE_WITH_INT_METHOD); // new method
executionContext.setMethodArgs(newArgs); // new method parameters
}
}
4.3. JdbcLifecycleEventListener
The JdbcLifecycleEventListener interface defines before/after method callbacks for all JDBC interfaces that
datasource-proxy proxies (DataSource, Connection, Statement, PreparedStatement, CallableStatement,
and ResultSet). It also defines before/after callbacks for method invocations and query executions,
analogous to MethodExecutionListener and QueryExecutionListener.
To implement your listener, you can extend JdbcLifecycleEventListenerAdapter, which implements all
interface methods with no-op implementations and lets you selectively override the callbacks you care about.
DataSource dataSource =
ProxyDataSourceBuilder.create(actualDataSource)
.listener(new JdbcLifecycleEventListenerAdapter() {
@Override
public void beforeGetConnection(MethodExecutionContext executionContext) {
// implement the logic before DataSource#getConnection();
}
@Override
public void afterRollback(MethodExecutionContext executionContext) {
// implement the logic after Connection#rollback();
}
})
.build();
4.3.1. Overloaded methods
Because JDBC API classes are hierarchical (for example, PreparedStatement extends Statement), some
methods are overloaded. For example, PreparedStatement#execute overloads Statement#execute, and
PreparedStatement#setInt and CallableStatement#setInt are both overloaded forms.
JdbcLifecycleEventListener callbacks are invoked by comparing the invoked method name.
Therefore, in this example, [before|after]Execute and [before|after]setInt are called.
If you need to know exactly which overload was invoked, check the invoked instance, method, or arguments to
identify the actual method that was called.
5. Built-in Listeners
5.1. Query Logging Listener
The query logging listener is the most commonly used listener. It logs executed queries and their actual
parameters to Commons Logging, SLF4J, JUL, or System.out.
Sample output
Name:MyProxy, Connection:1, Time:1, Success:True, Type:Statement, Batch:False, QuerySize:1, BatchSize:0, Query:["CREATE TABLE users(id INT, name VARCHAR(255))"], Params:[]
Name:MyProxy, Connection:2, Time:5, Success:True, Type:Prepared, Batch:True, QuerySize:1, BatchSize:2, Query:["INSERT INTO users (id, name) VALUES (?, ?)"], Params:[(1,foo),(2,bar)]
| Key | Value |
|---|---|
Name |
Name of |
Connection |
Connection ID |
Time |
How long the query took to execute, in milliseconds |
Success |
Whether the query execution succeeded |
Type |
Type of statement (Statement/Prepared/Callable) |
Batch |
Batch execution |
QuerySize |
Number of queries |
BatchSize |
Number of batch operations |
Query |
The query text |
Params |
Query parameters |
ProxyDataSourceBuilder provides methods for easily configuring and registering these listeners.
builder
.logQueryByCommons() // log using Commons Logging
.logQueryBySlf4j() // log using SLF4J
.logQueryByJUL() // log using Java Util Logging
.logQueryToSysOut() // log using System.out
.multiline() // enable multiline output
.build();
| If you choose to use Commons Logging or SLF4J, add the corresponding dependency to your project. |
By default, each log entry is printed on a single line. If the multiline() option is set, the log is written
across multiple lines. Likewise, the json() option prints the output in JSON format.
Here is another example of a multiline log that formats the query using Hibernate formatting.
Datasource-proxy v1.9 added the formatQuery() method on ProxyDataSourceBuilder, which lets you specify a
callback to modify the query before logging.
// v1.9 or above
Formatter formatter = FormatStyle.BASIC.getFormatter();
DataSource ds = ProxyDataSourceBuilder
.create(actualDataSource)
.formatQuery(formatter::format)
.multiline()
.logQueryToSysOut()
.build()
// prior to v1.9
QueryLogEntryCreator logEntryCreator = new DefaultQueryLogEntryCreator() {
@Override
protected String formatQuery(String query) {
return FormatStyle.BASIC.getFormatter().format(query); // use Hibernate formatter
}
};
logEntryCreator.setMultiline(true); // enable multiline output
// print to System.out
SystemOutQueryLoggingListener listener = new SystemOutQueryLoggingListener();
listener.setQueryLogEntryCreator(logEntryCreator);
return ProxyDataSourceBuilder
.create(actualDataSource)
.listener(listener)
.build()
You can also filter which queries are logged by providing a LoggingFilter:
builder
.logQueryBySlf4j()
// Only log queries that are not sent to the 'audit' table
.loggingFilter((execInfo, queryInfoList) ->
queryInfoList.stream().noneMatch(q -> q.getQuery().contains("audit"))
)
If no filter is set, all queries are logged by default.
5.2. Slow Query Logging Listener
There are two approaches to detecting slow queries:
- During execution
-
Detect slow queries while they are running. A scheduled check fires once the query has run longer than the configured threshold, even if the query has not finished yet.
SlowQueryListeneruses this approach. This approach is sometimes called preemptive; however, it only notifies you about the query. The running query is NOT interrupted or canceled. - After execution
-
Detect slow queries after they have finished. The
afterQuerycallback compares the query’s elapsed time with the threshold. This is a simple approach that can be implemented with anyQueryExecutionListener. This approach is sometimes called non-preemptive.
During execution (SlowQueryListener) |
After execution (afterQuery callback) |
|
|---|---|---|
When it is triggered |
When the threshold has passed while the query is still running |
After the query has finished |
Elapsed time in |
Time at the moment of the check (approximately the threshold time) |
Actual query execution time |
Query result, success/failure |
Not available |
Available |
Thread that runs the callback |
Thread of the scheduled executor in |
Thread that executed the query |
Queries that hang or never return |
Detected |
Not detected |
Overhead |
Schedules a check for each query execution |
Compares elapsed time |
Both approaches can be used together.
For example, use SlowQueryListener to get an early notice of long-running queries and use an
afterQuery callback to record their actual execution time.
5.2.1. During execution
When a query takes longer than the configured threshold, SlowQueryListener invokes a callback method.
The callback is invoked only once for the target query if it exceeds the threshold time.
Combining logging and slow query detection, the following built-in classes write slow query entries to their corresponding loggers:
-
CommonsSlowQueryListener -
SLF4JSlowQueryListener -
JULSlowQueryListener -
SystemOutSlowQueryListener
ProxyDataSourceBuilder provides methods for configuring these listeners.
builder
.logSlowQueryByCommons(1, TimeUnit.SECONDS) // log slow queries using Commons Logging
.logSlowQueryBySlf4j(1, TimeUnit.SECONDS) // log slow queries using SLF4J
.logSlowQueryByJUL(1, TimeUnit.SECONDS) // log slow queries using Java Util Logging
.logSlowQueryToSysOut(1, TimeUnit.SECONDS) // log slow queries using System.out
.multiline() // enable multiline output
.build();
|
Elapsed Time
For example, if the threshold is set to 1 minute and a query takes 5 minutes to complete, the query is logged
when 1 minute has passed while the query is still running. To log slow queries with their actual execution time, use the after execution approach. |
5.2.2. After execution
To log queries that exceeded the threshold with their actual execution time, you can customize the existing
Query Logging Listener implementation instead of using SlowQueryListener.
Here is a sample implementation using SLF4JQueryLoggingListener:
long thresholdInMills = ...
SLF4JQueryLoggingListener listener = new SLF4JQueryLoggingListener() {
@Override
public void afterQuery(ExecutionInfo execInfo, List<QueryInfo> queryInfoList) {
// call query logging logic only when it took more than the threshold
if (thresholdInMills <= execInfo.getElapsedTime()) {
super.afterQuery(execInfo, queryInfoList);
}
}
};
listener.setLogLevel(SLF4JLogLevel.WARN);
This implementation logs queries that took longer than the configured threshold AFTER the query execution has finished, so the actual query execution time is available.
To perform other actions for slow queries, such as recording metrics or sending notifications, register an
afterQuery callback with ProxyDataSourceBuilder:
long thresholdInMills = ...
builder
.afterQuery((execInfo, queryInfoList) -> {
if (thresholdInMills <= execInfo.getElapsedTime()) {
// slow query: log, record metrics, send notification, etc.
}
})
.build();
5.3. Query Count Listener
DataSourceQueryCountListener collects statistics for executed queries, such as the number of query types
(SELECT, INSERT, UPDATE, DELETE), statement types (Statement, Prepared, Callable), and more.
This class also accepts a strategy for storing such metrics. The default strategy stores metrics in a
thread-local value, which is expected to be reset at the end of the request-response lifecycle.
Another strategy is SingleQueryCountHolder, which accumulates query metrics from all threads until the metrics
are explicitly cleared.
builder.
.countQuery() // enable collecting query metrics
// .countQuery(new SingleQueryCountHolder()) // enable and specify a query count holder
.build();
Stored metrics can be retrieved by the following static method.
QueryCountHolder.get()
Semantics changes based on how metrics are stored - thread local vs global map.
When thread local is chosen, you need to clean up the stored metrics at the end of request-response lifecycle.
You can call QueryCountHolder.clear() explicitly. Or, if built-in query-count-logging-listeners(will be
addressed below) are registered, they automatically call clear() after it logs the count stats.
|
Combination of count listener and logging, datasource-proxy has built-in support for logging current query count
metrics to loggers(commons, slf4j, jul, or system.out).
They are available in servlet Filter and ServletRequestListener implementation.
Furthermore, there is a custom tag for jsp.
5.3.1. Query count logging with servlet Filter
-
CommonsQueryCountLoggingServletFilter
-
JULQueryCountLoggingServletFilter
-
SLF4JQueryCountLoggingServletFilter
-
SystemOutQueryCountLoggingServletFilter
QueryCountLoggerBuilder class helps constructing filter instance.
web.xml:<filter>
<filter-name>queryCountFilter</filter-name>
<filter-class>net.ttddyy.dsproxy.support.CommonsQueryCountLoggingServletFilter</filter-class>
<init-param> <!-- OPTIONAL -->
<param-name>clearQueryCounter</param-name>
<param-value>true</param-value>
</init-param>
<init-param> <!-- OPTIONAL -->
<param-name>logLevel</param-name>
<param-value>INFO</param-value>
</init-param>
</filter>
5.3.2. Query count logging with ServletRequestListener
-
CommonsQueryCountLoggingRequestListener
-
JULQueryCountLoggingRequestListener
-
SLF4JQueryCountLoggingRequestListener
-
SystemOutQueryCountLoggingRequestListener
-
QueryCounterClearServletRequestListener
web.xml:<context-param> <!-- OPTIONAL -->
<param-name>queryCountCommonsLogLevel</param-name>
<param-value>INFO</param-value>
</context-param>
<listener>
<listener-class>net.ttddyy.dsproxy.support.CommonsQueryCountLoggingRequestListener</listener-class>
</listener>
5.3.3. Taglib Support
For jsp, a custom tag is supported to display query count metrics.
Declare custom tag:
<%@ taglib prefix="dsp" uri="http://www.ttddyy.net/dsproxy/tags" %>
When datasource is not specfied, total number of each datasource will be displayed.
<dsp:metrics metric="select"/>
<dsp:metrics metric="update"/>
<dsp:metrics metric="insert"/>
<dsp:metrics metric="delete"/>
<dsp:metrics metric="other"/>
<dsp:metrics metric="total"/>
<dsp:metrics metric="call"/>
<dsp:metrics metric="failure"/>
<dsp:metrics metric="time"/>
Specific datasource
<dsp:metrics metric="select" dataSource="FOO"/>
<dsp:metrics metric="update" dataSource="FOO"/>
<dsp:metrics metric="insert" dataSource="FOO"/>
<dsp:metrics metric="delete" dataSource="FOO"/>
<dsp:metrics metric="other" dataSource="FOO"/>
<dsp:metrics metric="total" dataSource="FOO"/>
<dsp:metrics metric="call" dataSource="FOO"/>
<dsp:metrics metric="failure" dataSource="FOO"/>
<dsp:metrics metric="time" dataSource="FOO"/>
| Name | Description |
|---|---|
select |
Num of select queries |
insert |
Num of insert queries |
update |
Num of update queries |
delete |
Num of delete queries |
other |
Num of other queries |
statement |
Total num of statements |
prepared |
Total num of prepared statements |
callable |
Total num of callable statements |
total |
Total num of queries |
success |
Num of success queries |
failure |
Num of failure queries |
time |
Query execution time |
5.4. Tracing Method Listener
TracingMethodListener is a method execution listener that prints out all JDBC API interaction.
Sample JDBC interaction using HSQL:
Connection conn = dataSource.getConnection()
PreparedStatement ps = conn.prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)");
ps.setString(2, "FOO");
ps.setInt(1, 100);
ps.addBatch();
ps.setInt(1, 200);
ps.setString(2, "BAR");
ps.addBatch();
ps.executeBatch();
ps.close();
conn.close();
[1][success][0ms][conn=1] ProxyDataSource#getConnection()
[2][success][1ms][conn=1] JDBCConnection#prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)")
[3][success][0ms][conn=1] JDBCPreparedStatement#setString(2,"FOO")
[4][success][0ms][conn=1] JDBCPreparedStatement#setInt(1,100)
[5][success][0ms][conn=1] JDBCPreparedStatement#addBatch()
[6][success][0ms][conn=1] JDBCPreparedStatement#setInt(1,200)
[7][success][0ms][conn=1] JDBCPreparedStatement#setString(2,"BAR")
[8][success][0ms][conn=1] JDBCPreparedStatement#addBatch()
[9][success][1ms][conn=1] JDBCPreparedStatement#executeBatch()
[10][success][0ms][conn=1] JDBCPreparedStatement#close()
[11][success][0ms][conn=1] JDBCConnection#close()
ProxyDataSourceBuilder provides following methods:
builder
.traceMethods()
.build();
To change output, the builder method also takes a string consumer.
builder
// change the output to logger
.traceMethods(message -> logger.debug(message))
.build();
traceMethodsWhen method takes a boolean supplier to dynamically turn on/off tracing.
builder
.traceMethodsWhen(() -> ...condition to perform tracing... )
.build();
5.5. Custom Listeners
When you create a custom listener, you can register it via listener() or methodListener() methods.
builder
.listener(myQueryListener) // register QueryExecutionListener
.methodListener(myMethodListener) // register MethodExecutionListener
.build();
Also, with Java8 lambda, builder provides simple methods to inline listener definitions.
-
beforeQuery()andafterQuery()to registerQueryExecutionListener -
beforeMethod()andafterMethod()to registerMethodExecutionListener
builder
// register MethodExecutionListener
.afterMethod(executionContext -> {
....
})
// register QueryExecutionListener
.afterQuery((execInfo, queryInfoList) -> {
...
})
6. Sample Configuration
6.1. Use Case
-
Log all queries to
System.outas JSON -
Log query metrics to the Commons Logging logger at the end of each HTTP request using a servlet filter
6.2. pom.xml
Install datasource-proxy and commons-logging because this example uses Commons Logging for query metrics.
<dependency>
<groupId>net.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>[LATEST-VERSION]</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>[VERSION]</version>
</dependency>
6.3. web.xml
Add CommonsQueryCountLoggingServletFilter for query metrics.
<filter>
<filter-name>queryCountFilter</filter-name>
<filter-class>net.ttddyy.dsproxy.support.CommonsQueryCountLoggingServletFilter</filter-class>
<!-- Default to DEBUG level. Use the "logLevel" filter parameter to change the log level. -->
</filter>
<filter-mapping>
<filter-name>queryCountFilter</filter-name>
<url-pattern>*</url-pattern>
</filter-mapping>
6.4. DataSource
Create a proxy datasource that performs the following actions:
-
Log query execution to
System.outas JSON -
Collect query metrics
@Bean
public DataSource dataSource(DataSource actualDataSource) {
return ProxyDataSourceBuilder
.create(actualDataSource)
.name("dataSource")
.logQueryToSysOut() // log query execution to System.out
.asJson()
.countQuery() // collect query metrics
.build();
}
applicationContext.xml):<bean id="dataSource" primary="true" class="net.ttddyy.dsproxy.support.ProxyDataSource">
<property name="dataSource" ref="actualDataSource"/>
<property name="proxyConfig" ref="proxyConfig"/>
</bean>
<bean id="proxyConfig" factory-bean="proxyConfigSupport" factory-method="create"/>
<bean id="proxyConfigSupport" class="net.ttddyy.dsproxy.support.ProxyConfigSpringXmlSupport">
<property name="dataSourceName" value="my-ds"/>
<property name="queryListener" ref="queryListeners"/>
</bean>
<bean id="queryListeners" class="net.ttddyy.dsproxy.listener.ChainListener">
<property name="listeners">
<list>
<bean class="net.ttddyy.dsproxy.listener.logging.SystemOutQueryLoggingListener">
<property name="writeAsJson" value="true" />
</bean>
<bean class="net.ttddyy.dsproxy.listener.DataSourceQueryCountListener"/>
</list>
</property>
</bean>
6.5. Controller
Write a simple controller that returns query metrics as JSON using Spring Boot.
@RestController
public class MyController {
@RequestMapping
public QueryCount index() throws Exception {
// do some database operations
return QueryCountHolder.getGrandTotal();
}
}
6.6. Taglib (optional)
Display query metrics in a JSP page.
<%@ taglib prefix="dsp" uri="http://www.ttddyy.net/dsproxy/tags" %>
<dsp:metrics metric="select"/> - Select
<dsp:metrics metric="update" dataSource="FOO" /> - Number of update queries for datasource FOO
<dsp:metrics metric="total"/> - Total queries
<dsp:metrics metric="time"/> - Total time
6.7. Accessing the application
> curl http://localhost:8080
{"select":1,"insert":0,"update":0,"delete":0,"other":0,"total":1,"failure":0,"success":1,"time":6}
6.8. More examples
See more examples in the datasource-proxy-examples project.
7. Development
GitHub repository: https://github.com/jdbc-observations/datasource-proxy
8. Changelog
1.11.0
New Features
-
Filtering support for query logging (Issue-127).
Thanks Frederik Bülthoff for this contribution!!
Improvements
-
ProxyDataSource#closeto unwrap and delegatecloseto underlyingDataSourceif needed (Issue-129).When
ProxyDataSource#closeis called, and the originalDataSourcedoes not implementCloseableorAutoCloseable, the proxy now attempts to unwrap the originalDataSourceto one that does, in order to delegate theclosecall appropriately.Thanks Réda Housni Alaoui for this contribution!!
1.10.1
Improvements
-
Use monotonic time to calculate the elapsed time (Issue-110).
-
Create
RowSetFactoryonly at initialization (Issue-115).
Thanks Réda Housni Alaoui for this contribution!!
Bug Fixes
-
Do not create a proxy when
ResultSetisnull(Issue-117).
1.10
New Features
-
Added the ability to create a pure proxy
DataSourceobject (Issue-102).The
ProxyDataSourceis a concrete implementation of theDataSourceclass, not a proxy object. To create pure proxies all the way fromDataSourcetoResultSet, a new methodProxyDataSourceBuilder#buildProxy()has been introduced. ThisbuildProxy()method returns a proxyDataSourceobject and utilizesDataSourceProxyLogicto handle the proxy calls.
Improvements
-
Proxy instance is now retrievable from
MethodExecutionContext#getProxy()(Issue-108). -
Improved handling of
equalsfor proxy objects (Issue-105). -
Added
Automatic-Module-Name(Issue-101). -
Added
ProxyJdbcObject#getProxyConfig(Issue-103). -
Isolation level retrieval from the connection is now an opt-in feature (Issue-109).
It is disabled by default and does not callConnection#getTransactionIsolation(). To enable isolation level retrieval, useProxyDataSourceBuilder#retrieveIsolation().ProxyDataSourceBuilder#writeIsolation()is effective only whenProxyDataSourceBuilder#retrieveIsolation()is called.ProxyDataSourceBuilder.create(ds).retrieveIsolation().writeIsolation().build(); -
Documentation about the Jakarta namespace (Issue-96).
Bug Fixes
-
Properly call the before/after method callback on
ProxyDataSource#getConnection()(Issue-100).
1.9
New Features
-
Add
formatQuery()method onProxyDataSourceBuilder, which you can specify a callback to modify the query for logging. For example, apply a query formatter. (Issue-94)Formatter formatter = FormatStyle.BASIC.getFormatter(); DataSource ds = ProxyDataSourceBuilder .create(actualDataSource) .formatQuery(formatter::format) .logQueryToSysOut() .build()Thanks Raphael Vullriede for this contribution!!
1.8.1
Improvements
-
Improve
RepeatableReadResultSetProxyLogicThanks Réda Housni Alaoui for this contribution!!
1.8
New Features
-
Add
Isolation levelin query logs. (Issue-74)Logging the isolation level is disabled by default.
To enable, calllogIsolationLevel(true)onProxyDataSourceBuilder.ProxyDataSourceBuilder.create(datasource) .logQueryBySlf4j(SLF4JLogLevel.INFO) .logIsolationLevel(true) // <== .build();Thanks Alexei Brinza for this contribution!!
-
Add support for Log4j2. (Issue-78)
Thanks Ivan Jose Sanchez Pagador for this contribution!!
Improvements
-
Add
ProxyDataSource#getDataSource()method to return the original data source. (Issue-81)
Bug Fixes
-
Support
isWrapperForandunwrapforProxyDataSourceitself (Issue-82)
1.6
Improvements
-
GlobalConnectionIdManageris added. This id-manager generates unique connection ids(sequentially increasing number) across datasources within JVM. -
On
JdbcLifecycleEventListener, method names have updated to map invoked method name only. Classname has removed in order to improve the interception for overloaded methods.The callback method is determined by invoked method name. See the details on below "Breaking Changes" section.
Breaking Changes
-
JdbcLifecycleEventListenermethod names have changed.
(e.g.:beforeGetConnectionOnDataSource()⇒beforeGetConnection())Previously, the method names on the listener had convention of:
[before|after]<MethodName>On<ClassName>
e.g.:beforeGetConnectionOnDataSource()
This has changed to[before|after]<MethodName>
e.g.:beforeGetConnection()For overloaded methods (e.g.:
Statement#executeandPreparedStatement#execute), please reference the documentation: Overloaded methods
1.5.1
New Features and Improvements
-
Add custom value store on
ExecutionInfoandMethodExecutionContextMainly used for passing values between before and after listener callback. -
StopwatchandStopwatchFactoryare added for elapsed time calculation.
Elapsed time calculation for query(ExecutionInfo#getElapsedTime) and method(MethodExecutionContext.getElapsedTime()) have been updated to useStopwatchclass.
Stopwatchis created byStopwatchFactory, and it is configurable inProxyConfig.
Two implementation classes are available -SystemStopwatchFactorywhich createsSystemStopwatchandNanoTimeStopwatchFactorywhich createsNanoTimeStopwatch.
SystemStopwatchusesSystem.currentTimeMillis()andNanoTimeStopwatchusesSystem.nanoTime()to calculate elapsed time. Default is set toSystemStopwatchFactory.
SlowQueryListener needs to set StopwatchFactory independently from ProxyConfig in order to calculate
ExecutionInfo#getElapsedTime() for running slow queries.
|
1.5
-
JdbcLifecycleEventListeneris added.
This listener defines before/after callback methods for all of proxy classes(DataSource,Connection,Statement,PreparedStatement,CallableStatement, andResultSet).
See details on JdbcLifecycleEventListener.
1.4.10
-
Fix empty log line for
getGeneratedKeys()andgetResultSet()method calls. (Issue-54)
1.4.9
-
Fix memory leak in slow query listener. (Issue-53)
Thanks wuwen (https://github.com/wuwen5) for this contribution!!
1.4.8
-
Populate
ExecutionInfo#elapsedTimewhen query execution has failed. -
Change
ConnectionIdManager#getId()to returnStringinstead oflong.
1.4.7
-
Added
CachedRowSetResultSetProxyLogicto provide repeatable read onResultSetusingjavax.sql.rowset.CachedRowSetas its underlying implementation.
See details on Repeatable read ResultSet with CachedRowSetResultSetProxyLogic.
1.4.6
-
Method and parameter replacement in
MethodExecutionListener.
See details on Replace the invoking method and parameters. -
Updated Auto-retrieve generated keys feature. Auto-retrieval is now only called when key-generation is specified.
Following methods specify key-generation:-
Connection#prepareStatement(String, int[]) -
Connection#prepareStatement(String, String[]) -
Statement#execute(String, int[]) -
Statement#execute(String, String[]) -
Statement#executeUpdate(String, int[]) -
Statement#executeUpdate(String, String[]) -
Statement#executeLargeUpdate(String, int) -
Statement#executeLargeUpdate(String, int[]) -
Statement#executeLargeUpdate(String, String[])
-
-
Make generated-keys auto-retrieval for batch execution configurable.
-
ProxyDataSourceBuilder#retrieveGeneratedKeysForBatch-
batch execution for
Statement: auto-retrieval is set tofalseto default -
batch execution for
Prepared/Callable: auto-retrieval is set totrueto default
-
-
1.4.5
-
ConnectionInfonow has information whether the connection is closed or not. -
Generated keys support.
ResultSetforgetGeneratedKeys()now has own configuration. See details on Generated Keys
Thanks Réda Housni Alaoui(@reda-alaoui) for this contribution!!
1.4.4
-
Added
TracingMethodListenerthat prints out JDBC API interaction. See details on Tracing Method Listener. -
ProxyConfigSpringXmlSupportis added to support creatingProxyConfigbean in XML based spring config.
<bean id="proxyConfig"
factory-bean="proxyConfigSupport"
factory-method="create"/>
<bean id="proxyConfigSupport" class="net.ttddyy.dsproxy.support.ProxyConfigSpringXmlSupport">
<property name="dataSourceName" value="my-ds"/>
<property name="queryListener" ref="..."/>
<property name="methodListener" ref="..."/>
</bean>
1.4.3
-
QueryLoggingListeners(Commons, SLF4J, JUL) added overridable
loggingConditioncallback(boolean supplier) that simply decides whether to skip entire logging logic based on the current log level set on its logger.e.g.: when
SLF4JQueryLoggingListenerwrites SQL in DEBUG level, but the logger is set to INFO(more serious than DEBUG), then it will NOT perform logging logic including constructing log statement, etc. -
Proxying
ResultSetis refactored to align how other proxies are managed.Also, existing resultset-proxy is renamed to
RepeatableReadResultSetProxyLogic. As part of refactoring,ResultSetProxyJdbcProxyFactoryis removed. To enable proxyingResultSet,ProxyDataSourceBuildernow has#proxyResultSet()and#repeatableReadResultSet()methods.// before builder.jdbcProxyFactory(new ResultSetProxyJdbcProxyFactory()).build(); // new builder.repeatableReadResultSet().build(); // or builder.proxyResultSet(new RepeatableReadResultSetProxyFactory()).build(); -
ProxyConfigis added to represent all proxy related configurations (datasource name, listeners, proxy factory, connection id manager). All values onInterceptorHolderare moved toProxyConfigandInterceptorHolderclass is removed. -
MethodExecutionListeneris added.MethodExecutionListeneris a new type of listener that intercepts JDBC API calls: -Connection,Statement,PreparedStatement,CallableStatement: All methods -ResultSet: All methods when result set proxy is enabled. (ProxyDataSourceBuilder#[proxyResultSet()|repeatableReadResultSet()]) -ProxyDataSource:getConnection()methodlisteners can be registered via
ProxyDataSourceBuilder#methodListener().builder.methodListener(myMethodListener).build(); -
ProxyDataSourceBuilderhas addedbeforeMethod(),afterMethod(),beforeQuery(), andafterQuery()methods. These methods help inlining listener definitions especially with Java8 Lambda expression.ProxyDataSourceBuilder .create(actualDataSource) .name("MyDS") .proxyResultSet() // apply listener on resultset // register MethodExecutionListener .afterMethod(executionContext -> { Method method = executionContext.getMethod(); Class<?> targetClass = executionContext.getTarget().getClass(); System.out.println(targetClass.getSimpleName() + "#" + method.getName()); }) // register QueryExecutionListener .afterQuery((execInfo, queryInfoList) -> { System.out.println("Query took " + execInfo.getElapsedTime() + "msec"); }) .build();sample output:
# code: Connection conn = ds.getConnection(); PreparedStatement ps = conn.prepareStatement("INSERT INTO users (id, name) VALUES (?, ?)"); ps.setString(2, "FOO"); ps.setInt(1, 3); ps.addBatch(); ps.setInt(1, 4); ps.setString(2, "BAR"); ps.addBatch(); ps.executeBatch(); ps.close(); conn.close();# output: ProxyDataSource#getConnection JDBCConnection#prepareStatement JDBCPreparedStatement#setString JDBCPreparedStatement#setInt JDBCPreparedStatement#addBatch JDBCPreparedStatement#setInt JDBCPreparedStatement#setString JDBCPreparedStatement#addBatch JDBCPreparedStatement#executeBatch Query took 1msec JDBCPreparedStatement#close JDBCConnection#close
1.4.2
-
Assign connection ID on each connection
When a connection is obtained from DataSource(
DataSource.getConnection()), sequentially increasing unique number is assigned as its connection ID. (default implementation:DefaultConnectionIdManager) The connection ID is printed asConnectionin logging. -
Remove methods that don’t take
dataSourceNameonJdbcProxyFactoryInstead, you need to specify
null, empty String, or datasource name to thedataSourceNameparameter. Following methods are removed:-
Connection createConnection(Connection connection, InterceptorHolder interceptorHolder); -
Statement createStatement(Statement statement, InterceptorHolder interceptorHolder); -
PreparedStatement createPreparedStatement(PreparedStatement preparedStatement, String query, InterceptorHolder interceptorHolder);
-
-
DataSourceQueryCountListenernow takes a strategy to resolveQueryCount.Default uses
ThreadQueryCountHolderthat uses thread local to holdQueryCount. This behaves same as before that theQueryCountholds per request counts(servlet request-response lifecycle).SingleQueryCountHolderuses single instance to hold count values. Therefore, this holds total accumulated values from all threads. -
Update
SlowQueryListenerto use daemon threads as default.It is configurable by
SlowQueryListener#setUseDaemonThreadmethod.
1.4.1
-
Add
setLog/setLoggerto{Commons|SLF4J|JUL}QueryLoggingListenerto allow users to set custom logger. Also added getters as well. -
Update
~QueryCountLoggingServletFilterto allow configuring logger by name -
Add query count logging implementation for JUL(Java Util Logging)
-
JULQueryCountLoggingHandlerInterceptor -
JULQueryCountLoggingRequestListener -
JULQueryCountLoggingServletFilter
-
-
Fix writing log with
nullin parameter set methods. (e.g:setString(1, null);) -
Add
SlowQueryListenerthat triggers callback method when query takes longer than specified threshold time.Also, added slow query logging listeners:
CommonsSlowQueryListenerJULSlowQueryListenerSLF4JSlowQueryListenerSystemOutSlowQueryListenerIn
ProxyDataSourceBuilder, these methods are added:logSlowQueryByCommons()logSlowQueryByJUL()logSlowQueryBySlf4j()logSlowQueryToSysOut() -
Add support to easily apply formatters on each query for logging.
DefaultQueryLogEntryCreator#formatQuery()method has added. Subclass can override this method to provides formatted query.Example with
BasicFormatterImplin Hibernate.// set this instance to logging listeners public class PrettyQueryEntryCreator extends DefaultQueryLogEntryCreator { private Formatter formatter = FormatStyle.BASIC.getFormatter(); // from hibernate @Override protected String formatQuery(String query) { return this.formatter.format(query); } } -
Add multiline output support for query logging.
DefaultQueryLogEntryCreatornow hassetMultiline()method, andProxyDataSourceBuilderalso has addedmultiline()method. When multiline is enabled, logged query entries become multi lined.sample log output:
Name:MyDS, Time:0, Success:True Type:Prepared, Batch:True, QuerySize:1, BatchSize:2 Query:["INSERT INTO users (id, name) VALUES (?, ?)"] Params:[(1,foo),(2,bar)]set up with builder:
DataSource dataSource = ProxyDataSourceBuilder .create(actualDataSource) .logQueryByCommons(INFO) .logSlowQueryByCommons(10, TimeUnit.MINUTES) .multiline() // applies to both query logger and slow query logger .build(); -
Deprecate
{Commons|SLF4J|JUL}QueryLoggingListener#resetLogger()methods.Use newly added
setLog(String)orsetLogger(String)method instead.
1.4
-
Move logging related listeners to sub package
-
from
net.ttddyy.dsproxy.listenertonet.ttddyy.dsproxy.listener.logging
-
-
classes for logging entry creation has been updated
-
QueryLogEntryCreator#getLogEntryAsJsonhas removed. -
JSON style log entry creators is pulled up to
DefaultJsonQueryLogEntryCreator -
To use JSON style logging, you can set the
QueryLogEntryCreatorto[Commons|SLF4J|JUL|SystemOut]QueryLoggingListener#setQueryLogEntryCreator() -
OracleOutputParameterLogEntryCreatorhas been split toOutputParameterLogEntryCreatorandOutputParameterJsonLogEntryCreator
-
-
DefaultQueryLogEntryCreator#writeParamsForSingleEntry()has split towriteParamsEntryForSinglePreparedEntry()andwriteParamsForSingleCallableEntry() -
Do not include parameter index for logging prepared statement.
Before(v1.3.3):
..., Params:[(1=10,2=foo),(1=20,2=bar)] ..., Params:[(1=30,2=FOO),(1=40,2=BAR)]..., "params":[{"1":"10","2":"foo"},{"1":"20","2":"bar"}]} ..., "params":[{"1":"30","2":"FOO"},{"1":"40","2":"BAR"}]}Now:
..., Params:[(10,foo),(20,bar)] ..., Params:[(30,FOO),(40,BAR)]..., "params":[["10","foo"],["20","bar"]]} ..., "params":[["30","FOO"],["40","BAR"]]} -
Add
JULQueryLoggingListenerwhich uses JUL(Java Utils Logging) to log executed queries -
Update logging for
setNullandregisterOutParameterto include sqltypee.g.:
NULL(VARCHAR),OUTPUT(VARCHAR[12]) -
ResultSetProxyJdbcProxyFactoryto create a proxyResultSetthat can be consumed more than once.Thanks Liam Williams for this contribution!!
-
QueryExecutionListenerreceives same instance ofExecutionInfoinbeforeQueryandafterQuerymethods
1.3.2
-
add
CommonsOracleOutputParameterLoggingListener -
add new listener for oracle to log output params.
CommonsOracleOutputParameterLoggingListener
1.3.1
-
make logger name configurable in
CommonsQueryLoggingListenerandSLF4JQueryLoggingListener -
setNullandregisterOutParameterreceives descriptive string value inQueryInfo#getQueryArgsList(temporal implementation) -
ExecutionInfowill have access to the statement/prepared/callable object used by the execution
1.3
-
update minimum jdk to java6+
-
add java8 new jdbc API (JDBC 4.2)
-
new JNDI support class:
ProxyDataSourceObjectFactory -
new fluent API builder:
ProxyDataSourceBuilder -
logging:
-
update log format
-
add json format
-
more entries: statement-type, batch, batch-size
-
new logger for System.Out
-
-
change metric names: call ⇒ total, elapsedTime ⇒ time, added success, failure, etc.
-
rename
~QueryCountLoggingFilterto~QueryCountServletFilter -
remove deprecated methods
1.2.1
-
fixed prepared statement getting already executed queries in listener (Issue #9)