【问题标题】:@ManyToOne(fetch = FetchType.LAZY) doesn't work on non-primary key referenced column@ManyToOne(fetch = FetchType.LAZY) 不适用于非主键引用的列
【发布时间】:2015-07-16 22:00:41
【问题描述】:

我在创建 @ManyToOne 关联以延迟加载时遇到了一些麻烦。我正在使用 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:通过 DummyA 主键映射 DummyB -> DummyA

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

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

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

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

相同的 dummyB 选择被执行,但紧接着,一个 dummyA 选择被执行过滤 name=?获取相关的 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:hibernate-core:jar:4.2.17.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:hibernate-validator:jar:4.3.2.Final:provided

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

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

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

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

  • 在 DummyA getName() 方法上添加 @Column(unique = true) 没有成功。
  • 按照建议设置 optional=true 或 false here 也无效。
  • 尝试使用条件中的 setFetchMode 强制延迟加载无效,DummyA select 继续执行。

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

我在 Hibernate 的文档中找不到引用此行为的点,所以我想知道我的注释中是否有任何问题,或者我遇到了 Hibernate 错误。

谁能告诉我?

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

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

谢谢。

【问题讨论】:

    标签: hibernate jpa


    【解决方案1】:

    看到与 Hibernate 5.0.4 完全相同的行为。如果连接列是主键,@ManyToOne(带有倒数OneToMany)和Lazy 提取工作完美。如果不是这样,延迟加载会中断,并且 Hibernate 会在每次实例化对象时急切地获取所有 ManyToOne。如果您为 1000 条记录执行 Criteria.list(),这可能会非常缓慢。最初对 1000 条记录的单个查询可以膨胀为 5000 个查询,以便使用单独的选择急切地获取各种 @ManyToOne

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

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

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

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

    【讨论】:

      【解决方案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

      【讨论】:

        【解决方案3】:

        @peach 解决方案对我有用。只是没有提到几件事:

        @Entity
        public class DummyB implements Serializable {
        

        应该是

        @Entity
        public class DummyB implements Serializable, FieldHandled {
        

        如果你使用@JsonIgnoreProperties,你应该添加fieldHandler

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

        【讨论】:

          【解决方案4】:

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

          【讨论】:

          • 上面公开的案例是对我的问题的简化,在我的实际案例中,要加入的列是唯一的列。关于这是否有意义,解释起来很复杂,这不是我的问题的重点,我在问“这是预期的行为吗?如果是,它在哪里记录?”
          • 请更正您的问题,因为上面的问题是错误的。
          • 这正是我在上一段中所要求的,但正如你所要求的,我设置得更清楚了。
          • 我在问题中没有看到任何与唯一名称相关的信息。
          • 这可能是因为我在非 pk 字段上从 dummyB -> dummyA 创建了一个外键,我认为这不是问题,但我将其明确设置为你问过。
          猜你喜欢
          • 2015-03-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-22
          • 1970-01-01
          • 1970-01-01
          • 2011-01-07
          相关资源
          最近更新 更多