默认情况下,在Hibernate中为所有FetchType.LAZY非集合启用无代理行为 [英] Enable no-proxy behaviour for all FetchType.LAZY non-collections by default in Hibernate

查看:384
本文介绍了默认情况下,在Hibernate中为所有FetchType.LAZY非集合启用无代理行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用标准的JPA批注时,您可以在非集合字段上指定 FetchType.LAZY (即 @ManyToOne @OneToOne )。看起来Hibernate在这种情况下内部使用代理获取。但是代理获取有其继承的问题,我认为使用无代理获取与字节码检测相结合会更好。不幸的是,Hibernate仍然要求你在 hbm -file中指定no-proxy,或者使用特定于Hibernate的 @LazyToOne 注解。

我的问题是:Hibernate是否支持一个配置选项,对所有非集合字段使用无代理获取策略,即 FetchType.LAZY



这是我需要的:我想在大多数情况下只使用JPA注释一方面。另一方面,我想避免继承和懒惰领域的问题。我不喜欢将所有内容封装在接口中的想法,因为我在当前项目中使用了DDD,所以我认为在我的域模型中不应该存在样板垃圾,只有纯业务逻辑。



我有一个糟糕的解决方法的想法:通过使用字节码修改,我添加 @LazyToOne 注释到处 @ManyToOne <出现code>。但是,如果存在,我宁愿使用内置的Hibernate功能。






这是众所周知的代理抓取问题,使事情变得更清晰:

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

@Entity @DiscriminatorValue(B)
公共抽象类B扩展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;


$ / code $ / pre

准备:

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

和失败声明(在另一个EM中是另一个事务):

  D d = em.createQuery(从D d选择d,D.class).getSingleResult(); 
列表< C> cs = em.createQuery(select c from C c,C.class).getResultList();
assert d.getA()instanceof C;
assert d.getA()== cs.get(0);

以下是我要解决上述断言的方法:

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

我不想要同样的东西没有 @LazyToOne 注解。

解决方案

好吧,我放弃了接受答案。我仔细检查了Hibernate源代码,并得出结论认为Hibernate本身没有任何属性来实现我想要的。但是我想出了一点肮脏的黑客攻击,这正是我想要的。因此,这里是:

$ pre $ public $ D $ H $ $ $ $ $ $ $
@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();

$ b @Override
@SuppressWarnings({rawtypes,unchecked})
public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
Map属性) {
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(); (iter.hasNext()){
hackClass((PersistentClass)iter.next());

}
}
}

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

还必须有一个名为 META-INF / services / javax.persistence.spi.PersistenceProvider 的资源,其中包含具有该类名称的单行。



要使用这种方法,您应该在 persistence.xml 中指定以下内容:

 < provider> packagename.DirtyHackedHibernatePersistence< / provider> 
<属性>
< property name =hibernate.hack.no-proxy-by-defaultvalue =true/>
< / properties>

完整的示例可用 here

请注意,如果您删除 hibernate .hack.no-proxy-by-default 属性和重建项目,这两个断言都被破坏了。



另外我要发布一个特性请求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.

解决方案

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.

这篇关于默认情况下,在Hibernate中为所有FetchType.LAZY非集合启用无代理行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆