14

When using standard JPA annotations, you can specify FetchType.LAZY on non-collection fields (i.e. @ManyToOne and @OneToOne). It seems Hibernate internally uses "proxy" fetching in this case. But proxy fetching has its problems with inheritance and I think it is better to use no-proxy fetching in combination with bytecode instrumentation. Unfortunately, Hibernate still requires you either to specify "no-proxy" in the hbm-file or to use the Hibernate-specific @LazyToOne annotation.

My question is: does Hibernate support a configuration option to use a no-proxy fetch strategy for all non-collection fields, that are FetchType.LAZY?

Here is what I need this for: I would like to use only JPA annotations in most cases on the one hand. On the other hand, I'd like to avoid issues with inheritance and lazy fields. And I don't like the idea of wrapping everything in interfaces, because I use DDD in my current project, so I think that no boilerplate garbage should exist in my domain model, only pure business logic.

I have an idea of a poor workaround: by using bytecode modification, I add @LazyToOne annotation everywhere @ManyToOne appears. But I would prefer a built-in Hibernate feature, if exists.


Here is (well known) issue with proxy fetching, to make things a bit clearer:

@Entity @DiscriminatorColumn("t") @DiscriminatorValue("")
public abstract class A {
    @Id private Integer id;
}

@Entity @DiscriminatorValue("B")
public abstract class B extends A {
}

@Entity @DiscriminatorValue("C")
public abstract class C extends A {
}

@Entity public class D {
    @Id private Integer id;
    @ManyToOne(fetch = FetchType.LAZY) private A a;
    public A getA() {
        return a;
    }
}

prepare:

D d = new D();
C c = new C();
d.setA(c);
em.persist(d);

and fail assertion (in another EM, another transaction):

D d = em.createQuery("select d from D d", D.class).getSingleResult();
List<C> cs = em.createQuery("select c from C c", C.class).getResultList();
assert d.getA() instanceof C;
assert d.getA() == cs.get(0);

Here's what I would do to fix the assertions above:

@Entity public class D {
    @Id private Integer id;
    @ManyToOne(fetch = FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY)
    private A a;
    public A getA() {
        return a;
    }
}

And I don't want the same thing to be enabled by default, without @LazyToOne annotation.

Sameer Singh
  • 1,358
  • 1
  • 19
  • 47
Alexey Andreev
  • 1,980
  • 2
  • 17
  • 29
  • May be I am to explan, how no-proxy fetching helps? Well, no-proxy fetching modifies entity's bytecode (also required bytecode instrumentation enabled), so everywhere I read the lazy field's value it inserts instructions, that fetch field's value from database. And always the proper instance gets fetched, not proxy, subclassing base class. – Alexey Andreev Nov 26 '13 at 10:23
  • After a quick search I have looked here http://docs.jboss.org/hibernate/core/4.3/manual/en-US/html/ch03.html#configuration-optional-properties and didn't find any configuration for that. Also it may be that the performance differences on lazy fetching between Hibernate and other JPA implementations are based also these strategies (EclipseLink calls this bytecode manipulation "Weaving" and OpenJPA - "Enhancement" ). – V G Nov 26 '13 at 10:56
  • Yes, I've read this manual and found no configuration too. But, there are things that are not described good enough in Hibernate. So I thought there might be an undocumented feature? Also, AFAIK, OpenJPA and EclipseLink both use lazy fetching based on bytecode modification by default (if weaving enabled, or do everything eagerly except collection, otherwise). But switching ORM is so painful... – Alexey Andreev Nov 26 '13 at 11:04

1 Answers1

4

Ok, I gave up receiving an answer. I carefully examined Hibernate source code and made conclusion that Hibernate itself has no property to achieve what I wish to. But I came up with a little dirty hack, that gives me exactly what I want. So, here it is:

public class DirtyHackedHibernatePersistence extends HibernatePersistence {
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName,
            Map properties) {
        properties.put(AvailableSettings.PROVIDER, HibernatePersistence.class.getName());
        Ejb3Configuration cfg = new Ejb3Configuration().configure(persistenceUnitName, properties);
        if (cfg == null) {
            return null;
        }
        cfg.buildMappings();
        hackConfiguration(cfg);
        return cfg.buildEntityManagerFactory();
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
            Map properties) {
        properties.put(AvailableSettings.PROVIDER, HibernatePersistence.class.getName());
        Ejb3Configuration cfg = new Ejb3Configuration().configure(info, properties);
        if (cfg == null) {
            return null;
        }
        cfg.buildMappings();
        hackConfiguration(cfg);
        return cfg.buildEntityManagerFactory();
    }

    private void hackConfiguration(Ejb3Configuration cfg) {
        System.out.println("Hacking configuration");
        String noProxyByDefault = cfg.getProperties().getProperty("hibernate.hack.no-proxy-by-default", "false");
        if (Boolean.parseBoolean(noProxyByDefault)) {
            Iterator<?> iter = cfg.getClassMappings();
            while (iter.hasNext()) {
                hackClass((PersistentClass)iter.next());
            }
        }
    }

    private void hackClass(PersistentClass classMapping) {
        Iterator<?> iter = classMapping.getPropertyIterator();
        while (iter.hasNext()) {
            Property property = (Property)iter.next();
            if (property.getValue() instanceof ToOne) {
                ToOne toOne = (ToOne)property.getValue();
                if (toOne.isLazy()) {
                    toOne.setUnwrapProxy(true);
                }
            }
        }
    }
}

also there must be a resource named META-INF/services/javax.persistence.spi.PersistenceProvider containing a single line with name of the class.

To use this hack, you should specify the following in a persistence.xml:

<provider>packagename.DirtyHackedHibernatePersistence</provider>
<properties>
   <property name="hibernate.hack.no-proxy-by-default" value="true"/>
</properties>

The full example is available here.

Note that it if you remove the hibernate.hack.no-proxy-by-default property and rebuild project, both assertions get broken.

Also I am going to post a feature request to the Hibernate team.

Alexey Andreev
  • 1,980
  • 2
  • 17
  • 29