cancel
Showing results for 
Search instead for 
Did you mean: 

JDBC DataSource out of JNDI

jon2
Champ in-the-making
Champ in-the-making
I would like to know if Activit supports the retrieval of its JDBC DataSource out of JNDI (like JPA), so when running in a JEE Container it will used managed JDBC Connections?

JPA of course supports this, but as Activiti uses MyBatis under the hood, and Spring to configure the JDBC Connnection, it's all to confusing for me to determine wheter this is supported.
7 REPLIES 7

kesavkolla
Champ in-the-making
Champ in-the-making
Yes you can use the container's JDBC datasource.  Activiti uses spring configuration so you can use all the spring bean's to configure the datasource.

I use the following in my configuration:


<jee:jndi-lookup jndi-name="java:ActivitiDS" id="dataSource" />
<bean id="processEngineConfigurationStd" class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
  <!– Database configurations –>
  <property name="databaseSchemaUpdate" value="true" />
  <property name="dataSource" ref="dataSource" />
  <property name="dbCycleUsed" value="true" />
</bean>

HTH

jon2
Champ in-the-making
Champ in-the-making
Looks good, thanks.  I've 0 Spring knowledge.

Would you want to use the (Container|JTA)ProcessEngineConfiguration, so the transactions would run in XA mode?

ronald_van_kuij
Champ on-the-rise
Champ on-the-rise
you can also pass on your own datasource: without using spring

blackbetldev
Champ in-the-making
Champ in-the-making
There's a problem with only retrieving the DataSource once on startup with the JBoss EAP app server which is what I believe what Activiti does here:

org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl#initDataSource

As mention here (unfortunately JBoss customer's only):

https://access.redhat.com/solutions/1193073

Essentially it's possible that DataSource references can go bad (e.g. disabled, connection pool flushed) and then this will start appearing in the logs:

… javax.resource.ResourceException: IJ000451: The connection manager is shutdown: jdbc/MyDS

Here's the suggested resolution:

*  Don't cache datasource references. Look up the datasource in JNDI before each connection request, or implement logic to redo the look up if the cached reference is bad.
*  If something uses cached datasource references, don't disable/shutdown or reconfigure/redeploy the datasource at run time.

I'm currently investigating this problem because the "EntirePool" flush-strategy is used in our JBoss configuration so our existing DataSource references become stale (e.g. DB failover event during a patch cycle).

Are you aware of any DataSource proxy class that I can use that wraps the JNDI DataSource object that is returned from lookup to perform an automatic lookup as suggested by Red Hat? Or is there another possible solution to this? It doesn't happen  a lot but when it does our application is basically down until we restart the server.

Thanks

jbarrez
Star Contributor
Star Contributor
Thanks for the explanation.

> Look up the datasource in JNDI before each connection request,

Isn't that bad for performance?

> Are you aware of any DataSource proxy class that I can use that wraps the JNDI DataSource object that is returned from lookup to perform an automatic lookup as suggested by Red Hat

No, but that shouldn't be too hard to write yourself? The datasource interface is quite straightforward.

blackbetldev
Champ in-the-making
Champ in-the-making
I would tend to agree. I don't understand why JBoss implemented it this way as it's totally non-intuitive and violates the principal of least surprise. I didn't check the JEE spec if this is documented behavior or not or just a JBoss special.

Here's the abbreviated code for reconnecting to the DataSource I wrote. It works great now.

<code lang="java" linenumbers="normal">

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;

import javax.annotation.concurrent.ThreadSafe;
import javax.resource.ResourceException;
import javax.sql.CommonDataSource;
import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.concurrent.atomic.AtomicReference;

import static java.util.Objects.requireNonNull;

@ThreadSafe
public class SmartDataSourceWrapper implements DataSource {

    @FunctionalInterface
    private interface CheckedSqlFunction<R> {
        R apply(DataSource dataSource) throws Exception;
    }

    private static final Logger LOG = LoggerFactory.getLogger(SmartDataSourceWrapper.class);

    private final DataSourceLookup dataSourceLookup;
    private final String dataSourceName;
    private final AtomicReference<DataSource> cachedDataSource;

    public SmartDataSourceWrapper(final DataSourceLookup dataSourceLookup, final String dataSourceName) {
        this.dataSourceLookup = requireNonNull(dataSourceLookup);
        this.dataSourceName = requireNonNull(dataSourceName);
        this.cachedDataSource = new AtomicReference<>(dataSourceLookup.getDataSource(dataSourceName));
    }

    @Override
    public Connection getConnection() throws SQLException {
        return delegate("getConnection", DataSource::getConnection);
    }

    @Override
    public Connection getConnection(final String username, final String password) throws SQLException {
        return delegate("getConnection(String, String)", ds -> ds.getConnection(username, password));
    }



    @Override
    public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
        try {
            return delegate("getParentLogger()", CommonDataSource::getParentLogger);
        } catch (SQLFeatureNotSupportedException ex) {
            throw ex;
        } catch (Exception ex) {
            if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new RuntimeException(ex);
        }
    }



    @Override
    public void setLoginTimeout(int seconds) throws SQLException {
        delegate("setLoginTimeout(int)", ds -> {
            ds.setLoginTimeout(seconds);
            return null;
        });
    }

    private <T> T delegate(final String methodName, final CheckedSqlFunction<T> function) throws SQLException {
        LOG.trace("Invoking '{}' on cached DataSource reference", methodName);
        try {
            return function.apply(cachedDataSource.get());
        } catch (SQLException ex) {
            if (ex.getCause() instanceof ResourceException) {
                LOG.info("Caught ResourceException reloading DataSource from JNDI", ex);
                reload();
                return retry(methodName, function);
            }
            throw ex;
        } catch (Exception ex) {
            if (ex instanceof RuntimeException) {
                throw (RuntimeException) ex;
            }
            throw new RuntimeException(ex);
        }
    }

    private void reload() {
        LOG.info("Reloading '{}'", dataSourceName);
        cachedDataSource.set(dataSourceLookup.getDataSource(dataSourceName));
    }

    /**
     * Retry SQL operation after the {@link DataSource} reference was reloaded from JNDI.
     *
     * @param function the checked SQL function.
     * @param <T>      the result type.
     * @return the result.
     * @throws SQLException
     */
    private <T> T retry(final String methodName, final CheckedSqlFunction<T> function) throws SQLException {
        LOG.info("Retrying SQL operation '{}'", methodName);
        try {
            return function.apply(cachedDataSource.get());
        } catch (Exception retryEx) {
            LOG.info("SQL operation failed again", retryEx);
            if (retryEx instanceof SQLException) {
                throw (SQLException) retryEx;
            }
            throw new RuntimeException(retryEx);
        }
    }
}

</code>

jbarrez
Star Contributor
Star Contributor
wow, that's special.

Thanks for sharing this.