From 7dfad3a22586e70da3f0035a704e32c03e6a7e98 Mon Sep 17 00:00:00 2001 From: Ryszard Trojnacki Date: Wed, 8 Oct 2025 16:21:29 +0200 Subject: [PATCH 1/3] Add `DataSourcePoolNewConnectionListener` to set up newly created connection by `DataSourcePool`. --- .../ebean/datasource/DataSourceBuilder.java | 10 ++ .../io/ebean/datasource/DataSourceConfig.java | 12 ++ .../DataSourcePoolNewConnectionListener.java | 21 ++++ .../ebean/datasource/pool/ConnectionPool.java | 8 ++ ...nnectionPoolNewConnectionListenerTest.java | 112 ++++++++++++++++++ 5 files changed, 163 insertions(+) create mode 100644 ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java create mode 100644 ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java index 3a53a25..bfb0f13 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java @@ -379,6 +379,11 @@ default DataSourceBuilder listener(DataSourcePoolListener listener) { @Deprecated(forRemoval = true) DataSourceBuilder setListener(DataSourcePoolListener listener); + /** + * Set the connection listener to use. + */ + DataSourceBuilder connectionListener(DataSourcePoolNewConnectionListener connectionListener); + /** * Set a SQL statement used to test the database is accessible. *

@@ -933,6 +938,11 @@ default String driverClassName() { */ DataSourcePoolListener getListener(); + /** + * Return the new connection listener to use. + */ + DataSourcePoolNewConnectionListener getConnectionListener(); + /** * Return a SQL statement used to test the database is accessible. *

diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java index ac06b3e..1f607a5 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java @@ -84,6 +84,7 @@ public class DataSourceConfig implements DataSourceBuilder.Settings { private List initSql; private DataSourceAlert alert; private DataSourcePoolListener listener; + private DataSourcePoolNewConnectionListener connectionListener; private Properties clientInfo; private String applicationName; private boolean shutdownOnJvmExit; @@ -477,6 +478,17 @@ public DataSourceConfig setListener(DataSourcePoolListener listener) { return this; } + @Override + public DataSourcePoolNewConnectionListener getConnectionListener() { + return connectionListener; + } + + @Override + public DataSourceBuilder connectionListener(DataSourcePoolNewConnectionListener connectionListener) { + this.connectionListener= connectionListener; + return this; + } + @Override public String getHeartbeatSql() { return heartbeatSql; diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java new file mode 100644 index 0000000..a57fff8 --- /dev/null +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java @@ -0,0 +1,21 @@ +package io.ebean.datasource; + +import java.sql.Connection; + +/** + * A {@link DataSourcePool} listener which allows you to hook on the create connections process of the pool. + */ +public interface DataSourcePoolNewConnectionListener { + /** + * Called after a connection has been created, before any initialization. + * @param connection the created connection + */ + default void onCreatedConnection(Connection connection) {} + + /** + * Called after a connection has been initialized (after onCreatedConnection) and all settings applied. + * @param connection the created connection + */ + default void onAfterInitialized(Connection connection) {} + +} diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index ce438ea..41f3d9c 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -46,6 +46,7 @@ interface Heartbeat { */ private final DataSourceAlert notify; private final DataSourcePoolListener poolListener; + private final DataSourcePoolNewConnectionListener connectionListener; private final List initSql; private final String user; private final String schema; @@ -109,6 +110,7 @@ interface Heartbeat { this.name = name; this.notify = params.getAlert(); this.poolListener = params.getListener(); + this.connectionListener = params.getConnectionListener(); this.autoCommit = params.isAutoCommit(); this.readOnly = params.isReadOnly(); this.failOnStart = params.isFailOnStart(); @@ -434,6 +436,9 @@ private void testConnection() { * Initializes the connection we got from the driver. */ private Connection initConnection(Connection conn) throws SQLException { + if(connectionListener!=null) { + connectionListener.onCreatedConnection(conn); + } conn.setAutoCommit(autoCommit); // isolation level is set globally for all connections (at least for H2) and // you will need admin rights - so we do not change it, if it already matches. @@ -470,6 +475,9 @@ private Connection initConnection(Connection conn) throws SQLException { } } } + if(connectionListener!=null) { + connectionListener.onAfterInitialized(conn); + } return conn; } diff --git a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java new file mode 100644 index 0000000..fcb910b --- /dev/null +++ b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java @@ -0,0 +1,112 @@ +package io.ebean.datasource.pool; + +import io.ebean.datasource.DataSourceConfig; +import io.ebean.datasource.DataSourcePoolNewConnectionListener; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.util.HashMap; +import static org.junit.jupiter.api.Assertions.*; + +class ConnectionPoolNewConnectionListenerTest { + private ConnectionPool pool; + + private final HashMap createdConnections = new HashMap<>(); + private final HashMap afterConnections = new HashMap<>(); + + ConnectionPoolNewConnectionListenerTest() { + pool = createPool(); + } + + + private ConnectionPool createPool() { + + DataSourceConfig config = new DataSourceConfig(); + config.setDriver("org.h2.Driver"); + config.setUrl("jdbc:h2:mem:tests"); + config.setUsername("sa"); + config.setPassword(""); + config.setMinConnections(1); + config.setMaxConnections(5); + config.connectionListener(new DataSourcePoolNewConnectionListener() { + @Override + public void onCreatedConnection(Connection connection) { + synchronized (createdConnections) { + createdConnections.put(connection, 1 + createdConnections.getOrDefault(connection, 0)); + createdConnections.notifyAll(); + } + } + + @Override + public void onAfterInitialized(Connection connection) { + synchronized (afterConnections) { + afterConnections.put(connection, 1 + afterConnections.getOrDefault(connection, 0)); + afterConnections.notifyAll(); + } + } + }); + + return new ConnectionPool("initialize", config); + } + + @AfterEach + public void after() { + pool.shutdown(); + } + + @Test + public void initializeNewConnectionTest() { + // Min connections is 1 so one should be created on pool initialization + synchronized (createdConnections) { + assertEquals(1, createdConnections.size()); + assertEquals(1, afterConnections.size()); + } + + try (Connection connection = pool.getConnection()) { + assertNotNull(connection); + synchronized (createdConnections) { + assertEquals(1, createdConnections.size()); + } + synchronized (afterConnections) { + assertEquals(1, afterConnections.size()); + } + } catch (Exception e) { + fail(e.getMessage()); + } + + try (Connection connection = pool.getConnection()) { + assertNotNull(connection); + synchronized (createdConnections) { + assertEquals(1, createdConnections.size()); + } + synchronized (afterConnections) { + assertEquals(1, afterConnections.size()); + } + + try (Connection connection2 = pool.getConnection()) { + assertNotNull(connection2); + synchronized (createdConnections) { + assertEquals(2, createdConnections.size()); + } + synchronized (afterConnections) { + assertEquals(2, afterConnections.size()); + } + } + } catch (Exception e) { + fail(e.getMessage()); + } + synchronized (createdConnections) { + for (var entry : createdConnections.entrySet()) { + // It should be always 1 + assertEquals(1, entry.getValue()); + } + } + synchronized (afterConnections) { + for (var entry : afterConnections.entrySet()) { + // It should be always 1 + assertEquals(1, entry.getValue()); + } + } + } +} From 457fad2afa88744f6dfcda0017d2151a17bb0caa Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 5 Nov 2025 19:50:21 +1300 Subject: [PATCH 2/3] Format only changes --- .../ebean/datasource/DataSourcePoolNewConnectionListener.java | 1 + .../main/java/io/ebean/datasource/pool/ConnectionPool.java | 4 ++-- .../pool/ConnectionPoolNewConnectionListenerTest.java | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java index a57fff8..c8cf470 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java @@ -6,6 +6,7 @@ * A {@link DataSourcePool} listener which allows you to hook on the create connections process of the pool. */ public interface DataSourcePoolNewConnectionListener { + /** * Called after a connection has been created, before any initialization. * @param connection the created connection diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 41f3d9c..22a4ef1 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -436,7 +436,7 @@ private void testConnection() { * Initializes the connection we got from the driver. */ private Connection initConnection(Connection conn) throws SQLException { - if(connectionListener!=null) { + if (connectionListener != null) { connectionListener.onCreatedConnection(conn); } conn.setAutoCommit(autoCommit); @@ -475,7 +475,7 @@ private Connection initConnection(Connection conn) throws SQLException { } } } - if(connectionListener!=null) { + if (connectionListener != null) { connectionListener.onAfterInitialized(conn); } return conn; diff --git a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java index fcb910b..bd3c945 100644 --- a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java +++ b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.*; class ConnectionPoolNewConnectionListenerTest { + private ConnectionPool pool; private final HashMap createdConnections = new HashMap<>(); From e5bfa99a137cd8e05eeafac173b7acda4826a978 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Wed, 5 Nov 2025 20:13:13 +1300 Subject: [PATCH 3/3] Rename DataSourcePoolNewConnectionListener to NewConnectionInitializer Rename class and method names only --- .../java/io/ebean/datasource/DataSourceBuilder.java | 6 +++--- .../java/io/ebean/datasource/DataSourceConfig.java | 10 +++++----- ...onListener.java => NewConnectionInitializer.java} | 10 +++++++--- .../io/ebean/datasource/pool/ConnectionPool.java | 12 ++++++------ .../ConnectionPoolNewConnectionListenerTest.java | 8 ++++---- 5 files changed, 25 insertions(+), 21 deletions(-) rename ebean-datasource-api/src/main/java/io/ebean/datasource/{DataSourcePoolNewConnectionListener.java => NewConnectionInitializer.java} (73%) diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java index bfb0f13..a508c6f 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceBuilder.java @@ -380,9 +380,9 @@ default DataSourceBuilder listener(DataSourcePoolListener listener) { DataSourceBuilder setListener(DataSourcePoolListener listener); /** - * Set the connection listener to use. + * Set the connection initializer to use. */ - DataSourceBuilder connectionListener(DataSourcePoolNewConnectionListener connectionListener); + DataSourceBuilder connectionInitializer(NewConnectionInitializer connectionListener); /** * Set a SQL statement used to test the database is accessible. @@ -941,7 +941,7 @@ default String driverClassName() { /** * Return the new connection listener to use. */ - DataSourcePoolNewConnectionListener getConnectionListener(); + NewConnectionInitializer getConnectionInitializer(); /** * Return a SQL statement used to test the database is accessible. diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java index 1f607a5..83068a0 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourceConfig.java @@ -84,7 +84,7 @@ public class DataSourceConfig implements DataSourceBuilder.Settings { private List initSql; private DataSourceAlert alert; private DataSourcePoolListener listener; - private DataSourcePoolNewConnectionListener connectionListener; + private NewConnectionInitializer connectionInitializer; private Properties clientInfo; private String applicationName; private boolean shutdownOnJvmExit; @@ -479,13 +479,13 @@ public DataSourceConfig setListener(DataSourcePoolListener listener) { } @Override - public DataSourcePoolNewConnectionListener getConnectionListener() { - return connectionListener; + public NewConnectionInitializer getConnectionInitializer() { + return connectionInitializer; } @Override - public DataSourceBuilder connectionListener(DataSourcePoolNewConnectionListener connectionListener) { - this.connectionListener= connectionListener; + public DataSourceBuilder connectionInitializer(NewConnectionInitializer connectionInitializer) { + this.connectionInitializer = connectionInitializer; return this; } diff --git a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java b/ebean-datasource-api/src/main/java/io/ebean/datasource/NewConnectionInitializer.java similarity index 73% rename from ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java rename to ebean-datasource-api/src/main/java/io/ebean/datasource/NewConnectionInitializer.java index c8cf470..31990df 100644 --- a/ebean-datasource-api/src/main/java/io/ebean/datasource/DataSourcePoolNewConnectionListener.java +++ b/ebean-datasource-api/src/main/java/io/ebean/datasource/NewConnectionInitializer.java @@ -5,18 +5,22 @@ /** * A {@link DataSourcePool} listener which allows you to hook on the create connections process of the pool. */ -public interface DataSourcePoolNewConnectionListener { +public interface NewConnectionInitializer { /** * Called after a connection has been created, before any initialization. + * * @param connection the created connection */ - default void onCreatedConnection(Connection connection) {} + default void preInitialize(Connection connection) { + } /** * Called after a connection has been initialized (after onCreatedConnection) and all settings applied. + * * @param connection the created connection */ - default void onAfterInitialized(Connection connection) {} + default void postInitialize(Connection connection) { + } } diff --git a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java index 22a4ef1..bcd3c95 100644 --- a/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java +++ b/ebean-datasource/src/main/java/io/ebean/datasource/pool/ConnectionPool.java @@ -46,7 +46,7 @@ interface Heartbeat { */ private final DataSourceAlert notify; private final DataSourcePoolListener poolListener; - private final DataSourcePoolNewConnectionListener connectionListener; + private final NewConnectionInitializer connectionInitializer; private final List initSql; private final String user; private final String schema; @@ -110,7 +110,7 @@ interface Heartbeat { this.name = name; this.notify = params.getAlert(); this.poolListener = params.getListener(); - this.connectionListener = params.getConnectionListener(); + this.connectionInitializer = params.getConnectionInitializer(); this.autoCommit = params.isAutoCommit(); this.readOnly = params.isReadOnly(); this.failOnStart = params.isFailOnStart(); @@ -436,8 +436,8 @@ private void testConnection() { * Initializes the connection we got from the driver. */ private Connection initConnection(Connection conn) throws SQLException { - if (connectionListener != null) { - connectionListener.onCreatedConnection(conn); + if (connectionInitializer != null) { + connectionInitializer.preInitialize(conn); } conn.setAutoCommit(autoCommit); // isolation level is set globally for all connections (at least for H2) and @@ -475,8 +475,8 @@ private Connection initConnection(Connection conn) throws SQLException { } } } - if (connectionListener != null) { - connectionListener.onAfterInitialized(conn); + if (connectionInitializer != null) { + connectionInitializer.postInitialize(conn); } return conn; } diff --git a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java index bd3c945..2c71087 100644 --- a/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java +++ b/ebean-datasource/src/test/java/io/ebean/datasource/pool/ConnectionPoolNewConnectionListenerTest.java @@ -1,7 +1,7 @@ package io.ebean.datasource.pool; import io.ebean.datasource.DataSourceConfig; -import io.ebean.datasource.DataSourcePoolNewConnectionListener; +import io.ebean.datasource.NewConnectionInitializer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; @@ -30,9 +30,9 @@ private ConnectionPool createPool() { config.setPassword(""); config.setMinConnections(1); config.setMaxConnections(5); - config.connectionListener(new DataSourcePoolNewConnectionListener() { + config.connectionInitializer(new NewConnectionInitializer() { @Override - public void onCreatedConnection(Connection connection) { + public void preInitialize(Connection connection) { synchronized (createdConnections) { createdConnections.put(connection, 1 + createdConnections.getOrDefault(connection, 0)); createdConnections.notifyAll(); @@ -40,7 +40,7 @@ public void onCreatedConnection(Connection connection) { } @Override - public void onAfterInitialized(Connection connection) { + public void postInitialize(Connection connection) { synchronized (afterConnections) { afterConnections.put(connection, 1 + afterConnections.getOrDefault(connection, 0)); afterConnections.notifyAll();