Skip to content

CorrelationIdConnectionProvider

Paulo Gomes da Cruz Junior edited this page Aug 27, 2026 · 1 revision

CorrelationIdConnectionProvider

Overview

A Hibernate ConnectionProvider decorator that injects the current Micrometer B3 trace ID into the PostgreSQL transaction via the app.correlation_id GUC. Part of the Correlation ID Audit Propagation pattern.

Public Interface

Constructor

public CorrelationIdConnectionProvider(ConnectionProvider delegate, Tracer tracer)
Parameter Type Description
delegate ConnectionProvider The real pooled provider (e.g. DatasourceConnectionProviderImpl)
tracer Tracer Micrometer tracer providing the current B3 span

Methods

getConnection(): Connection

Returns a pooled JDBC connection. When a Micrometer span is active with a non-blank trace ID, wraps the connection in DeferredCorrelationIdConnection, which binds the trace ID lazily on first statement use. Without an active span, a noop span, or a usable trace ID, returns the raw delegate connection unchanged.

configure(Map<String> properties)

Forwards configuration to the delegate if it implements Configurable.

stop()

Forwards lifecycle stop to the delegate if it implements Stoppable.

closeConnection(Connection conn)

Delegates directly to the underlying provider.

supportsAggressiveRelease(): boolean

Delegates to the underlying provider.

isUnwrappableAs(Class<?>): boolean

Delegates to the underlying provider.

unwrap(Class<T>): T

Delegates to the underlying provider.

Internal Structure

DeferredCorrelationIdConnection

A private final inner class implementing java.sql.Connection. Wraps the real connection and executes SELECT set_config('app.correlation_id', ?, true) exactly once, immediately before the first createStatement, prepareStatement, prepareCall, or setSavepoint call.

Every other Connection method is forwarded verbatim to the target.

Binding SQL

SELECT set_config('app.correlation_id', ?, true)

The third argument true marks the setting as transaction-local (is_local), so PostgreSQL discards it on commit/rollback. No explicit reset is required.

Dependencies

Inward

  • io.micrometer.tracing.Tracer — reads currentSpan() for trace context
  • io.micrometer.tracing.Span — reads context().traceId()
  • org.hibernate.engine.jdbc.connections.spi.ConnectionProvider — decorator target
  • org.hibernate.service.spi.Configurable / Stoppable — lifecycle forwarding

Outward

  • GlobalConfiguration.correlationIdConnectionProviderCustomizer — creates and registers this provider as a HibernatePropertiesCustomizer bean
  • V1.0.6__audit_correlation_id.sql — the trigger reads current_setting('app.correlation_id', true)

Known Constraints

  • GraalVM-safe: No reflection, no proxies, no runtime bytecode. All dependencies are constructor-injected.
  • Deferred binding is required: Eager binding breaks SERIALIZABLE isolation. See Correlation-ID Audit Trigger — Deferred Binding and Test Hygiene for details.
  • Pool-wide auto-commit=false required: set_config(..., true) needs an active transaction block.
  • Native-image not validated: GraalVM native-image compilation and runtime validation remain a follow-up task.
  • @SuppressWarnings({"deprecation", "removal"}) on customizer: The DatasourceConnectionProviderImpl constructor and configure() method use deprecated Hibernate APIs; these are intentional and must not be "simplified."

Usage Example

Registered automatically via GlobalConfiguration:

@Bean
@SuppressWarnings({"deprecation", "removal"})
public HibernatePropertiesCustomizer correlationIdConnectionProviderCustomizer(
    DataSource dataSource, Tracer tracer) {
  return hibernateProperties -> {
    DatasourceConnectionProviderImpl delegate = new DatasourceConnectionProviderImpl();
    delegate.setDataSource(dataSource);
    CorrelationIdConnectionProvider provider = new CorrelationIdConnectionProvider(delegate, tracer);
    provider.configure(hibernateProperties);
    hibernateProperties.put(JdbcSettings.CONNECTION_PROVIDER, provider);
  };
}

Edge Cases

  • No active span: Returns raw connection, current_setting('app.correlation_id', true) stays NULL in the trigger.
  • Noop span: Same as no active span — provider checks span.isNoop().
  • Blank trace ID: Returns raw connection without wrapping.
  • Connection reuse (Hikari pool): Transaction-local GUC ensures no bleed between sequential transactions on the same physical connection.

Related Pages

Sources

Merged via PR #1258.

Waste Plus Wiki

Home

Architecture

Codes

Decisions

Knowledge Base

Development

Changes

Clone this wiki locally