【问题标题】:Enable no-proxy behaviour for all FetchType.LAZY non-collections by default in Hibernate在 Hibernate 中默认为所有 FetchType.LAZY 非集合启用无代理行为
【发布时间】:2013-11-12 11:51:27
【问题描述】:

使用标准 JPA 注释时,您可以在非集合字段(即 @ManyToOne@OneToOne)上指定 FetchType.LAZY。在这种情况下,Hibernate 似乎在内部使用“代理”获取。但是代理获取在继承方面存在问题,我认为将无代理获取与字节码检测结合使用会更好。不幸的是,Hibernate 仍然要求您在 hbm 文件中指定“no-proxy”或使用 Hibernate 特定的 @LazyToOne 注释。

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

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

我有一个糟糕的解决方法的想法:通过使用字节码修改,我在@ManyToOne 出现的任何地方添加@LazyToOne 注释。但我更喜欢内置的 Hibernate 功能(如果存在)。


这里是代理获取的(众所周知的)问题,让事情更清楚一点:

@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;
    }
}

准备:

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

断言失败(在另一个 EM,另一个事务中):

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);

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

@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 注释的情况下默认启用相同的功能。

【问题讨论】:

  • 我可以解释一下,无代理获取有什么帮助?好吧,无代理获取会修改实体的字节码(也需要启用字节码检测),所以在我读取惰性字段的值的任何地方,它都会插入指令,从数据库中获取字段的值。并且总是获取正确的实例,而不是代理,子类化基类。
  • 快速搜索后,我在这里查看了docs.jboss.org/hibernate/core/4.3/manual/en-US/html/… 并没有找到任何配置。也可能是 Hibernate 和其他 JPA 实现之间延迟获取的性能差异也基于这些策略(EclipseLink 将此字节码操作称为“编织”和 OpenJPA -“增强”)。
  • 是的,我已经阅读了本手册,也没有发现任何配置。但是,有些事情在 Hibernate 中描述得不够好。所以我认为可能有一个未记录的功能?此外,AFAIK、OpenJPA 和 EclipseLink 在默认情况下都使用基于字节码修改的延迟获取(如果启用了编织,或者除了收集之外急切地做所有事情)。但是切换ORM实在是太痛苦了……

标签: java hibernate jpa


【解决方案1】:

好的,我放弃接收答案了。我仔细检查了 Hibernate 源代码并得出结论,Hibernate 本身没有属性来实现我想要的。但是我想出了一个小窍门,这正是我想要的。所以,这里是:

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);
                }
            }
        }
    }
}

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

要使用此 hack,您应该在 persistence.xml 中指定以下内容:

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

完整示例可在here获取。

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

我还将向 Hibernate 团队发布功能请求。

【讨论】:

  • 嗨阿列克谢!您是否创建了票证?
猜你喜欢
  • 2011-08-17
  • 2020-10-24
  • 1970-01-01
  • 2017-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多