@ManyToOne(fetch = FetchType.LAZY)不适用于非主键引用列

kot*_*dir 11 hibernate jpa

我有一些麻烦要让@ManyToOne关联加载lazilly.我正在使用fetch = LAZY但是当主键列没有进行连接时它不起作用.

我知道这个问题已被提出但我认为没有得到妥善回答,因此我提供了详细信息以澄清问题.

这是我的模特:

DummyB -> DummyA
Run Code Online (Sandbox Code Playgroud)

这些是表格:

create table dummyA  (
  id number(18,0), --pk
  name varchar2(20) -- unique field
);

create table dummyB  (
  id number(18,0),
  dummya_id number(18,0),
  dummya_name varchar2(20)
);
Run Code Online (Sandbox Code Playgroud)

这些是实体:

@Entity
public class DummyA implements Serializable {

    private Long id;
    private String name;

    @Id
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

@Entity
public class DummyB implements Serializable {

    private Long id;
    private DummyA dummyA;

    @Id
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    /* Case 1: mapping DummyB -> DummyA by DummyA NON primary key (field name) */
    // @ManyToOne(fetch = FetchType.LAZY)
    // @JoinColumn(name = "dummya_id")
    // public DummyA getDummyA() {
    // return dummyA;
    // }

    /* Case 2: mapping DummyB -> DummyA by DummyA primary key */
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "dummya_name", referencedColumnName = "name")
    @LazyToOne(LazyToOneOption.PROXY)
    public DummyA getDummyA() {
        return dummyA;
    }

    public void setDummyA(DummyA dummyA) {
        this.dummyA = dummyA;
    }

}
Run Code Online (Sandbox Code Playgroud)

注意实体DummyB中的getDummyA方法是重复的,可以尝试两种情况来加入实体.

案例1:通过DummyA主键映射DummyB - > DummyA

@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name ="dummya_id")

这工作正常,只执行一个查询来检索DummyB对象.

案例2:通过DummyA NON主键(字段名称)映射DummyB - > DummyA

@ManyToOne(fetch = FetchType.LAZY)@JoinColumn(name ="dummya_name",referencedColumnName ="name")

执行相同的dummyB select,但是之后,执行dummyA select,按名称=?进行过滤 获取相关的A对象.

我正在使用一个非常简单的jUnit来执行过滤:

public class DummyTest {

    @Autowired
    HibernateTransactionManager transactionManager;

    @Test
    @Transactional
    public void testFindDummyB() throws DAOException {
        Long idDummyB = 2L;

        Session session = getCurrentHibernateSession();

        List lst = session.createCriteria(DummyB.class)
                .add(Restrictions.eq("id", idDummyB)).list();

        assertTrue(lst.size() > 0);
    }

    private Session getCurrentHibernateSession() {
        return this.transactionManager.getSessionFactory().getCurrentSession();
    }

}
Run Code Online (Sandbox Code Playgroud)

我的图书馆:

  • org.hibernate作为:休眠核心:罐子:4.2.17.Final:编译
  • org.hibernate.common:冬眠公地的注解:罐子:4.0.2.Final:编译
  • org.hibernate.javax.persistence:休眠-JPA-2.0-API:罐:1.0.1.Final:编译
  • org.hibernate作为:休眠-验证:罐子:4.3.2.Final:提供

我已经尝试过的其他事情:

  • 将hiberante的@LazyToOne添加到getDummyA()方法没有任何效果.

    @LazyToOne(LazyToOneOption.PROXY)@ManyToOne(fetch = FetchType.LAZY,optional = true)@JoinColumn(name ="dummya_name",referencedColumnName ="name")@LazyToOne(LazyToOneOption.PROXY)

  • 从DummyB表到dummyA(以及dummya.name字段中的唯一约束)创建外键无效.

  • 在DummyA getName()方法上添加@Column(unique = true)没有成功.
  • 设置optional = true或false,这里建议 也没有效果.
  • 尝试使用条件中的s​​etFetchMode强制延迟加载不起作用,DummyA select继续执行.

    List lst = session.createCriteria(DummyB.class).add(Restrictions.eq("id",idDummyB)).setFetchMode("dummyA",FetchMode.SELECT) .list();

我在Hibernate的文档中找不到它引用这种行为的一点,所以我想知道我的注释中是否有任何错误,或者我遇到了一个Hibernate错误.

谁能说出来?

由md-dev请求更新: 更清楚地设置它:

这是预期的行为还是一个错误?如果这是预期的行为,它在哪里记录?

谢谢.

小智 9

看到与 Hibernate 5.0.4 完全相同的行为。@ManyToOne(具有倒数OneToMany)并且Lazy如果连接列是主键,则获取工作完美。如果不是,延迟加载就会中断,ManyToOne每次实例化对象时,Hibernate 都会急切地获取 all 。如果你Criteria.list()为 1000 条记录做一个,这可能会非常缓慢。开始时对 1000 条记录的单个查询可以膨胀为 5000 条查询,以便@ManyToOne使用单独的选择急切地获取各种's。

绝对没有任何我能够测试/更改的内容以任何方式解释了这一点,我可以可靠地重现它。

我必须在我的应用程序中实现的使用非 PK 进行连接的解决方案是垃圾@ManyToOne/@OneToMany注释对并手动写入集合提取(使用瞬态变量缓存结果)。这是更多的工作,但性能要高得多,因为我的一些对象有 5 或 6 个@ManyToOne对象,并且所有这些都被 Hibernate 用单独的选择急切地获取。

不幸的是,我无法重新组织架构以适应 Hibernate 中的这种怪癖。我正在做一个涉及 Heroku Connect 的项目,当合并来自 Salesforce.com 的数据时,表之间的连接是使用表中非主键的“sfid”列完成的。主键是 Heroku Postgres 数据库中记录唯一的单独值,不能用于连接,因为数据库中没有其他表引用此主键。

我假设这是 Hibernate 中的一个错误;我读过或无法修改的任何内容都不会以任何方式影响这种行为,正如我所提到的,如果连接列是主键,我可以使系统完全按预期工作。


pea*_*ach 5

如果有人仍然遇到问题,我们可以按以下方式工作:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dummya_name", referencedColumnName = "name", insertable = false, updatable = false),
@LazyToOne(LazyToOneOption.NO_PROXY)
public DummyA getDummyA() {
    if (fieldHandler != null) {
        return (DummyA) fieldHandler.readObject(this, "dummyA", dummyA);
    }
    return dummyA;
}

public void setDummyA(DummyA dummyA) {
    if (fieldHandler != null) {
        this.dummyA = (DummyA ) fieldHandler.writeObject(this, "dummyA", this.dummyA, dummyA);
        return;
    }
    this.dummyA= dummyA;
}

@Override
public void setFieldHandler(FieldHandler fieldHandler) {
    this.fieldHandler = fieldHandler;
}

@Override
public FieldHandler getFieldHandler() {
    return fieldHandler;
}
Run Code Online (Sandbox Code Playgroud)

Hibernate 延迟加载反向一对一解决方法对此进行了很好的解释- 这是如何工作的?

正如 @alina-petukhova 提到的,DummyB 类必须实现 FieldHandled 接口。

在 Spring Boot 2 中,FieldHandled 接口被 PersistentAttributeInterceptable 取代,FieldHandler 被 PersistentAttributeInterceptor 取代

请参阅https://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/engine/spi/PersistentAttributeInterceptable.html


mh-*_*dev -2

您不能使用该名称作为连接列,因为没有唯一约束。因此,它可能会导致 ManyToMany 映射而不是 ManyToOne。我不知道 hibernate 是否接受这一点,但从长远来看,它最终会出现意想不到的结果。此外,我没有看到它的用例。我建议您始终使用长 id 作为主键并通过此字段自动映射。仅当您有非正统的用例或必须与遗留数据库兼容时,才需要进行这种特殊处理。