1

Does JPA support optional persistence units and if so how do I configure that?

I do have one persistence unit which is my main database.

Then I configured another one where I just read objects from the db to do some checks. To avoid messing around with transactions on multiple datasources I set the second datasource jta="false".

But I like to be able to check if a second datasource was defined at all and only use it if it is there. If it is not defined I'd like to simply skip my checks.

The problem is that I can not find how to make this optional. If the second datasource is not configured I get a New missing/unsatisfied dependencies error from the deployment scanner in Jboss when my war is deployed:

service jboss.naming.context.java.secondDatasource (missing) dependents: [service jboss.persistenceunit."de.my.war#secondDatasource"] 

(Btw: I am using JBoss 7.1.0 and configured the datasources in standard.xml - if that info is of any relevance.)

Any hints?

Jens
  • 6,243
  • 1
  • 49
  • 79

2 Answers2

4

I'm not sure it's possible with XML configuration which is basically a convention of how JPA should be used in most of the cases. Try to look for a programmatic approach.

Perhaps you could programmatically lookup the datasource as JNDI resource, and if there is one found, you could build an EntityManager yourself. With the help of CDI it might even not be that difficult as you would think.

@Produces
@MyAlterNativeEntityManager
public EntityManager getEntityManager {
    EntityManager entityManager = null;
    if(jndiLookupMySecondDatasource()){
       entityManager = buildEntityManagerProgrammatically(...);
    }
    return entityManager;
}

Of course if the datasource is not found, an empty EntityManager will be returned but then in the calling code you could simply check if it was initialized and use only if it was. Hope that helps to put you on the right way.

Community
  • 1
  • 1
Balázs Németh
  • 6,222
  • 9
  • 45
  • 60
  • Thanks for the idea. I think this already happens when my `persistence.xml` is processed. But I will check this... – Jens Mar 06 '13 at 14:46
  • Of course, but this way you don't even have to declare the second datasource in the `persistence.xml`! – Balázs Németh Mar 06 '13 at 21:57
  • @Balzás Mária Németh: Sorry for my delayed answer to this. I tried your solution of building an `EntityManager` myself if there is a `datasource` found via JNDI. But: I don't know how to set the found `datasource` on my self-created `EntityManager` resp. `EntityManagerFactory`. It only accepts `properties` to create one. But I have a `datasouce` at hand. I thought about mapping all necessary properties myself but the password is something I can not find on the `datasource`. Is there a way to let the created `EntityManager` use my `datasource`? – Jens Mar 18 '13 at 10:10
  • I don't think so. As the other answer of Glen Best also mentions, EMF must relate to one PU, so you cannot replace its datasource, and there's no point creating one without a datasource. If there's a datasource, create the EMF, if there isn't, you cannot create one. Is this answer useful to you? Or I might have misunderstood the question. – Balázs Németh Mar 18 '13 at 10:32
  • I thought I could create a second `EntityManagerFactory` for the second `datasource`. So this is a second `PersistenceUnit`. All of this already works if I define it in the `persistence.xml` file. The only problem is that I am running into exceptions when the second `datasource` is not defined (in my standalone.xml file on Jboss). Following your idea I thought I could shift the creation of the second `EntityManagerFactory` to runtime so I can avoid the exceptions on startup. – Jens Mar 18 '13 at 10:56
  • Yes, but you have to do this in the `getEntityManager` method. You have to do any creation there. – Balázs Németh Mar 18 '13 at 11:35
  • I am trying to do it in that method. But now my problem is as described in the third comment. I have datasource (with Driver, URL, user, password, etc) at hand but the creation of an `EntityManagerFactory` only accepts a `Properties` map. Mapping each attribute from the `datasource` to the `properties` does not work because I can't get the password from the datasource object. – Jens Mar 18 '13 at 12:57
  • I understand now but unfortunately I don't know the solution. I've never done something like this, I only had an idea which I shared with you, but I cannot help you with the implementation. Sorry. :( – Balázs Németh Mar 18 '13 at 13:13
1

In the JPA standard:

  • Each EntityManagerFactory (EMF) must relate to one PersistentUnit (PU) - not possible to have multiple PersistenceUnits nor zero PersistenceUnits.
  • Each EntityManager (EM) instance is created from an EMF and so also has a single PU. Of course, can create multiple EMs from an EMF, all using the same PU.
  • Can configure which PU to use explicitly against each EMF using hard coding

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("EmployeeService");

    or by injecting the EMF using annotations

    @PersistenceUnit("EmployeeService") EntityManagerFactory emf;

    or can leave it blank and just pick up the default PU as configured in persistence.xml.

  • Can configure which PU to use explicitly against each EM by injecting the EM using annotations. Here the code doesn't refer to the EMF at all (letting the container reference an EMF invisibly)

    @PersistenceContext(unitName="EmployeeService") EntityManager em;

In all of this the idea of optional PUs doesn't make any sense. If you are using JPA EMs, you must commit to a PU.

Options:

  1. Edit persistence.xml to have the same logical persistence unit "switch" between two different physical database instances. Doesn't meet full requirements - will still read values from alternative data source.
  2. Lookup/inject an environment variable ("debug" or not) and programmatically flick between different data sources as per solution of Balazs Maria Nemeth. Doesn't meet full requirements, because it will still read values from alternative data source.
  3. Lookup/inject an environment variable ("debug" or not) and run/exclude code for your alternative EM appropriately.

3 is only option that meets full requirements. E.g.:

// Inject environment setting. Resource annotation works with/without CDI -  
// just doesn't give scoping support, which isn't required here.
@Resource boolean debugMode;

if (debugMode) {
    @PersistenceContext(unitName="DebugPersistenceUnit")
    EntityManager debugEM;

    Employee emp = debugEM.find(Employee.class, empId);
}

Then in your web.xml or ejb.xml include an env-entry:

<env-entry>
    <description>
     Only set this true in a debugging environment
    </description>
    <env-entry-name>debugMode</env-entry-name>
    <env-entry-type>java.lang.Boolean</env-entry-type>
    <env-entry-value>true</env-entry-value>
</env-entry>
Glen Best
  • 22,769
  • 3
  • 58
  • 74