diff --git a/README.md b/README.md
index 764927ca..71b355e8 100644
--- a/README.md
+++ b/README.md
@@ -54,7 +54,8 @@ public class Application {
By default, [Narayana Transactional driver](https://www.narayana.io/docs/api/com/arjuna/ats/jdbc/TransactionalDriver.html)
is used to enlist a relational database to a JTA transaction which provides a basic XAResource enlistment and recovery as
-well as a simple pooling mechanism.
+well as a simple pooling mechanism which is disabled as default. See [TransactionalDriverProperties](narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/TransactionalDriverProperties.java)
+for more details.
> Be aware that Narayana Transactional driver automatically set transaction isolation level to `Connection.TRANSACTION_SERIALIZABLE`,
which might change default behaviour of the used database system!
diff --git a/narayana-spring-boot-core/pom.xml b/narayana-spring-boot-core/pom.xml
index 77b785cf..bd0bfd44 100644
--- a/narayana-spring-boot-core/pom.xml
+++ b/narayana-spring-boot-core/pom.xml
@@ -60,6 +60,11 @@
spring-boot-configuration-processor
true
+
+ org.springframework
+ spring-jdbc
+ true
+
org.mockito
mockito-junit-jupiter
diff --git a/narayana-spring-boot-core/src/main/java/com/arjuna/ats/internal/jdbc/ProvidedXADataSourceConnection.java b/narayana-spring-boot-core/src/main/java/com/arjuna/ats/internal/jdbc/ProvidedXADataSourceConnection.java
new file mode 100644
index 00000000..07025446
--- /dev/null
+++ b/narayana-spring-boot-core/src/main/java/com/arjuna/ats/internal/jdbc/ProvidedXADataSourceConnection.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2020 Red Hat, Inc, and individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.arjuna.ats.internal.jdbc;
+
+import java.sql.SQLException;
+
+import javax.sql.XAConnection;
+import javax.sql.XADataSource;
+import javax.transaction.xa.XAResource;
+
+import jakarta.transaction.Transaction;
+
+import com.arjuna.ats.internal.jdbc.drivers.modifiers.ConnectionModifier;
+import com.arjuna.ats.jdbc.logging.jdbcLogger;
+import dev.snowdrop.boot.narayana.core.jdbc.NamedXAResource;
+
+public class ProvidedXADataSourceConnection implements ConnectionControl, TransactionalDriverXAConnection {
+
+ private final BaseTransactionalDriverXAConnection delegate = new BaseTransactionalDriverXAConnection() {
+ };
+
+ public ProvidedXADataSourceConnection(String dbName, String user, String passwd, XADataSource xaDatasource, ConnectionImple conn) {
+ if (jdbcLogger.logger.isTraceEnabled()) {
+ jdbcLogger.logger.trace("ProvidedXADataSourceConnection.ProvidedXADataSourceConnection( " + dbName + ", " + user + ", " + passwd + ", " + xaDatasource + " )");
+ }
+ this.delegate._dbName = dbName;
+ this.delegate._user = user;
+ this.delegate._passwd = passwd;
+ this.delegate._theDataSource = xaDatasource;
+ this.delegate._theArjunaConnection = conn;
+ }
+
+ @Override
+ public String dynamicClass() {
+ return this.delegate.dynamicClass();
+ }
+
+ @Override
+ public String dataSourceName() {
+ return this.delegate.dataSourceName();
+ }
+
+ @Override
+ public String password() {
+ return this.delegate.password();
+ }
+
+ @Override
+ public void setModifier(ConnectionModifier cm) {
+ this.delegate.setModifier(cm);
+ }
+
+ @Override
+ public Transaction transaction() {
+ return this.delegate.transaction();
+ }
+
+ @Override
+ public String url() {
+ return this.delegate.url();
+ }
+
+ @Override
+ public String user() {
+ return this.delegate.user();
+ }
+
+ @Override
+ public XADataSource xaDataSource() {
+ return this.delegate.xaDataSource();
+ }
+
+ @Override
+ public void closeCloseCurrentConnection() throws SQLException {
+ this.delegate.closeCloseCurrentConnection();
+ }
+
+ @Override
+ public XAConnection getConnection() throws SQLException {
+ return this.delegate.getConnection();
+ }
+
+ @Override
+ public XAResource getResource() throws SQLException {
+ if (this.delegate._theXAResource == null) {
+ this.delegate._theXAResource = new NamedXAResource(this.delegate.getResource(), this.delegate.dataSourceName());
+ }
+ return this.delegate._theXAResource;
+ }
+
+ @Override
+ public boolean inuse() {
+ return this.delegate.inuse();
+ }
+
+ @Override
+ public boolean setTransaction(Transaction tx) {
+ return this.delegate.setTransaction(tx);
+ }
+
+ @Override
+ public boolean validTransaction(Transaction tx) {
+ return this.delegate.validTransaction(tx);
+ }
+}
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelper.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelper.java
index 0330062c..56c8e475 100644
--- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelper.java
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelper.java
@@ -31,14 +31,16 @@
public class DataSourceXAResourceRecoveryHelper implements XAResourceRecoveryHelper, XAResource {
private final ConnectionManager connectionManager;
+ private final String name;
/**
* Create a new {@link DataSourceXAResourceRecoveryHelper} instance.
*
* @param xaDataSource the XA data source
+ * @param name the datasource name or {@code null}
*/
- public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource) {
- this(xaDataSource, null, null);
+ public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String name) {
+ this(xaDataSource, null, null, name);
}
/**
@@ -47,9 +49,11 @@ public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource) {
* @param xaDataSource the XA data source
* @param user the database user or {@code null}
* @param password the database password or {@code null}
+ * @param name the datasource name or {@code null}
*/
- public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user, String password) {
+ public DataSourceXAResourceRecoveryHelper(XADataSource xaDataSource, String user, String password, String name) {
this.connectionManager = new ConnectionManager(xaDataSource, user, password);
+ this.name = name;
}
@Override
@@ -67,7 +71,7 @@ public XAResource[] getXAResources() {
}
}
- return new XAResource[]{this};
+ return new XAResource[]{new NamedXAResource(this, this.name)};
}
@Override
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapper.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapper.java
index 7555b71d..23fca3c2 100644
--- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapper.java
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapper.java
@@ -16,13 +16,23 @@
package dev.snowdrop.boot.narayana.core.jdbc;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
import javax.sql.DataSource;
import javax.sql.XADataSource;
+import com.arjuna.ats.internal.jdbc.drivers.modifiers.IsSameRMModifier;
+import com.arjuna.ats.internal.jdbc.drivers.modifiers.ModifierFactory;
+import com.arjuna.ats.internal.jdbc.drivers.modifiers.SupportsMultipleConnectionsModifier;
import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule;
import com.arjuna.ats.jta.recovery.XAResourceRecoveryHelper;
import dev.snowdrop.boot.narayana.core.properties.RecoveryCredentialsProperties;
+import dev.snowdrop.boot.narayana.core.properties.TransactionalDriverProperties;
+import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.XADataSourceWrapper;
+import org.springframework.jdbc.support.JdbcUtils;
/**
* An {@link XADataSourceWrapper} implementation which handles {@link XAResourceRecoveryHelper} creation and
@@ -33,12 +43,13 @@
public class GenericXADataSourceWrapper implements XADataSourceWrapper {
private final XARecoveryModule xaRecoveryModule;
+ private final TransactionalDriverProperties transactionalDriverProperties;
private final RecoveryCredentialsProperties recoveryCredentials;
/**
* Create a new {@link GenericXADataSourceWrapper} instance.
*
- * @param xaRecoveryModule recovery module to register data source with.
+ * @param xaRecoveryModule recovery module to register data source with.
*/
public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule) {
this(xaRecoveryModule, RecoveryCredentialsProperties.DEFAULT);
@@ -47,11 +58,34 @@ public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule) {
/**
* Create a new {@link GenericXADataSourceWrapper} instance.
*
- * @param xaRecoveryModule recovery module to register data source with.
- * @param recoveryCredentials credentials for recovery helper
+ * @param xaRecoveryModule recovery module to register data source with.
+ * @param recoveryCredentials credentials for recovery helper
*/
public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule, RecoveryCredentialsProperties recoveryCredentials) {
+ this(xaRecoveryModule, new TransactionalDriverProperties(), recoveryCredentials);
+ }
+
+ /**
+ * Create a new {@link GenericXADataSourceWrapper} instance.
+ *
+ * @param xaRecoveryModule recovery module to register data source with.
+ * @param transactionalDriverProperties Transactional driver properties
+ */
+ public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule, TransactionalDriverProperties transactionalDriverProperties) {
+ this(xaRecoveryModule, transactionalDriverProperties, RecoveryCredentialsProperties.DEFAULT);
+ }
+
+ /**
+ * Create a new {@link GenericXADataSourceWrapper} instance.
+ *
+ * @param xaRecoveryModule recovery module to register data source with.
+ * @param transactionalDriverProperties Transactional driver properties
+ * @param recoveryCredentials credentials for recovery helper
+ */
+ public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule, TransactionalDriverProperties transactionalDriverProperties,
+ RecoveryCredentialsProperties recoveryCredentials) {
this.xaRecoveryModule = xaRecoveryModule;
+ this.transactionalDriverProperties = transactionalDriverProperties;
this.recoveryCredentials = recoveryCredentials;
}
@@ -66,14 +100,34 @@ public GenericXADataSourceWrapper(XARecoveryModule xaRecoveryModule, RecoveryCre
public DataSource wrapDataSource(XADataSource dataSource) throws Exception {
XAResourceRecoveryHelper recoveryHelper = getRecoveryHelper(dataSource);
this.xaRecoveryModule.addXAResourceRecoveryHelper(recoveryHelper);
- return new NarayanaDataSource(dataSource);
+ registerModifier(dataSource);
+ return new NarayanaDataSource(dataSource, this.transactionalDriverProperties);
}
private XAResourceRecoveryHelper getRecoveryHelper(XADataSource dataSource) {
if (this.recoveryCredentials.isValid()) {
return new DataSourceXAResourceRecoveryHelper(dataSource, this.recoveryCredentials.getUser(),
- this.recoveryCredentials.getPassword());
+ this.recoveryCredentials.getPassword(), this.transactionalDriverProperties.getName());
+ }
+ return new DataSourceXAResourceRecoveryHelper(dataSource, this.transactionalDriverProperties.getName());
+ }
+
+ private void registerModifier(XADataSource dataSource) throws SQLException {
+ try (Connection conn = dataSource.getXAConnection().getConnection()) {
+ DatabaseMetaData metaData = conn.getMetaData();
+ String driver = metaData.getDriverName();
+ int major = metaData.getDriverMajorVersion();
+ int minor = metaData.getDriverMinorVersion();
+ switch (this.transactionalDriverProperties.getModifier()) {
+ case DEFAULT -> {
+ switch (DatabaseDriver.fromProductName(JdbcUtils.commonDatabaseName(metaData.getDatabaseProductName()))) {
+ case DB2, H2, MYSQL, ORACLE, SQLSERVER -> ModifierFactory.putModifier(driver, major, minor, IsSameRMModifier.class.getName());
+ case POSTGRESQL -> ModifierFactory.putModifier(driver, major, minor, SupportsMultipleConnectionsModifier.class.getName());
+ }
+ }
+ case IS_SAME_RM -> ModifierFactory.putModifier(driver, major, minor, IsSameRMModifier.class.getName());
+ case SUPPORTS_MULTIPLE_CONNECTIONS -> ModifierFactory.putModifier(driver, major, minor, SupportsMultipleConnectionsModifier.class.getName());
+ }
}
- return new DataSourceXAResourceRecoveryHelper(dataSource);
}
}
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NamedXAResource.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NamedXAResource.java
new file mode 100644
index 00000000..210bce87
--- /dev/null
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NamedXAResource.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2020 Red Hat, Inc, and individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.snowdrop.boot.narayana.core.jdbc;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.jboss.tm.XAResourceWrapper;
+
+public class NamedXAResource implements XAResourceWrapper {
+
+ private static final String PRODUCT_NAME = NamedXAResource.class.getPackage().getImplementationTitle();
+ private static final String PRODUCT_VERSION = NamedXAResource.class.getPackage().getImplementationVersion();
+
+ private final XAResource xaResource;
+ private final String name;
+
+ public NamedXAResource(XAResource xaResource, String name) {
+ this.xaResource = xaResource;
+ this.name = name;
+ }
+
+ @Override
+ public XAResource getResource() {
+ return this.xaResource;
+ }
+
+ @Override
+ public String getProductName() {
+ return PRODUCT_NAME;
+ }
+
+ @Override
+ public String getProductVersion() {
+ return PRODUCT_VERSION;
+ }
+
+ @Override
+ public String getJndiName() {
+ return this.name;
+ }
+
+ @Override
+ public void commit(Xid xid, boolean onePhase) throws XAException {
+ getResource().commit(xid, onePhase);
+ }
+
+ @Override
+ public void end(Xid xid, int flags) throws XAException {
+ getResource().end(xid, flags);
+ }
+
+ @Override
+ public void forget(Xid xid) throws XAException {
+ getResource().forget(xid);
+ }
+
+ @Override
+ public int getTransactionTimeout() throws XAException {
+ return getResource().getTransactionTimeout();
+ }
+
+ @Override
+ public boolean isSameRM(XAResource xaRes) throws XAException {
+ if (xaRes instanceof NamedXAResource namedXaResource) {
+ return getResource().isSameRM((namedXaResource).getResource());
+ }
+ return false;
+ }
+
+ @Override
+ public int prepare(Xid xid) throws XAException {
+ return getResource().prepare(xid);
+ }
+
+ @Override
+ public Xid[] recover(int flag) throws XAException {
+ return getResource().recover(flag);
+ }
+
+ @Override
+ public void rollback(Xid xid) throws XAException {
+ getResource().rollback(xid);
+ }
+
+ @Override
+ public boolean setTransactionTimeout(int seconds) throws XAException {
+ return getResource().setTransactionTimeout(seconds);
+ }
+
+ @Override
+ public void start(Xid xid, int flags) throws XAException {
+ getResource().start(xid, flags);
+ }
+}
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSource.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSource.java
index 173b88ec..fbf1a605 100644
--- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSource.java
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSource.java
@@ -28,6 +28,7 @@
import com.arjuna.ats.internal.jdbc.ConnectionManager;
import com.arjuna.ats.jdbc.TransactionalDriver;
+import dev.snowdrop.boot.narayana.core.properties.TransactionalDriverProperties;
/**
* {@link DataSource} implementation wrapping {@link XADataSource} and using
@@ -38,30 +39,38 @@
public class NarayanaDataSource implements DataSource {
private final XADataSource xaDataSource;
+ private final TransactionalDriverProperties transactionalDriverProperties;
/**
* Create a new {@link NarayanaDataSource} instance.
*
- * @param xaDataSource the XA DataSource
+ * @param xaDataSource the XA DataSource
+ * @param transactionalDriverProperties Transactional driver pool properties
*/
- public NarayanaDataSource(XADataSource xaDataSource) {
+ public NarayanaDataSource(XADataSource xaDataSource, TransactionalDriverProperties transactionalDriverProperties) {
this.xaDataSource = xaDataSource;
+ this.transactionalDriverProperties = transactionalDriverProperties;
}
- @Override
- public Connection getConnection() throws SQLException {
+ private Properties createProperties() {
Properties properties = new Properties();
properties.put(TransactionalDriver.XADataSource, this.xaDataSource);
- return ConnectionManager.create(null, properties);
+ properties.put(TransactionalDriver.poolConnections, String.valueOf(this.transactionalDriverProperties.getPool().isEnabled()));
+ properties.put(TransactionalDriver.maxConnections, this.transactionalDriverProperties.getPool().getMaxConnections());
+ return properties;
+ }
+
+ @Override
+ public Connection getConnection() throws SQLException {
+ return ConnectionManager.create(this.transactionalDriverProperties.getName(), createProperties());
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
- Properties properties = new Properties();
- properties.put(TransactionalDriver.XADataSource, this.xaDataSource);
+ Properties properties = createProperties();
properties.put(TransactionalDriver.userName, username);
properties.put(TransactionalDriver.password, password);
- return ConnectionManager.create(null, properties);
+ return ConnectionManager.create(this.transactionalDriverProperties.getName(), properties);
}
@Override
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java
index 0048a539..68d642a6 100644
--- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaProperties.java
@@ -123,6 +123,12 @@ public class NarayanaProperties {
private List expiryScanners = List.of(
"com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner");
+ /**
+ * Narayana Transactional driver specific properties.
+ */
+ @NestedConfigurationProperty
+ private final TransactionalDriverProperties transactionalDriver = new TransactionalDriverProperties();
+
/**
* MessagingHub specific properties used if pooled connection factory wrapper is enabled.
* See ... for the list of supported properties.
@@ -273,6 +279,10 @@ public void setRecoveryJmsCredentials(RecoveryCredentialsProperties recoveryJmsC
this.recoveryJmsCredentials = recoveryJmsCredentials;
}
+ public TransactionalDriverProperties getTransactionalDriver() {
+ return this.transactionalDriver;
+ }
+
public MessagingHubConnectionFactoryProperties getMessaginghub() {
return this.messaginghub;
}
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java
index 29020731..9ae42b91 100644
--- a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializer.java
@@ -28,6 +28,7 @@
import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException;
import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean;
import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean;
+import com.arjuna.ats.jdbc.common.JDBCEnvironmentBean;
import com.arjuna.ats.jta.common.JTAEnvironmentBean;
import com.arjuna.common.internal.util.propertyservice.BeanPopulator;
import org.springframework.beans.factory.InitializingBean;
@@ -63,6 +64,8 @@ public void afterPropertiesSet() {
setCommitMarkableResourceJNDINames(this.properties.getCommitMarkableResourceJNDINames());
setRecoveryModules(this.properties.getRecoveryModules());
setExpiryScanners(this.properties.getExpiryScanners());
+ setDefaultIsolationLevel(this.properties.getTransactionalDriver().getDefaultIsolationLevel().getLevel());
+ setDefaultIsSameRMOverride(this.properties.getTransactionalDriver().isDefaultIsSameRMOverride());
}
private void setNodeIdentifier(String nodeIdentifier, boolean shortenNodeIdentifierIfNecessary) {
@@ -147,6 +150,14 @@ private void setExpiryScanners(List expiryScanners) {
getPopulator(RecoveryEnvironmentBean.class).setExpiryScannerClassNames(expiryScanners);
}
+ private void setDefaultIsolationLevel(int defaultIsolationLevel) {
+ getPopulator(JDBCEnvironmentBean.class).setIsolationLevel(defaultIsolationLevel);
+ }
+
+ private void setDefaultIsSameRMOverride(boolean defaultIsSameRMOverride) {
+ getPopulator(JDBCEnvironmentBean.class).setDefaultIsSameRMOverride(defaultIsSameRMOverride);
+ }
+
private T getPopulator(Class beanClass) {
return BeanPopulator.getDefaultInstance(beanClass);
}
diff --git a/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/TransactionalDriverProperties.java b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/TransactionalDriverProperties.java
new file mode 100644
index 00000000..87ec5e51
--- /dev/null
+++ b/narayana-spring-boot-core/src/main/java/dev/snowdrop/boot/narayana/core/properties/TransactionalDriverProperties.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2020 Red Hat, Inc, and individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.snowdrop.boot.narayana.core.properties;
+
+import java.sql.Connection;
+
+public class TransactionalDriverProperties {
+
+ private String name = "jdbc";
+ private Modifier modifier = Modifier.DEFAULT;
+ private IsolationLevel defaultIsolationLevel = IsolationLevel.TRANSACTION_SERIALIZABLE;
+ private boolean defaultIsSameRMOverride = false;
+ private Pool pool = new Pool();
+
+ public String getName() {
+ return this.name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Modifier getModifier() {
+ return this.modifier;
+ }
+
+ public void setModifier(Modifier modifier) {
+ this.modifier = modifier;
+ }
+
+ public IsolationLevel getDefaultIsolationLevel() {
+ return this.defaultIsolationLevel;
+ }
+
+ public void setDefaultIsolationLevel(IsolationLevel defaultIsolationLevel) {
+ this.defaultIsolationLevel = defaultIsolationLevel;
+ }
+
+ public boolean isDefaultIsSameRMOverride() {
+ return this.defaultIsSameRMOverride;
+ }
+
+ public void setDefaultIsSameRMOverride(boolean defaultIsSameRMOverride) {
+ this.defaultIsSameRMOverride = defaultIsSameRMOverride;
+ }
+
+ public Pool getPool() {
+ return this.pool;
+ }
+
+ public void setPool(Pool pool) {
+ this.pool = pool;
+ }
+
+ public enum Modifier {
+ /**
+ * Register {@link com.arjuna.ats.internal.jdbc.drivers.modifiers.IsSameRMModifier} for used JDBC driver.
+ */
+ IS_SAME_RM,
+ /**
+ * Register {@link com.arjuna.ats.internal.jdbc.drivers.modifiers.SupportsMultipleConnectionsModifier} for used JDBC driver.
+ */
+ SUPPORTS_MULTIPLE_CONNECTIONS,
+ /**
+ * Use default modifier.
+ */
+ DEFAULT;
+ }
+
+ public enum IsolationLevel {
+
+ /**
+ * Transaction isolation level TRANSACTION_READ_UNCOMMITTED.
+ * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
+ */
+ TRANSACTION_READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED),
+ /**
+ * Transaction isolation level TRANSACTION_READ_COMMITTED.
+ * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
+ */
+ TRANSACTION_READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED),
+ /**
+ * Transaction isolation level TRANSACTION_REPEATABLE_READ.
+ * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
+ */
+ TRANSACTION_REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ),
+ /**
+ * Transaction isolation level TRANSACTION_SERIALIZABLE.
+ * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
+ */
+ TRANSACTION_SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE);
+
+ private final int level;
+
+ IsolationLevel(int level) {
+ this.level = level;
+ }
+
+ public int getLevel() {
+ return this.level;
+ }
+ }
+
+ public static class Pool {
+
+ private boolean enabled = false;
+ private int maxConnections = 10;
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public int getMaxConnections() {
+ return this.maxConnections;
+ }
+
+ public void setMaxConnections(int maxConnections) {
+ this.maxConnections = maxConnections;
+ }
+ }
+}
diff --git a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelperTests.java b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelperTests.java
index 1fa410ad..e52c793a 100644
--- a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelperTests.java
+++ b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/DataSourceXAResourceRecoveryHelperTests.java
@@ -56,7 +56,7 @@ class DataSourceXAResourceRecoveryHelperTests {
@BeforeEach
void before() throws SQLException {
- this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.mockXaDataSource);
+ this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.mockXaDataSource, null);
given(this.mockXaDataSource.getXAConnection()).willReturn(this.mockXaConnection);
given(this.mockXaConnection.getXAResource()).willReturn(this.mockXaResource);
@@ -66,17 +66,17 @@ void before() throws SQLException {
void shouldCreateConnectionAndGetXAResource() throws SQLException {
XAResource[] xaResources = this.recoveryHelper.getXAResources();
assertThat(xaResources).hasSize(1);
- assertThat(xaResources[0]).isSameAs(this.recoveryHelper);
+ assertThat(((NamedXAResource) xaResources[0]).getResource()).isSameAs(this.recoveryHelper);
verify(this.mockXaDataSource).getXAConnection();
}
@Test
void shouldCreateConnectionWithCredentialsAndGetXAResource() throws SQLException {
given(this.mockXaDataSource.getXAConnection(anyString(), anyString())).willReturn(this.mockXaConnection);
- this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.mockXaDataSource, "username", "password");
+ this.recoveryHelper = new DataSourceXAResourceRecoveryHelper(this.mockXaDataSource, "username", "password", null);
XAResource[] xaResources = this.recoveryHelper.getXAResources();
assertThat(xaResources).hasSize(1);
- assertThat(xaResources[0]).isSameAs(this.recoveryHelper);
+ assertThat(((NamedXAResource) xaResources[0]).getResource()).isSameAs(this.recoveryHelper);
verify(this.mockXaDataSource).getXAConnection("username", "password");
}
diff --git a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapperTests.java b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapperTests.java
index e88a7d1f..8cb716b2 100644
--- a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapperTests.java
+++ b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/GenericXADataSourceWrapperTests.java
@@ -16,7 +16,12 @@
package dev.snowdrop.boot.narayana.core.jdbc;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+
import javax.sql.DataSource;
+import javax.sql.XAConnection;
import javax.sql.XADataSource;
import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule;
@@ -43,6 +48,15 @@ class GenericXADataSourceWrapperTests {
@Mock
private XADataSource mockXaDataSource;
+ @Mock
+ private XAConnection mockXaConnection;
+
+ @Mock
+ private Connection mockConnection;
+
+ @Mock
+ private DatabaseMetaData mockDatabaseMetaData;
+
@Mock
private XARecoveryModule mockXaRecoveryModule;
@@ -52,7 +66,11 @@ class GenericXADataSourceWrapperTests {
private GenericXADataSourceWrapper wrapper;
@BeforeEach
- void before() {
+ void before() throws SQLException {
+ given(this.mockXaDataSource.getXAConnection()).willReturn(this.mockXaConnection);
+ given(this.mockXaConnection.getConnection()).willReturn(this.mockConnection);
+ given(this.mockConnection.getMetaData()).willReturn(this.mockDatabaseMetaData);
+ given(this.mockDatabaseMetaData.getDatabaseProductName()).willReturn("");
this.wrapper = new GenericXADataSourceWrapper(this.mockXaRecoveryModule, this.mockRecoveryCredentialsProperties);
}
diff --git a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSourceTests.java b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSourceTests.java
index 65c3ba4f..b65bec96 100644
--- a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSourceTests.java
+++ b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/jdbc/NarayanaDataSourceTests.java
@@ -27,6 +27,7 @@
import javax.sql.XADataSource;
import com.arjuna.ats.internal.jdbc.ConnectionImple;
+import dev.snowdrop.boot.narayana.core.properties.TransactionalDriverProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -50,11 +51,13 @@ class NarayanaDataSourceTests {
@Mock
private XADataSource mockXaDataSource;
+ private TransactionalDriverProperties transactionalDriverProperties;
private NarayanaDataSource dataSourceBean;
@BeforeEach
void before() {
- this.dataSourceBean = new NarayanaDataSource(this.mockXaDataSource);
+ this.transactionalDriverProperties = new TransactionalDriverProperties();
+ this.dataSourceBean = new NarayanaDataSource(this.mockXaDataSource, this.transactionalDriverProperties);
}
@Test
@@ -79,6 +82,24 @@ void shouldUnwrapXaDataSource() throws SQLException {
assertThat(this.dataSourceBean.unwrap(XADataSource.class)).isSameAs(this.mockXaDataSource);
}
+ @Test
+ void shouldGetSameConnection() throws SQLException {
+ this.transactionalDriverProperties.getPool().setEnabled(true);
+ Connection connection1 = this.dataSourceBean.getConnection();
+ connection1.close();
+ Connection connection2 = this.dataSourceBean.getConnection();
+ assertThat(connection2).isSameAs(connection1);
+ }
+
+ @Test
+ void shouldNotGetSameConnection() throws SQLException {
+ this.transactionalDriverProperties.getPool().setEnabled(false);
+ Connection connection1 = this.dataSourceBean.getConnection();
+ connection1.close();
+ Connection connection2 = this.dataSourceBean.getConnection();
+ assertThat(connection2).isNotSameAs(connection1);
+ }
+
@Test
void shouldGetConnectionAndCommit() throws SQLException {
DatabaseMetaData mockMetaData = mock(DatabaseMetaData.class);
diff --git a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java
index bc209897..abb42e01 100644
--- a/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java
+++ b/narayana-spring-boot-core/src/test/java/dev/snowdrop/boot/narayana/core/properties/NarayanaPropertiesInitializerTests.java
@@ -18,6 +18,7 @@
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
+import java.sql.Connection;
import java.util.List;
import java.util.Map;
@@ -25,6 +26,7 @@
import com.arjuna.ats.arjuna.common.CoreEnvironmentBean;
import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean;
import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean;
+import com.arjuna.ats.jdbc.common.JDBCEnvironmentBean;
import com.arjuna.ats.jta.common.JTAEnvironmentBean;
import com.arjuna.common.internal.util.propertyservice.BeanPopulator;
import org.junit.jupiter.api.AfterEach;
@@ -112,6 +114,12 @@ void shouldSetDefaultProperties() {
assertThat(BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class)
.getXaResourceRecoveryClassNames()).isEmpty();
+
+ assertThat(BeanPopulator.getDefaultInstance(JDBCEnvironmentBean.class)
+ .getIsolationLevel()).isEqualTo(Connection.TRANSACTION_SERIALIZABLE);
+
+ assertThat(BeanPopulator.getDefaultInstance(JDBCEnvironmentBean.class)
+ .getDefaultIsSameRMOverride()).isFalse();
}
@Test
diff --git a/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledRecoveryIT.java b/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledRecoveryIT.java
new file mode 100644
index 00000000..e614f2ad
--- /dev/null
+++ b/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledRecoveryIT.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Red Hat, Inc, and individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.snowdrop.boot.narayana.pooled;
+
+import dev.snowdrop.boot.narayana.app.TestApplication;
+import dev.snowdrop.boot.narayana.generic.GenericRecoveryIT;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest(classes = TestApplication.class, properties = {"narayana.transactionalDriver.pool.enabled=true"})
+public class SimplePooledRecoveryIT extends GenericRecoveryIT {
+}
diff --git a/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledTransactionalIT.java b/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledTransactionalIT.java
new file mode 100644
index 00000000..b5663baa
--- /dev/null
+++ b/narayana-spring-boot-starter-it/src/test/java/dev/snowdrop/boot/narayana/pooled/SimplePooledTransactionalIT.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Red Hat, Inc, and individual contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dev.snowdrop.boot.narayana.pooled;
+
+import dev.snowdrop.boot.narayana.app.TestApplication;
+import dev.snowdrop.boot.narayana.generic.GenericTransactionalIT;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest(classes = TestApplication.class, properties = {"narayana.transactionalDriver.pool.enabled=true"})
+public class SimplePooledTransactionalIT extends GenericTransactionalIT {
+}
diff --git a/narayana-spring-boot-starter/src/main/java/dev/snowdrop/boot/narayana/autoconfigure/NarayanaAutoConfiguration.java b/narayana-spring-boot-starter/src/main/java/dev/snowdrop/boot/narayana/autoconfigure/NarayanaAutoConfiguration.java
index 5298727b..ea0ff96c 100644
--- a/narayana-spring-boot-starter/src/main/java/dev/snowdrop/boot/narayana/autoconfigure/NarayanaAutoConfiguration.java
+++ b/narayana-spring-boot-starter/src/main/java/dev/snowdrop/boot/narayana/autoconfigure/NarayanaAutoConfiguration.java
@@ -164,6 +164,7 @@ static class GenericJdbcConfiguration {
public XADataSourceWrapper xaDataSourceWrapper(NarayanaProperties narayanaProperties,
XARecoveryModule xaRecoveryModule) {
return new GenericXADataSourceWrapper(xaRecoveryModule,
+ narayanaProperties.getTransactionalDriver(),
narayanaProperties.getRecoveryDbCredentials());
}