Michael Remijan
Michael Remijan

Reputation: 707

CDI inject a @PersistenceContext based on user input

I'm trying to figure out how to do with with CDI. I'm working on an application where users are able to connect to multiple databases. The Application asks which they want to connect to and they can also disconnect and connect to a different database. This is a Java SE application using Seam for CDI and Hibernate for JPA. What I'm trying to figure out is how to leverage CDI to wire up @PersistenceContext but also make it dynamic so different databases can be accessed. I'm just not sure the pattern or technique I would use to do this with CDI. Thoughts?

Upvotes: 2

Views: 540

Answers (2)

Michael Remijan
Michael Remijan

Reputation: 707

So I think I figured out what I wanted to do by using javax.enterprise.inject.Instance. First define a simple bean:

@Alternative
public class Foo {
    private int value;

    public void setValue(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}

It is defined as an @Alternative so CDI doesn't get confused between this and the producer method (shown next). A better solution would be too define Foo as an interface then FooImpl would be annotated with @Typed so CDI thinks it's only a FooImpl bean type. Anyway, next is a producer class.

@ApplicationScoped
public class FooProducer {
    private int value;
    public FooProducer() {
        value = -1;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    @Produces
    public Foo getFoo() {
        Foo p = new Foo();
        p.setValue(getValue());
        return p;
    }
}

The getFoo() method produces a new Foo object with different values. The value can be changed by the setValue(int) method. Next I use the javax.enterprise.inject.Instance to inject Foo.

@Inject
Instance<Foo> fooInstance;

@Inject
FooProducer fooProducer;

....

fooProducer.setValue(10);

Foo foo = fooInstance.get();
System.out.printf("foo = %s, %d\n", foo.getValue());

fooProducer.setValue(10000);

foo = fooInstance.get();
System.out.printf("foo = %s, %d\n", foo.getValue());

Here I use the injected fooProducer.setValue() method to change how the producer will produce Foo. When fooInstance.get() is called the first time Foo will contain the value 10 and the second time it will have the value 10000.

Given this simple example, it is easy to apply to getting EntityManager instances to different databases at runtime. There is a bit more to the code which produces the EntityManager but not too much.

Upvotes: 2

maress
maress

Reputation: 3533

PersistenceContext is handled by an EJB container. Since you are not in an EJB container, forget the persistence context and consider using CDI producers.

@ApplicationScoped
public class EntityManagerFactoryProvider {

  private EntityManagerFactory emf;

  public void voidApplicationEMF() {
    //load the EMF here based on dynamic properties
    this.emf = Persistence.createEntityManagerFactpry(Map of loaded properties);
  }

  @Produces
  public EntityManager em() {
    return emf.createEntityManager();
  }
}

Then somewhere in your code

@SessionScoped
public class MyEntityController {
  @Inject
  private EntityManager emf;
}

There are still issues to deal with transactions etc.

In a JSE environment, you must explicitly start and commit/rollback transactions. Since you are using CDI, you can use Interceptors to start-of/check for transaction status and rollback/commit as necessary.

@Interceptor
public class MyInterceptor {
  @Inject
  private EntityManager em;

  @AroundInvoke
  public Object intercept(InvocationContext ic) {
    try {
      //initiate transaction
      em.getTransaction().start ....
      return ic.proceed();
    }catch(Exception ex) { //rollback if necessary }
    finally {//commit transaction if necessary }
  }
}

Upvotes: 0

Related Questions