Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Transactional driver pool configurable #183

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
geoand marked this conversation as resolved.
Show resolved Hide resolved
<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
Loading