Skip to content

Commit

Permalink
Merge pull request #183 from graben/transactionalDriverPool
Browse files Browse the repository at this point in the history
Make Transactional driver pool configurable
  • Loading branch information
geoand authored Jan 13, 2025
2 parents bda7da7 + c8e86f6 commit 1988bf2
Show file tree
Hide file tree
Showing 17 changed files with 585 additions and 25 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
5 changes: 5 additions & 0 deletions narayana-spring-boot-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand All @@ -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
Expand All @@ -67,7 +71,7 @@ public XAResource[] getXAResources() {
}
}

return new XAResource[]{this};
return new XAResource[]{new NamedXAResource(this, this.name)};
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
Expand All @@ -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;
}

Expand All @@ -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);
}
}
Loading

0 comments on commit 1988bf2

Please sign in to comment.