16

I need a way to dynamically specify the persistence unit in a EJB.

Simplified example:

I have an application using multiple databases as data stores. Each of the data stores are structurally the same. Depending on the client connecting to the application I need to access the data from a specific data store.

Therefore, I would like to use the same EJB so that the business logic is not duplicated, but then just select the correct persistence unit based on the client.

Up to this point I've only directly injected the entity manager with the persistence unit name hard coded. Is there a way I can dynamically inject the entity manager with the required persistence unit attached to the EJB? Also, can persistence units be added dynamically during runtime? I currently have to specify the persistence unit in the persistence.xml file. Ideally I would like to create pools on the server jdbc/db1, jdbc/db2 etc as required while the system is running. Then just add these to the central client database and link it to a client, so that when the client connects, it will check the name of the pool, and use it when calling the EJB to get the persistence unit.

I'm still really new to Java EE development. Any help would be greatly appreciated.

likenoother
  • 429
  • 1
  • 3
  • 11

4 Answers4

9

In the current JPA version, it's unfortunately not possible to create persistence units dynamically. If this functionality it's important for you, you could consider creating a JIRA issue for it at the JPA issue tracker: http://java.net/jira/browse/JPA_SPEC

Using the @PersistenceContext annotation, it's also not possible to dynamically select a specific persistence unit. This is actually the domain of sharding, which Hibernate once tried to address but then suddenly discontinued. See http://www.hibernate.org/subprojects/shards.html

There are however a few thing you could do to get a similar effect.

One approach is to create a Stateless EJB/CDI factory bean, that you inject with all your entity managers. The cost of this is marginal, since those beans will be pooled and entity managers are not that expensive to be created in the first place.

If you also want to inject them based on some condition, then this condition will have to be available from the context or has to be specified at the injection point (but if you would do that, you could just as well inject the right entity manager directly).

A kickoff example:

@Stateless
@TransactionAttribute(SUPPORTS)
public class ShardingEntityManagerFactory {

    @Resource
    private SessionContext sessionContext;

    @PersistenceContext(unitName = "pu1")
    private EntityManager entityManager1;

    @PersistenceContext(unitName = "pu2")
    private EntityManager entityManager2;

    @Produces @TransactionScoped @ShardedPersistenceContext
    public EntityManager getEntityManager() {
        if (sessionContext.isCallerInRole("FOO")) {
            return entityManager1;
        } else {
            return entityManager2;
        }
    }
}

And then in your beans:

@Stateless
public class SomeBean {

    @Inject @ShardedPersistenceContext
    private EntityManager entityManager;

    // ...
}

Note that you would need Seam Persistence here for the @TransactionScoped annotation. It also might be easier, but a little more verbose, to forget about injecting the entity manager transparently and inject the ShardingEntityManagerFactory instead and obtain the right one from it manually.

Arjan Tijms
  • 37,782
  • 12
  • 108
  • 140
  • This is almost what I need, but I still need to define each persistence unit upfront into the SharedEntityManagerFactory. I would have liked to be able to add them dynamically so that no code changes are required and no redeployment required. I can simply save the persistence unit details in the main database and link it to the client when required. But this is already much better than implementing each data store as a separate web service. Much less overhead. The web service has the added benefit of being able to store the URL in db and use it dynamically. I'll have to way up the options. – likenoother Apr 01 '12 at 08:31
3

This probably is of no help to solve the problem, but you may like to know that this kind of problems is being discussed for the implementation of JPA 2.1

This sounds like one of those cases of multitenancy:

Proposal for Multitenancy Support in JPA 2.1 JSR-338

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
  • I think it's technical not that far away from multitenancy indeed, but whereas there a single DB is divided into equal shares and shared between multiple tenants, here a singe tenant wants multiple DBs appearing as a single one. – Arjan Tijms Mar 31 '12 at 19:31
  • @ArjanTijms is correct. What I'm looking for is similar to multitenancy, but instead of using one database, I would like to use more than on database. Maybe it is worth separating each database into separate EJB deployments and then connecting to then via web service. I don't like the overhead, but at least it gives me the freedom of supporting databases across different systems if needed. Store clients on different machines. Not sure if remote interfaces can be used for this? – likenoother Apr 01 '12 at 08:36
  • @likenoother good thinking! Yes, the one dynamic way to add persistence units to a running system is by hot deploying EJB modules (ejb jars) to an AS. Looking up beans from what are technically "different applications on the same AS" via a local interface is not specified, but actually does work in practice. Instead of injecting those beans directly, you can use programmatic JNDI lookups to dynamically get beans that encapsulate those dynamically added persistent units. The setup is a little unorthodox though, but it might just work. – Arjan Tijms Apr 01 '12 at 11:51
  • @ArjanTijms How would I be able to look up a specific deployment of a EJB module? I've only ever injected them directly. I've read about local and remote interfaces, but from what I understand they tightly couple to a specific EJB deployment. – likenoother Apr 01 '12 at 17:50
  • I think I figured out how to lookup a EJB via JDNI. Thanks @ArjanTijms for the advice. Will try JDNI lookup of EJB applications deployed on the server. – likenoother Apr 01 '12 at 18:06
  • JNDI lookup is simple, the simplest version is `new InitialContext().lookup(name)`. Every EJB bean automatically gets an EJB name and path as I explained here: http://en.wikipedia.org/wiki/Ejb#Naming_and_directory_services – Arjan Tijms Apr 01 '12 at 19:05
  • @likenoother so you if you have 20 clients you will basically deploy 20 different EJB3 JPA jars with the "entity"/"facade" classes and different hardcoded @PersistenceContext(unitName = "user38PersistenceUnit") annotations in each ? (otherwise the classes in each jar being identical?). And then you would access them from your backing beans by looking them up using JNDI instead of injecting them? That's the pattern? – Marcus Junius Brutus Aug 08 '12 at 14:40
  • @(Arjan Tijms). Yeah, but such automatically assigned names do not allow for distinct names of the same bean class / package name which would be needed to implement a pattern of hardcoding different @PersistenceContext values in each facade bean (for each of the different datastores) and having the front-end code dynamically look up the right facade bean to use based on JNDI lookup instead of injection. – Marcus Junius Brutus Aug 13 '12 at 08:40
2

You can use the same persistence unit for this. You just need to provide a properties Map when you call createEntityManagerFactory() with the url/datasource you want to use.

James
  • 17,965
  • 11
  • 91
  • 146
  • Nice idea, but that doesn't work in A Java EE environment does it? You can inject an em factory, but the create method is for Java SE environments only. – Mike Braun Oct 03 '12 at 08:30
1

Just an idea, not sure if it'll help: You may open a inputStream to read the persistence.xml file and replace these lines:

<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/agendasccv2?zeroDateTimeBehavior=convertToNull"/>
<property name="javax.persistence.jdbc.password" value="btxbtxbtx"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.user" value="root"/>

Then splash a connection config screen where the user logs in, and set the connection according to the user privileges on that file, then start the main application

Not sure if that will help, it's just and idea. Depends on your application and business logic.

Brad Werth
  • 17,411
  • 10
  • 63
  • 88
javier_tf
  • 11
  • 1