Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enable no-proxy behaviour for all FetchType.LAZY non-collections by default in Hibernate

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.

like image 493
Alexey Andreev Avatar asked Nov 12 '13 11:11

Alexey Andreev


People also ask

What is default FetchType in Hibernate?

FetchType, on the other hand, defines whether Hibernate will load data eagerly or lazily. The exact rules between these two are as follows: if the code doesn't set FetchMode, the default one is JOIN and FetchType works as defined. with FetchMode.

Is lazy loading default in Hibernate?

By default, Hibernate uses lazy select fetching for collections and lazy proxy fetching for single-valued associations. These defaults make sense for most associations in the majority of applications. If you set hibernate.

Is FetchType lazy default?

By default, Fetch type would be Lazy. FetchType. LAZY: It fetches the child entities lazily, that is, at the time of fetching parent entity it just fetches proxy (created by cglib or any other utility) of the child entities and when you access any property of child entity then it is actually fetched by hibernate.

What is proxy in Hibernate lazy loading?

By definition, a proxy is “a function authorized to act as the deputy or substitute for another”. This applies to Hibernate when we call Session. load() to create what is called an uninitialized proxy of our desired entity class. Simply put, Hibernate subclasses our entity class, using the CGLib library.


1 Answers

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.

like image 90
Alexey Andreev Avatar answered Sep 22 '22 02:09

Alexey Andreev