views:

2991

answers:

2

Is there a way to change the JPA fetch type on a single method without editing the entity object?

I have a shared ORM layer consisting of JPA entity classes. This ORM layer is accessed by two DAO layers. One DAO needs lazy fetching, as it is for my web application, the other needs eager fetching, as I need it to be threadsafe.

Here is an example method from my threadsafe DAO,

@PersistenceContext(unitName = "PersistenceUnit",
type = PersistenceContextType.TRANSACTION)
private EntityManager em;

public ErrorCode findErrorCodeById(short id) {
    return (ErrorCode) em.createNamedQuery("ErrorCode.findById").
            setParameter("id", id).getSingleResult();
}

How would I make this method (or entire class) use eager fetching?

+3  A: 

I assume that your entity associations (@OneToOne, @OneToMany, @ManyToOne) are fechted lazy (FetchType.Lazy)

Then I can think of two ways:

A. write two jpa query one which fetch the association of the lazy (thats the default way for hibernate) and a second query which explicit force eager loading of association (see "fetch" keyword in query).

        Query q = HibernateUtil.getSessionFactory().getCurrentSession()
                .createQuery("select c from Category as c" +
                        " left join fetch c.categorizedItems as ci" +
                        " join fetch ci.item as i");


B. use Hibernate.initialize(entity) to force eager loading of lazy relations of an entity after you have retrieved it (e.g. through finder ...)

ErrorCode lazyCode = findErrorCodeById(1);
// eager load associations
Hibernate.initialize(lazyCode);
KlausMeier
Thanks Klaus, obviously option B is going make the code dependent on importing Hibernate, which is a draw back, but I'll give that a try.
James McMahon
Hi Nemo, could You please mark my answer with the green checkmark. ;)
KlausMeier
I'm still hitting a 'session closed' issue using method b.
James McMahon
have you called Hibernate.initialize during a transaction? Transaction tx = null; Session session = SessionFactoryUtil.getInstance().getCurrentSession(); try { tx = session.beginTransaction(); ErrorCode lazyCode = findErrorCodeById(1); // eager load associations Hibernate.initialize(lazyCode); tx.commit(); } ...
KlausMeier
Klaus, I am actually using annotated sessions/transactions managed by Spring.
James McMahon
Coming back to this question with a little more experience with JPA, I now realize that JPA has some serious limitations, at least in the initial version, 2 might fix some of these issues. So your suggestion of using Hibernate directly is probably the way to go.
James McMahon
+1  A: 

In JPA the Fetch mode is specified on each persistence attribute, either through an annotation or in an xml mapping file.

So a JPA vendor agnostic way to accomplish your goal is to have separate mapping file for each DAO layer. Unfortunately this will require a separate PersistenceUnit for each mapping file, but you can at least share the same entity classes and the same JPQL query.

Code skeletons follow.

persistence.xml :

<persistence>
    <persistence-unit name="dao-eager">
        <mapping-file>orm-eager.xml</mapping-file>
    </persistence-unit>

    <persistence-unit name="dao-lazy">
        <mapping-file>orm-lazy.xml</mapping-file>
    </persistence-unit>
</persistence>

orm-eager.xml :

<entity-mappings>
    <entity class="ErrorCode">
     <attributes>
      <basic name="name" fetch="EAGER"/>
     </attributes>
    </entity> 
</entity-mappings>

orm-lazy.xml :

<entity-mappings>
    <entity class="ErrorCode">
     <attributes>
      <basic name="name" fetch="LAZY"/>
     </attributes>
    </entity> 
</entity-mappings>

Then it's just a matter of creating an EntityManagerFactory for the appropriate persistence-unit in your DAO layers.

Actually you don't need two mapping files, you could specify either LAZY or EAGER as an annotation in the Entity and then specify the opposite in an xml mapping file (you'll still want two persistence-units though).

Might be a little more code than the Hibernate solution above, but your application should be portable to other JPA vendors.

As an aside, OpenJPA provides similar functionality to the Hibernate solution above using FetchGroups (a concept borrowed from JDO).

One last caveat, FetchType.LAZY is a hint in JPA, the provider may load the rows eagerly if needed.

Updated per request.

Consider an entity like this :

@Entity 
public class ErrorCode { 
    //  . . . 
    @OneToMany(fetch=FetchType.EAGER)  // default fetch is LAZY for Collections
    private Collection myCollection; 
    // . . .
}

In that case you'd still need two persistence units, but you'll only need orm-lazy.xml. I changed the field name to reflect a more realistic scenario (only collections and blobs use FetchType.LAZY by default). So the resulting orm-lazy.xml might look like this :

<entity-mappings>
    <entity class="ErrorCode">
        <attributes>
            <one-to-many name="myCollection" fetch="LAZY"/>
        </attributes>
    </entity> 
</entity-mappings>

And persistence.xml will look like this :

<persistence>
    <persistence-unit name="dao-eager">
       <!--
          . . .
         -->
    </persistence-unit>

    <persistence-unit name="dao-lazy">
        <!--
           . . . 
          -->
        <mapping-file>orm-lazy.xml</mapping-file>
    </persistence-unit>
</persistence>
Mike
Mike, can you go into more detail on "Actually you don't need two mapping files, you could specify either LAZY or EAGER as an annotation in the Entity and then specify the opposite in an xml mapping file"?
James McMahon
I've added a quick example. Hope it helps.
Mike
We'll editing the xml to change the annotations is pretty much like editing the entity object. However this might be the best option I have.
James McMahon