Multiple Persistence Modules with Transaction

304 views
Skip to first unread message

JavaPlayer

unread,
May 10, 2012, 3:13:45 PM5/10/12
to google...@googlegroups.com
Was someone able to use the Multiple Persistence Modules with transactions ?


I don't need a transaction across multiple db but on every different db. 

I tried to create private module and bind my singleton dao into them and expose them but the JpaLocalTxnInterceptor is never called.


Am I the only one with this problem ? 


example of configure(): 

bind(ClientDAO.class).to(ClientDAOImpl.class);
expose(ClientChannelReadDAO.class);
JpaPersistModule persistModule = new JpaPersistModule(entitiyManagerName);
persistModule.properties(loadHibernateProperties());
install(persistModule);
bind(JpaPersistModule.class).annotatedWith(Names.named(entitiyManagerName)).toInstance(persistModule);
expose(JpaPersistModule.class).annotatedWith(Names.named(entitiyManagerName));
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class); 
bind(EntityManager.class).annotatedWith(Names.named(entitiyManagerName)).toProvider(entityManagerProvider); 
expose(EntityManager.class).annotatedWith(Names.named(entitiyManagerName)); 
Provider<PersistService> persistServiceProvider = binder().getProvider(PersistService.class);
bind(PersistService.class).annotatedWith(Names.named(entitiyManagerName)).toProvider(persistServiceProvider);
expose(PersistService.class).annotatedWith(Names.named(entitiyManagerName));

scl

unread,
May 11, 2012, 3:56:04 AM5/11/12
to google...@googlegroups.com
No you are not the first to have this problem
https://groups.google.com/d/msg/google-guice/-/eltHSCDwcNwJ
https://groups.google.com/d/msg/google-guice/-/utXFimt1S0cJ

You can use this as a startingpoint:
I copied this from a running project and removed all project specific code.
It's possible that it doesn't compile right a way but it should get you started quickly.
this code allows to define custom transaction annotations to start a transaction only on a specific data source.

most important for you is proabably the configureServlets() method. From how you describe your problem I think this is what is missing in your code.


Once I find some time, i will refactor this whole code and offer it as a patch to GuicePersist.

----------


public class MultiDatasourcePersistModule extends ServletModule {

    //---- Fields

    /** The data source this persist module is for. */
    private final DataSource dataSource;


    //---- Constructor

    /**
     * Creates a new Instance.
     *
     * @param dataSource  the data source this perist module is for. Must not be {@code null}.
     */
    public MultiDatasourcePersistModule(DataSource dataSource) {
        checkNotNull(dataSource);
        this.dataSource = dataSource;
    }


    //---- Methods

    /**
     * {@inheritDoc}
     */
    @Override
    protected final void configureServlets() {
        final PrivateJpaPersistModule jpm = new PrivateJpaPersistModule();
        install(jpm);

        bindInterceptor(annotatedWith(dataSource.getTransactionAnnotationType()), any(), jpm.getTransactionInterceptor());
        bindInterceptor(any(), annotatedWith(dataSource.getTransactionAnnotationType()), jpm.getTransactionInterceptor());

        filter(dataSource.getUrlFilterPattern()).through(jpm.getPersistFilter());
    }


    //---- Inner Class

    /**
     * This private module encapsulates the actual {@link JpaPersistModule}. This is necessary because Guice allows only a single {@link JpaPersistModule}
     * to be defined in the global context.
     */
    private class PrivateJpaPersistModule extends PrivateModule {

        //---- Fields

        private final Key<PersistFilter> persistFilterKey = Key.get(PersistFilter.class, dataSource.getDataSourceAnnotationType());

        private MethodInterceptor transactionInterceptor;


        //---- Methods

        /**
         * {@inheritDoc}
         */
        @Override
        public void configure() {
            final JpaPersistModule jpm = new JpaPersistModule(dataSource.getDataSourceName());
            jpm.properties(dataSource.getDataSourceProperties());

            install(jpm);

            final Provider<UnitOfWork> unitOfWorkProvider = binder().getProvider(UnitOfWork.class);
            bind(UnitOfWork.class).annotatedWith(dataSource.getDataSourceAnnotationType()).toProvider(unitOfWorkProvider);
            expose(UnitOfWork.class).annotatedWith(dataSource.getDataSourceAnnotationType());

            bind(persistFilterKey).to(PersistFilter.class);
            expose(persistFilterKey);

            transactionInterceptor = new TransactionInterceptor(dataSource);
            requestInjection(transactionInterceptor);
        }

        /**
         * @return the key to the persist filter.
         */
        public Key<PersistFilter> getPersistFilter() {
            return persistFilterKey;
        }

        /**
         * Returns an interceptor which will handle all transactions for the data source.
         *
         * @return the interceptor.
         */
        public MethodInterceptor getTransactionInterceptor() {
            return transactionInterceptor;
        }
    }


------------


public interface DataSource {

    //---- Methods

    /**
     * @return the name of the data source represented by this instance.
     */
    String getDataSourceName();

    /**
     * @return the properties for the data source represented by this instance.
     */
    Properties getDataSourceProperties();

    /**
     * @return the annotation used for marking methods that should be executed within a transaction and use the session provider specified by this data source.
     */
    Class<? extends Annotation> getTransactionAnnotationType();

    /**
     * @return the session provider for this data source.
     */
    Class<? extends AbstractSessionProvider> getSessionProviderType();

    /**
     * @return the annotation which is used for binding the data source provider.
     */
    Class<? extends Annotation> getDataSourceAnnotationType();

    /**
     * @return the URL filter pattern that determines for which requests the {@link PersistFilter} is applicable.
     */
    String getUrlFilterPattern();
}


------------


public class TransactionInterceptor implements MethodInterceptor {

    //---- Static

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

    //---- Fields

    private final Class<? extends Annotation> annotationToIntercept;
    private final String dataSourceName;

    @Inject
    private Provider<EntityManager> emProvider;


    //---- Constructor

    /**
     * Creates a new Instance.
     *
     * @param dataSource  the data source this transaction interceptor is for.
     */
    public TransactionInterceptor(DataSource dataSource) {
        this.annotationToIntercept = dataSource.getTransactionAnnotationType();
        this.dataSourceName = dataSource.getDataSourceName();
    }


    //---- Methods

    /**
     * {@inheritDoc}
     */
    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        // this will start the corresponding UnitOfWork if it has not been started yet.
        final EnityManager em = emProvider.get();

        final EntityTransaction txn = em.getTransaction();
        final Transaction transaction = Transaction.get(txn, dataSourceName);

        return doTransactional(methodInvocation, transaction, txn);
    }

    private Object doTransactional(MethodInvocation methodInvocation, Transaction transaction) throws Throwable {
        final Object result;
        transaction.begin();

        try {
            result = methodInvocation.proceed();
        }
        catch (Exception e) {
            final TransactionalFacade transactional = readTransactionMetadata(methodInvocation);

            if (rollbackIsNecessary(transactional, e)) {
                transaction.rollback();
            }
            else {
                transaction.commit();
            }

            throw e;
        }

        transaction.commit();
        return result;
    }

    private TransactionalFacade readTransactionMetadata(MethodInvocation methodInvocation) {
        Annotation annotation;
        final Method method = methodInvocation.getMethod();
        final Class<?> targetClass = methodInvocation.getThis().getClass();

        annotation = method.getAnnotation(annotationToIntercept);
        if (null == annotation) {
            // If none on method, try the class.
            annotation = targetClass.getAnnotation(annotationToIntercept);
        }
        if (null == annotation) {
            // If there is no annotation present, use the default
            annotation = Defaults.of(annotationToIntercept);
        }

        return TransactionalFacadeFactory.getFacadeFor(annotation);
    }

    /**
     * Returns True if a rollback is necessary.
     *
     * @param transactional  The metadata annotation of the method
     * @param e              The exception to test for rollback
     * @return {@code true} if a rollback is necessary, {@code false} otherwise.
     */
    private boolean rollbackIsNecessary(TransactionalFacade transactional, Exception e) {
        boolean rollback = false;

        // check rollback clauses
        for (Class<? extends Exception> rollBackOn : transactional.rollbackOn()) {

            // if one matched, try to perform a rollback
            if (rollBackOn.isInstance(e)) {
                rollback = true;

                // check ignore clauses (supercedes rollback clause)
                for (Class<? extends Exception> exceptOn : transactional.ignore()) {
                    // An exception to the rollback clause was found, DON'T rollback
                    // (i.e. commit and throw anyway)
                    if (exceptOn.isInstance(e)) {
                        rollback = false;
                        break;
                    }
                }

                // done checking once we found a hit
                break;
            }
        }

        return rollback;
    }


    //---- Inner Class

    private abstract static class Transaction {

        protected final EntityTransaction txn;
        protected final String dataSourceName;

        public static Transaction get(EntityTransaction txn, String dataSourceName) {
            if (txn.isActive()) {
                return new InnerTransaction(txn, dataSourceName);
            }
            return new OuterTransaction(txn, dataSourceName);
        }

        public Transaction(EntityTransaction txn, String dataSourceName) {
            this.txn = txn;
            this.dataSourceName = dataSourceName;
        }

        public abstract void begin(TransactionImplementor txn);
        public abstract void commit(TransactionImplementor txn);
        public abstract void rollback(TransactionImplementor txn);
    }

    private static class InnerTransaction extends Transaction {
        InnerTransaction(EntityTransaction txn, String dataSourceName) {
            super(txn, dataSourceName);
        }
        @Override
        public void begin() {
            LOG.trace("begin inner transaction ({}) - do nothing", dataSourceName);
            // do nothing
        }
        @Override
        public void commit() {
            LOG.trace("commit inner transaction ({}) - do nothing", dataSourceName);
            // do nothing
        }

        @Override
        public void rollback() {
            LOG.trace("rollback inner transaction ({}) - mark rollback only", dataSourceName);
            txn.markRollbackOnly();
        }
    }

    private static class OuterTransaction extends Transaction {
        OuterTransaction(EntityTransaction txn, String dataSourceName) {
            super(txn, dataSourceName);
        }
        @Override
        public void begin() {
            LOG.trace("begin transaction ({})", dataSourceName);
            txn.begin();
        }
        @Override
        public void commit() {
            LOG.trace("commit transaction ({})", dataSourceName);
            txn.commit();
        }
        @Override
        public void rollback() {
            LOG.trace("rollback transaction ({})", dataSourceName);
            txn.rollback();
        }
    }

    private static final class Defaults implements InvocationHandler {

        // Statics

        /**
         * Returns a dynamic proxy for the given annotation which will return the default values for any method invocation.
         *
         * @param annotation  the annotation for which to create a proxy.
         * @return the proxy.
         */
        public static <A extends Annotation> A of(Class<A> annotation) {
            @SuppressWarnings("unchecked")
            final A proxy = (A) Proxy.newProxyInstance(annotation.getClassLoader(), new Class[] {annotation}, new Defaults());
            return proxy;
        }


        //---- Methods

        /**
         * return the default value of the annotation method
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.getDefaultValue();
        }
    }

}


----------


public interface TransactionalFacade {

    /**
     * A list of exceptions to rollback on, if thrown by the transactional method.
     * These exceptions are propagated correctly after a rollback.
     */
    public Class<? extends Exception>[] rollbackOn();

    /**
     * A list of exceptions to <b>not<b> rollback on. A caveat to the rollbackOn clause.
     * The disjunction of rollbackOn and ignore represents the list of exceptions
     * that will trigger a rollback.
     * The complement of rollbackOn and the universal set plus any exceptions in the
     * ignore set represents the list of exceptions that will trigger a commit.
     * Note that ignore exceptions take precedence over rollbackOn, but with subtype
     * granularity.
     */
    public Class<? extends Exception>[] ignore();

}

Reply all
Reply to author
Forward
0 new messages