diff --git a/pom.xml b/pom.xml index 1cb9792e..a6bb4427 100644 --- a/pom.xml +++ b/pom.xml @@ -158,9 +158,9 @@ test - mysql - mysql-connector-java - 8.0.33 + com.mysql + mysql-connector-j + 8.2.0 test @@ -286,6 +286,15 @@ transactionoutbox-spring + + java-21-modules + + [21,) + + + transactionoutbox-virtthreads + + release diff --git a/transactionoutbox-acceptance/pom.xml b/transactionoutbox-acceptance/pom.xml index 64570f7c..99704440 100644 --- a/transactionoutbox-acceptance/pom.xml +++ b/transactionoutbox-acceptance/pom.xml @@ -61,8 +61,8 @@ ojdbc11 - mysql - mysql-connector-java + com.mysql + mysql-connector-j com.h2database diff --git a/transactionoutbox-jooq/pom.xml b/transactionoutbox-jooq/pom.xml index e449c602..c01e5916 100644 --- a/transactionoutbox-jooq/pom.xml +++ b/transactionoutbox-jooq/pom.xml @@ -71,8 +71,8 @@ ojdbc11 - mysql - mysql-connector-java + com.mysql + mysql-connector-j diff --git a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java index d4f16c5f..2e48f70f 100644 --- a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java +++ b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/AbstractAcceptanceTest.java @@ -1,11 +1,12 @@ package com.gruelbox.transactionoutbox.testing; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; import static org.junit.jupiter.api.Assertions.*; import com.gruelbox.transactionoutbox.*; -import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.DriverManager; @@ -22,40 +23,22 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; -import lombok.Builder; import lombok.SneakyThrows; -import lombok.Value; -import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; -import org.hamcrest.MatcherAssert; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Slf4j -public abstract class AbstractAcceptanceTest { +public abstract class AbstractAcceptanceTest extends BaseTest { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractAcceptanceTest.class); private final ExecutorService unreliablePool = new ThreadPoolExecutor(2, 2, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(16)); private static final Random random = new Random(); - protected HikariDataSource dataSource; - - @BeforeEach - final void baseBeforeEach() { - HikariConfig config = new HikariConfig(); - config.setJdbcUrl(connectionDetails().url()); - config.setUsername(connectionDetails().user()); - config.setPassword(connectionDetails().password()); - config.addDataSourceProperty("cachePrepStmts", "true"); - dataSource = new HikariDataSource(config); - } - - @AfterEach - final void baseAfterEach() { - dataSource.close(); - } /** * Uses a simple direct transaction manager and connection manager and attempts to fire an @@ -107,15 +90,15 @@ public void success(TransactionOutboxEntry entry) { outbox.schedule(InterfaceProcessor.class).process(3, "Whee"); try { // Should not be fired until after commit - assertFalse(latch.await(2, TimeUnit.SECONDS)); + assertFalse(latch.await(2, SECONDS)); } catch (InterruptedException e) { fail("Interrupted"); } }); // Should be fired after commit - assertTrue(chainedLatch.await(2, TimeUnit.SECONDS)); - assertTrue(latch.await(1, TimeUnit.SECONDS)); + assertTrue(chainedLatch.await(2, SECONDS)); + assertTrue(latch.await(1, SECONDS)); assertTrue(gotScheduled.get()); } @@ -244,7 +227,7 @@ public void success(TransactionOutboxEntry entry) { .schedule(ClassProcessor.class) .process("6")); - MatcherAssert.assertThat(ids, containsInAnyOrder("1", "2", "4", "6")); + assertThat(ids, containsInAnyOrder("1", "2", "4", "6")); } /** @@ -272,7 +255,7 @@ final void dataSourceConnectionProviderReflectionInstantiatorConcreteClass() transactionManager.inTransaction(() -> outbox.schedule(ClassProcessor.class).process(myId)); - assertTrue(latch.await(2, TimeUnit.SECONDS)); + assertTrue(latch.await(2, SECONDS)); assertEquals(List.of(myId), ClassProcessor.PROCESSED); } } @@ -364,7 +347,7 @@ public T requireTransactionReturns( } postCommitHooks.forEach(Runnable::run); - assertTrue(latch.await(2, TimeUnit.SECONDS)); + assertTrue(latch.await(2, SECONDS)); assertEquals(List.of(myId), ClassProcessor.PROCESSED); } } @@ -395,7 +378,7 @@ final void retryBehaviour() throws Exception { () -> { transactionManager.inTransaction( () -> outbox.schedule(InterfaceProcessor.class).process(3, "Whee")); - assertTrue(latch.await(15, TimeUnit.SECONDS)); + assertTrue(latch.await(15, SECONDS)); }); } @@ -467,12 +450,12 @@ final void lastAttemptTime_updatesEveryTime() throws Exception { () -> { transactionManager.inTransaction( () -> outbox.schedule(InterfaceProcessor.class).process(3, "Whee")); - assertTrue(blockLatch.await(10, TimeUnit.SECONDS)); + assertTrue(blockLatch.await(10, SECONDS)); assertTrue( (Boolean) transactionManager.inTransactionReturns( tx -> outbox.unblock(orderedEntryListener.getBlocked().getId()))); - assertTrue(successLatch.await(10, TimeUnit.SECONDS)); + assertTrue(successLatch.await(10, SECONDS)); var orderedEntryEvents = orderedEntryListener.getOrderedEntries(); log.info("The entry life cycle is: {}", orderedEntryEvents); @@ -521,12 +504,12 @@ final void blockAndThenUnblockForRetry() throws Exception { () -> { transactionManager.inTransaction( () -> outbox.schedule(InterfaceProcessor.class).process(3, "Whee")); - assertTrue(blockLatch.await(3, TimeUnit.SECONDS)); + assertTrue(blockLatch.await(3, SECONDS)); assertTrue( (Boolean) transactionManager.inTransactionReturns( tx -> outbox.unblock(latchListener.getBlocked().getId()))); - assertTrue(successLatch.await(3, TimeUnit.SECONDS)); + assertTrue(successLatch.await(3, SECONDS)); }); } @@ -574,79 +557,17 @@ public void success(TransactionOutboxEntry entry) { outbox.schedule(InterfaceProcessor.class).process(i * 10 + j, "Whee"); } })); - assertTrue(latch.await(30, TimeUnit.SECONDS), "Latch not opened in time"); + assertTrue(latch.await(30, SECONDS), "Latch not opened in time"); }); - MatcherAssert.assertThat( + assertThat( "Should never get duplicates running to full completion", duplicates.keySet(), empty()); - MatcherAssert.assertThat( + assertThat( "Only got: " + results.keySet(), results.keySet(), containsInAnyOrder(IntStream.range(0, count * 10).boxed().toArray())); } - protected ConnectionDetails connectionDetails() { - return ConnectionDetails.builder() - .dialect(Dialect.H2) - .driverClassName("org.h2.Driver") - .url( - "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE") - .user("test") - .password("test") - .build(); - } - - protected TransactionManager txManager() { - return TransactionManager.fromDataSource(dataSource); - } - - protected Persistor persistor() { - return Persistor.forDialect(connectionDetails().dialect()); - } - - protected void clearOutbox() { - DefaultPersistor persistor = Persistor.forDialect(connectionDetails().dialect()); - TransactionManager transactionManager = txManager(); - transactionManager.inTransaction( - tx -> { - try { - persistor.clear(tx); - } catch (SQLException e) { - throw new RuntimeException(e); - } - }); - } - - protected void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) - throws Exception { - Thread backgroundThread = - new Thread( - () -> { - while (!Thread.interrupted()) { - try { - // Keep flushing work until there's nothing left to flush - //noinspection StatementWithEmptyBody - while (outbox.flush()) {} - } catch (Exception e) { - log.error("Error flushing transaction outbox. Pausing", e); - } - try { - //noinspection BusyWait - Thread.sleep(250); - } catch (InterruptedException e) { - break; - } - } - }); - backgroundThread.start(); - try { - runnable.run(); - } finally { - backgroundThread.interrupt(); - backgroundThread.join(); - } - } - private static class FailingInstantiator implements Instantiator { private final AtomicInteger attempts; @@ -699,15 +620,4 @@ public Object getInstance(String name) { } } } - - @Value - @Accessors(fluent = true) - @Builder - public static class ConnectionDetails { - String driverClassName; - String url; - String user; - String password; - Dialect dialect; - } } diff --git a/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/BaseTest.java b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/BaseTest.java new file mode 100644 index 00000000..7cf9ed2f --- /dev/null +++ b/transactionoutbox-testing/src/main/java/com/gruelbox/transactionoutbox/testing/BaseTest.java @@ -0,0 +1,106 @@ +package com.gruelbox.transactionoutbox.testing; + +import com.gruelbox.transactionoutbox.*; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.sql.SQLException; +import lombok.Builder; +import lombok.Value; +import lombok.experimental.Accessors; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; + +@Slf4j +public abstract class BaseTest { + + protected HikariDataSource dataSource; + + @BeforeEach + final void baseBeforeEach() { + HikariConfig config = new HikariConfig(); + config.setJdbcUrl(connectionDetails().url()); + config.setUsername(connectionDetails().user()); + config.setPassword(connectionDetails().password()); + config.addDataSourceProperty("cachePrepStmts", "true"); + dataSource = new HikariDataSource(config); + } + + @AfterEach + final void baseAfterEach() { + dataSource.close(); + } + + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(Dialect.H2) + .driverClassName("org.h2.Driver") + .url( + "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;DEFAULT_LOCK_TIMEOUT=60000;LOB_TIMEOUT=2000;MV_STORE=TRUE;DATABASE_TO_UPPER=FALSE") + .user("test") + .password("test") + .build(); + } + + protected TransactionManager txManager() { + return TransactionManager.fromDataSource(dataSource); + } + + protected Persistor persistor() { + return Persistor.forDialect(connectionDetails().dialect()); + } + + protected void clearOutbox() { + DefaultPersistor persistor = Persistor.forDialect(connectionDetails().dialect()); + TransactionManager transactionManager = txManager(); + transactionManager.inTransaction( + tx -> { + try { + persistor.clear(tx); + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + + protected void withRunningFlusher(TransactionOutbox outbox, ThrowingRunnable runnable) + throws Exception { + Thread backgroundThread = + new Thread( + () -> { + while (!Thread.interrupted()) { + try { + // Keep flushing work until there's nothing left to flush + //noinspection StatementWithEmptyBody + while (outbox.flush()) {} + } catch (Exception e) { + log.error("Error flushing transaction outbox. Pausing", e); + } + try { + //noinspection BusyWait + Thread.sleep(250); + } catch (InterruptedException e) { + break; + } + } + }); + backgroundThread.start(); + try { + runnable.run(); + } finally { + backgroundThread.interrupt(); + backgroundThread.join(); + } + } + + @Value + @Accessors(fluent = true) + @Builder + public static class ConnectionDetails { + String driverClassName; + String url; + String user; + String password; + Dialect dialect; + } +} diff --git a/transactionoutbox-testing/src/main/resources/logback-test.xml b/transactionoutbox-testing/src/main/resources/logback-test.xml new file mode 100644 index 00000000..96692642 --- /dev/null +++ b/transactionoutbox-testing/src/main/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n + + + + + + diff --git a/transactionoutbox-virtthreads/pom.xml b/transactionoutbox-virtthreads/pom.xml new file mode 100644 index 00000000..4db8afdb --- /dev/null +++ b/transactionoutbox-virtthreads/pom.xml @@ -0,0 +1,88 @@ + + + + transactionoutbox-parent + com.gruelbox + ${revision} + + 4.0.0 + Transaction Outbox Virtual Threads support + jar + transactionoutbox-virtthreads + A safe implementation of the transactional outbox pattern for Java (core library) + + 21 + 21 + + + + com.gruelbox + transactionoutbox-core + ${project.version} + test + + + + + org.projectlombok + lombok + + + + + com.gruelbox + transactionoutbox-testing + ${project.version} + test + + + com.gruelbox + transactionoutbox-jooq + ${project.version} + test + + + me.escoffier.loom + loom-unit + 0.3.0 + test + + + org.testcontainers + testcontainers + + + org.testcontainers + junit-jupiter + + + org.testcontainers + postgresql + + + org.testcontainers + oracle-xe + + + org.testcontainers + mysql + + + org.postgresql + postgresql + + + com.oracle.database.jdbc + ojdbc11 + + + com.mysql + mysql-connector-j + + + com.h2database + h2 + + + diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/AbstractVirtualThreadsTest.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/AbstractVirtualThreadsTest.java new file mode 100644 index 00000000..59e4baa6 --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/AbstractVirtualThreadsTest.java @@ -0,0 +1,105 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.empty; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.gruelbox.transactionoutbox.*; +import com.gruelbox.transactionoutbox.testing.BaseTest; +import com.gruelbox.transactionoutbox.testing.InterfaceProcessor; +import java.time.Duration; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; +import lombok.extern.slf4j.Slf4j; +import me.escoffier.loom.loomunit.LoomUnitExtension; +import me.escoffier.loom.loomunit.ShouldNotPin; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +@Slf4j +@ExtendWith(LoomUnitExtension.class) +abstract class AbstractVirtualThreadsTest extends BaseTest { + + private static final String VIRTUAL_THREAD_SCHEDULER_PARALLELISM = + "jdk.virtualThreadScheduler.parallelism"; + + @Test + @ShouldNotPin + final void highVolumeVirtualThreads() throws Exception { + var count = 10; + var latch = new CountDownLatch(count * 10); + var transactionManager = txManager(); + var results = new ConcurrentHashMap(); + var duplicates = new ConcurrentHashMap(); + var outbox = + TransactionOutbox.builder() + .transactionManager(transactionManager) + .persistor(Persistor.forDialect(connectionDetails().dialect())) + .instantiator(Instantiator.using(clazz -> (InterfaceProcessor) (foo, bar) -> {})) + .submitter( + Submitter.withExecutor( + r -> Thread.ofVirtual().name(UUID.randomUUID().toString()).start(r))) + .attemptFrequency(Duration.ofMillis(500)) + .flushBatchSize(1000) + .listener( + new TransactionOutboxListener() { + @Override + public void success(TransactionOutboxEntry entry) { + Integer i = (Integer) entry.getInvocation().getArgs()[0]; + if (results.putIfAbsent(i, i) != null) { + duplicates.put(i, i); + } + latch.countDown(); + } + }) + .build(); + + var parallelism = System.getProperty(VIRTUAL_THREAD_SCHEDULER_PARALLELISM); + System.setProperty(VIRTUAL_THREAD_SCHEDULER_PARALLELISM, "1"); + try { + withRunningFlusher( + outbox, + () -> { + var futures = + IntStream.range(0, count) + .mapToObj( + i -> + new FutureTask( + () -> + transactionManager.inTransaction( + () -> { + for (int j = 0; j < 10; j++) { + outbox + .schedule(InterfaceProcessor.class) + .process(i * 10 + j, "Whee"); + } + }), + null)) + .toList(); + futures.forEach(Thread::startVirtualThread); + for (var future : futures) { + future.get(20, TimeUnit.SECONDS); + } + assertTrue(latch.await(30, TimeUnit.SECONDS), "Latch not opened in time"); + }); + } finally { + if (parallelism == null) { + System.clearProperty(VIRTUAL_THREAD_SCHEDULER_PARALLELISM); + } else { + System.setProperty(VIRTUAL_THREAD_SCHEDULER_PARALLELISM, parallelism); + } + } + + assertThat( + "Should never get duplicates running to full completion", duplicates.keySet(), empty()); + assertThat( + "Only got: " + results.keySet(), + results.keySet(), + containsInAnyOrder(IntStream.range(0, count * 10).boxed().toArray())); + } +} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2.java new file mode 100644 index 00000000..9495febf --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2.java @@ -0,0 +1,3 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +public class TestVirtualThreadsH2 extends AbstractVirtualThreadsTest {} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2Jooq.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2Jooq.java new file mode 100644 index 00000000..d03a93d3 --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsH2Jooq.java @@ -0,0 +1,34 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import com.gruelbox.transactionoutbox.ThreadLocalContextTransactionManager; +import com.gruelbox.transactionoutbox.jooq.JooqTransactionListener; +import com.gruelbox.transactionoutbox.jooq.JooqTransactionManager; +import org.jooq.SQLDialect; +import org.jooq.impl.DSL; +import org.jooq.impl.DataSourceConnectionProvider; +import org.jooq.impl.DefaultConfiguration; +import org.jooq.impl.ThreadLocalTransactionProvider; +import org.junit.jupiter.api.BeforeEach; + +public class TestVirtualThreadsH2Jooq extends AbstractVirtualThreadsTest { + + private ThreadLocalContextTransactionManager txm; + + @Override + protected final ThreadLocalContextTransactionManager txManager() { + return txm; + } + + @BeforeEach + final void beforeEach() { + DataSourceConnectionProvider connectionProvider = new DataSourceConnectionProvider(dataSource); + DefaultConfiguration configuration = new DefaultConfiguration(); + configuration.setConnectionProvider(connectionProvider); + configuration.setSQLDialect(SQLDialect.H2); + configuration.setTransactionProvider( + new ThreadLocalTransactionProvider(connectionProvider, true)); + JooqTransactionListener listener = JooqTransactionManager.createListener(); + configuration.set(listener); + txm = JooqTransactionManager.create(DSL.using(configuration), listener); + } +} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql5.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql5.java new file mode 100644 index 00000000..308f579e --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql5.java @@ -0,0 +1,32 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import static com.gruelbox.transactionoutbox.Dialect.MY_SQL_5; + +import java.time.Duration; +import org.junit.jupiter.api.Disabled; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@SuppressWarnings("WeakerAccess") +@Testcontainers +@Disabled +class TestVirtualThreadsMySql5 extends AbstractVirtualThreadsTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + new MySQLContainer<>("mysql:5").withStartupTimeout(Duration.ofHours(1)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(MY_SQL_5) + .driverClassName("com.mysql.cj.jdbc.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } +} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql8.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql8.java new file mode 100644 index 00000000..b2233c7c --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsMySql8.java @@ -0,0 +1,32 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import static com.gruelbox.transactionoutbox.Dialect.MY_SQL_8; + +import java.time.Duration; +import org.junit.jupiter.api.Disabled; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@SuppressWarnings("WeakerAccess") +@Testcontainers +@Disabled +class TestVirtualThreadsMySql8 extends AbstractVirtualThreadsTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + new MySQLContainer<>("mysql:8").withStartupTimeout(Duration.ofMinutes(5)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(MY_SQL_8) + .driverClassName("com.mysql.cj.jdbc.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } +} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsOracle21.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsOracle21.java new file mode 100644 index 00000000..6ccd450e --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsOracle21.java @@ -0,0 +1,30 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import static com.gruelbox.transactionoutbox.Dialect.ORACLE; + +import java.time.Duration; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@SuppressWarnings("WeakerAccess") +@Testcontainers +class TestVirtualThreadsOracle21 extends AbstractVirtualThreadsTest { + + @Container + @SuppressWarnings("rawtypes") + private static final JdbcDatabaseContainer container = + new OracleContainer("gvenzl/oracle-xe:21-slim").withStartupTimeout(Duration.ofHours(1)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(ORACLE) + .driverClassName("oracle.jdbc.OracleDriver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } +} diff --git a/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsPostgres16.java b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsPostgres16.java new file mode 100644 index 00000000..4d8b8e34 --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/java/com/gruelbox/transactionoutbox/virtthreads/TestVirtualThreadsPostgres16.java @@ -0,0 +1,31 @@ +package com.gruelbox.transactionoutbox.virtthreads; + +import static com.gruelbox.transactionoutbox.Dialect.POSTGRESQL_9; + +import java.time.Duration; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +@SuppressWarnings("WeakerAccess") +@Testcontainers +class TestVirtualThreadsPostgres16 extends AbstractVirtualThreadsTest { + + @Container + @SuppressWarnings({"rawtypes", "resource"}) + private static final JdbcDatabaseContainer container = + (JdbcDatabaseContainer) + new PostgreSQLContainer("postgres:16").withStartupTimeout(Duration.ofHours(1)); + + @Override + protected ConnectionDetails connectionDetails() { + return ConnectionDetails.builder() + .dialect(POSTGRESQL_9) + .driverClassName("org.postgresql.Driver") + .url(container.getJdbcUrl()) + .user(container.getUsername()) + .password(container.getPassword()) + .build(); + } +} diff --git a/transactionoutbox-virtthreads/src/test/resources/logback-test.xml b/transactionoutbox-virtthreads/src/test/resources/logback-test.xml new file mode 100644 index 00000000..96692642 --- /dev/null +++ b/transactionoutbox-virtthreads/src/test/resources/logback-test.xml @@ -0,0 +1,11 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n + + + + + +