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

时间:2015-05-06 16:20:45

标签: hibernate jpa

我有一些麻烦要让@ManyToOne关联加载lazilly。我使用了fetch = LAZY但是当主键列没有加入时它不起作用。

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

这是我的模特:

DummyB -> DummyA

这些是表格:

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

这些是实体:

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

}

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

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

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

这很好用,只执行一个查询来检索DummyB对象。

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

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

}

我的图书馆:

  • 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,因为建议here也没有效果。
  • 尝试使用条件中的s​​etFetchMode强制延迟加载,DummyA select继续执行。

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

我无法在Hibernate的文档中找到引用此行为的点,所以我想知道我的注释中是否有任何错误或者我遇到了Hibernate错误。< / p>

谁能说出来?

由md-dev请求更新: 更明确地说明:

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

谢谢。

4 个答案:

答案 0 :(得分:4)

使用Hibernate 5.0.4查看完全相同的行为。如果连接列是主键,则OneToMany(具有倒数Lazy)和ManyToOne提取功能完美无缺。如果不是,则每次实例化对象时,延迟加载中断和Hibernate都会急切地获取所有Criteria.list()。如果您为1000条记录执行@ManyToOne,这可能会造成灾难性的影响。最初作为1000个记录的单个查询的内容可以演示为5000个查询,以便使用个别选择来热切地获取各种@ManyToOne

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

我必须在我的应用中实现的使用非PK连接的解决方案就是删除@OneToMany / @ManyToOne注释对并手动编写集合提取(使用非缓存结果一个瞬态变量)。虽然我的一些对象有5个或6个{{1}}个对象,并且所有这些对象都是由Hibernate单独选择的,但是所有这些对象都被急切地获取了。

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

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

答案 1 :(得分:2)

如果有人仍然遇到此问题 我们按照以下方式开展工作:

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

Hibernate lazy loading for reverse one to one workaround - how does this work?

对此进行了详细解释

正如@ 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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

@peach解决方案对我有用。只提到了一些事情:

@Entity
public class DummyB implements Serializable {

应该是

@Entity
public class DummyB implements Serializable, FieldHandled {

如果您使用@JsonIgnoreProperties,则应添加fieldHandler

@JsonIgnoreProperties({"hibernateLazyInitializer", "handler", "fieldHandler"})