简体   繁体   English

@ManyToOne(fetch = FetchType.LAZY) 对非主键引用列不起作用

[英]@ManyToOne(fetch = FetchType.LAZY) doesn't work on non-primary key referenced column

I'm having some troubles to make a @ManyToOne association to be loaded lazilly.我在延迟加载 @ManyToOne 关联时遇到了一些麻烦。 I'm using the fetch=LAZY but it doesn't work when join isn't made by the primary key column.我正在使用 fetch=LAZY 但是当连接不是由主键列进行时它不起作用。

I know this question was already asked but I think it wasn't properly answered, so I provide detailed information to clarify the issue.我知道这个问题已经有人问过了,但我认为没有正确回答,所以我提供了详细信息来澄清这个问题。

This is my model:这是我的模型:

DummyB -> DummyA

These are the tables:这些是表:

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

And these are the entities:这些是实体:

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

}

Note getDummyA method in entity DummyB is duplicate to try out two cases to join the entities.注意实体 DummyB 中的 getDummyA 方法是重复的,以尝试两种情况来连接实体。

Case 1: mapping DummyB -> DummyA by DummyA primary key案例 1:通过 DummyA 主键映射 DummyB -> DummyA

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

This works fine, just one query is executed to retrieve DummyB objects.这工作正常,只执行一个查询来检索 DummyB 对象。

Case 2: mapping DummyB -> DummyA by DummyA NON primary key (field name)案例 2:通过 DummyA NON 主键(字段名称)映射 DummyB -> DummyA

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

Same dummyB select is execute, but right after, a dummyA select is executed filtering by name=?同样的 dummyB 选择被执行,但紧接着,一个 dummyA 选择被执行过滤 name=? to fetch the related A object.获取相关的 A 对象。

I'm using a really simple jUnit to execute filtering:我正在使用一个非常简单的 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();
    }

}

My libraries:我的图书馆:

  • org.hibernate:hibernate-core:jar:4.2.17.Final:compile org.hibernate:hibernate-core:jar:4.2.17.Final:compile
  • org.hibernate.common:hibernate-commons-annotations:jar:4.0.2.Final:compile org.hibernate.common:hibernate-commons-annotations:jar:4.0.2.Final:compile
  • org.hibernate.javax.persistence:hibernate-jpa-2.0-api:jar:1.0.1.Final:compile org.hibernate.javax.persistence:hibernate-jpa-2.0-api:jar:1.0.1.Final:compile
  • org.hibernate:hibernate-validator:jar:4.3.2.Final:provided org.hibernate:hibernate-validator:jar:4.3.2.Final:provided

Other things I've already tried:我已经尝试过的其他事情:

  • Adding hiberante's @LazyToOne to getDummyA() method doesn't have any effect.将 hiberante 的 @LazyToOne 添加到 getDummyA() 方法没有任何效果。

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

  • Creating a foreign key from DummyB table to dummyA (and an unique constraint in dummya.name field) has no effect.创建从 DummyB 表到 dummyA(以及 dummya.name 字段中的唯一约束)的外键无效。

  • Adding @Column(unique = true) on DummyA getName() method didn't make it.在 DummyA getName() 方法上添加 @Column(unique = true) 没有成功。
  • Set optional=true or false as suggested here has no effect either.按照此处的建议设置 optional=true 或 false 也无效。
  • Trying to force the lazy loading using the setFetchMode in the criteria didn't work, DummyA select keeps executing.尝试使用条件中的 setFetchMode 强制延迟加载不起作用,DummyA select 继续执行。

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

I can't find in Hibernate's docs a point where it refers to this behaviour, so I wonder if there's anything is wrong in my annotations or I came upon a Hibernate bug.我在 Hibernate 的文档中找不到它指代这种行为的地方,所以我想知道我的注释是否有问题,或者我遇到了 Hibernate 错误。

Can anyone tell?谁能告诉?

UPDATED by md-dev request: To set it more clear:由 md-dev 请求更新:将其设置得更清楚:

Is this the expected behaviour or is a bug?这是预期的行为还是错误? if this the expected behaviour, where is it documented?如果这是预期的行为,它在哪里记录?

Thank you.谢谢你。

Seeing the exact same behaviour with Hibernate 5.0.4.看到与 Hibernate 5.0.4 完全相同的行为。 @ManyToOne (with a reciprocal OneToMany ) and Lazy fetching works perfectly if the join column is the primary key.如果连接列是主键, @ManyToOne (具有互惠的OneToMany )和Lazy获取工作完美。 If it's not, lazy loading breaks and Hibernate eagerly fetches all ManyToOne 's every time an object is instantiated.如果不是,延迟加载就会中断,每次实例化对象时,Hibernate 都会急切地获取所有ManyToOne This can be catastrophically slow if you do a Criteria.list() for, say, 1000 records.如果您为 1000 条记录执行Criteria.list() ,这可能会非常缓慢。 What started out as a single query for 1000 records can balloon into 5000 queries to eagerly fetch a variety of @ManyToOne 's using individual selects.最初是对 1000 条记录的单个查询可以膨胀为 5000 条查询,以便使用单独的选择急切地获取各种@ManyToOne

Absolutely nothing I've been able to test/change has explained this in any way and I can reproduce it reliably.绝对没有任何我能够测试/更改的内容以任何方式解释了这一点,我可以可靠地重现它。

The solution I had to implement in my app that uses non-PK's for joins was to just trash @ManyToOne / @OneToMany annotation pairs and write collection fetches manually (caching the results with a transient variable).我必须在我的应用程序中实现的使用非 PK 进行连接的解决方案是@ManyToOne / @OneToMany注释对并手动写入集合提取(使用瞬态变量缓存结果)。 It's way more work but the performance is substantially higher given that some of my objects have 5 or 6 @ManyToOne objects and all of these were being eagerly fetched with individual selects by Hibernate.这是更多的工作,但性能要高得多,因为我的一些对象有 5 或 6 个@ManyToOne对象,并且所有这些都被 Hibernate 用单独的选择急切地获取。

Unfortunately, I can't reorganize my schema to accommodate this quirk in Hibernate.不幸的是,我无法重新组织架构以适应 Hibernate 中的这种怪癖。 I'm doing a project involving Heroku Connect and the joins between tables when merging data from Salesforce.com are done using a "sfid" column in the table that is not the primary key.我正在做一个涉及 Heroku Connect 的项目,当合并来自 Salesforce.com 的数据时,表之间的连接是使用表中非主键的“sfid”列完成的。 The primary key is a separate value unique to the record in the Heroku Postgres database and can't be used to do joins on as no other tables in the database refer to this primary key.主键是 Heroku Postgres 数据库中记录唯一的单独值,不能用于连接,因为数据库中没有其他表引用此主键。

I'm assuming that this is a bug in Hibernate;我假设这是 Hibernate 中的一个错误; nothing I've read or been able to modify has affected this behaviour in any way and, as I mentioned, I can make the system work exactly as expected if the join columns are the primary keys.我读过或无法修改的任何内容都不会以任何方式影响这种行为,正如我所提到的,如果连接列是主键,我可以使系统完全按预期工作。

In case someone still has problems with this We got it to work the following way:万一有人仍然有这个问题,我们让它按以下方式工作:

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

It was explained well in Hibernate lazy loading for reverse one to one workaround - how does this work?Hibernate 延迟加载中对反向一对一解决方法进行了很好的解释 - 这是如何工作的?

As @alina-petukhova mentioned the DummyB class must implement the FieldHandled interface.正如@alina-petukhova 提到的,DummyB 类必须实现 FieldHandled 接口。

In Spring Boot 2 the FieldHandled interface was replaced by PersistentAttributeInterceptable and the FieldHandler was replaced by PersistentAttributeInterceptor在 Spring Boot 2 中,FieldHandled 接口被 PersistentAttributeInterceptable 取代,FieldHandler 被 PersistentAttributeInterceptor 取代

see https://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/engine/spi/PersistentAttributeInterceptable.htmlhttps://docs.jboss.org/hibernate/orm/5.3/javadocs/org/hibernate/engine/spi/PersistentAttributeInterceptable.html

@peach solution worked for me. @peach解决方案对我有用 Just few things wasn't mentioned:只是有几件事没有提到:

@Entity
public class DummyB implements Serializable {

should be应该

@Entity
public class DummyB implements Serializable, FieldHandled {

and if you are using @JsonIgnoreProperties you should add fieldHandler如果您使用的是 @JsonIgnoreProperties,则应添加 fieldHandler

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

You can't use the name as a join column, because there isn't an unique constraint for that.您不能将该名称用作连接列,因为它没有唯一约束。 So it is possible that it results in a ManyToMany mapping instead of ManyToOne.因此,它可能导致 ManyToMany 映射而不是 ManyToOne。 I have no idea if hibernate accepts this, but it would end up in something unexpected in the long run.我不知道 hibernate 是否接受这一点,但从长远来看,它会以意想不到的方式结束。 Additionally I don't see the use case for that.此外,我没有看到它的用例。 I advice you to use always a Long id as primary key and map automatically via this field.我建议您始终使用 Long id 作为主键并通过此字段自动映射。 Such special handling is only necessary if you have an unorthodox usecase or must be compatible with a legacy db.仅当您有一个非正统的用例或必须与旧数据库兼容时,才需要这种特殊处理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM