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();
}
--
You received this message because you are subscribed to the Google Groups
"google-guice" group.
To view this discussion on the web visit
https://groups.google.com/d/msg/google-guice/-/CO-fhWjs1IcJ.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/google-guice?hl=en.