默认情况下,在休眠中为所有 FetchType.LAZY 非集合启用无代理行为

2022-09-04 04:36:44

使用标准 JPA 注释时,可以在非集合字段上指定(即 和 )。在这种情况下,Hibernate似乎在内部使用“代理”获取。但是代理提取在继承方面有其问题,我认为最好将无代理提取与字节码检测结合使用。遗憾的是,Hibernate 仍然需要您在 -file 中指定“no-proxy”或使用特定于 Hibernate 的注释。FetchType.LAZY@ManyToOne@OneToOnehbm@LazyToOne

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

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

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


以下是代理获取的(众所周知的)问题,以使事情更清晰一些:

@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


答案 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

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

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

此处提供了完整的示例。

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

另外,我将向Hibernate团队发布一个功能请求。


答案 2

推荐