views:

979

answers:

2

Hi, all,

I'm looking for suggestions on how to inject runtime dependencies into JPA entities retrieved from Hibernate. My problem is essentially this:

I have a number of different subclasses of a Transaction object. Each Transaction subclass has different behavior when it is executed, and requires a different set of dependencies from the environment. These Transaction objects are managed as JPA entities by Hibernate, so I can't effectively use Guice for dependency injection to populate the instances with their environmental dependencies as I do in the rest of my application.

To get around this problem, I've taken an approach that's somewhat akin to the Visitor pattern, as follows:

public abstract class Transaction {
    // ...snip...
    public abstract void apply(Transactor transactor);
}

public class TransactionA extends Transaction {
    public void apply(Transactor transactor) {
        transactor.execute(this);
    }
}

public class TransactionB extends Transaction {
    public void apply(Transactor transactor) {
        transactor.execute(this);
    }
}
// other Transaction subclasses with the same boilerplate

public interface Transactor {
    public void execute(TransactionA trans);
    public void execute(TransactionB trans);
    // corresponding methods for other transaction types.
}

public class BeginTransactor {
     @Inject
     private Foo execAdep;
     public void execute(TransactionA trans) {
         execAdep.doSomething(...)    
     }

     @Inject
     private Bar execBdep;
     public void execute(TransactionB trans) {
         execBdep.doOther(...)    
     }
 }

I have various implementations of Transactor for different parts of the transaction lifecycle. These can be dependency-injected using Guice into the context in which I want to process the transactions, where I simply call:

 Transactor transactor = injector.getInstance(BeginTransactor.class); //Guice injection
 Transaction t = ... //get a transaction instance
 t.apply(transactor);

What I don't like about this approach is (1) Not every type of transaction should be executable in each lifecycle phase, but every Transactor must implement an execute() method for every transaction subclass and (2) Essentially none of the injected dependencies are used for processing more than one transaction type.

Essentially, my Transactor interface & implementations have a lot of unrelated crud glopped together. Ideally I'd just have the execute() method on the transaction object itself, but I don't want the calling code to have to know about the type of the Transaction or the dependencies that it requires. Also, this could make testing harder because I couldn't easily mock out the execute() method if it were a concrete method on the Transaction object. Using the Transactor interface means that I can easily mock it out as needed.

Can anyone suggest how to address this problem in a typesafe manner that doesn't result in a bunch of mostly-unrelated behavior being glommed together in the Transactor, but keeps the testability and allows for dependency injection?

+2  A: 

I use guice for transactions, but I use the AOP to carry them out. I have almost no boilerplate, at the expense of a little "magic". As long as your intercepted class is "in the club," it works really well.

class BusinessLogic {
    @Inject public EntityManager em;

    @Transactional
    publc void doSomething() {
       //...
       em.persist(myObj);
    }

    @Transactional
    public void doSomethingElse() {
       //...
       em.delete(myObj);
    }
}

class TransactionalInterceptor implements MethodInterceptor {
    @Inject static Injector injector;
    public Object intercept(MethodInvocation invocation) {
        EntityManager em = injector.getInstance(EntityManager.class);
        em.getTransaction().begin();
        Object result = invocation.proceed();
        em.getTransaction().commit();
        return result;
    }
}
class TransactionalModule extends AbstractModule {
    public void configure() {
        requestStaticInjection(TransactionalInterceptor.class);
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class),
                 new TransactionalInterceptor());
    }
}
SamBeran
That's excellent, thank you! I had suspected that there might be an AOP approach to this problem, but wasn't exactly sure how to go about it.
Kris Nuttycombe
+2  A: 

Check: http://www.hibernate.org/hib_docs/v3/api/org/hibernate/Interceptor.html

Configure your hibernate to use an Intercetor. The method

public Object instantiate(String entityName,
                      EntityMode entityMode,
                      Serializable id)

will be called to

Instantiate the entity class. Return null to indicate that Hibernate
should use the default constructor of the class. The identifier property
of the returned instance should be initialized with the given identifier.

You may call your injector.getInstance() from there.

Maybe, you could use the getEntity() or the onLoad() methods. From the onLoad() method you could invoke the injector.injectMembers().

Banengusk