【问题标题】:Lasy Fetch with Spring Data JPA使用 Spring Data JPA 进行 Lasy Fetch
【发布时间】:2020-09-13 05:00:21
【问题描述】:

我创建了一对一的关系,我希望有一些方法可以获取相同的实体(Distributor.class),但一个是惰性获取,另一个是急切的。

@Entity
@Table
public class Distributor {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String site;

    @OneToOne(
            mappedBy = "distributor",
            cascade = CascadeType.ALL,
            orphanRemoval = true,
            fetch = FetchType.LAZY,
            optional = false
    )
    private Location location;

    public void setLocation(Location repositoryLocation) {
        if (repositoryLocation == null) {
            if (this.location != null) {
                this.location.setDataProvider(null);
            }
        }
        else {
            repositoryLocation.setDataProvider(this);
        }
        this.location = repositoryLocation;

    }

// getters/setters/constructor

    }

}
@Entity
@Table(name = "location")
public class Location {

    @Id
    private Long id;

    @Column(name = "country_code")
    private String countryCode;

    private Double longitude;

    private Double latitude;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    @JoinColumn(name = "id")
    private Distributor distributor;

    public Distributor getDistributor() {
        return distributor;
    }

    public void setDistributor(Distributor distributor) {
        this.distributor = distributor;
    }
// other getters/setters
}

我遇到的最大问题是 Spring Data JPA 似乎忽略了 FetchType 并急切地获取所有相关表(基于 related threads 和 Spring Data JPA 文档)。所有使用 Distributor.class 获取数据的 Spring Data Repository 方法都在两个选择中急切地从 Distributor 和 Location 中获取 Location。 通过这样使用@NamedEntityGraph

@Entity
@Table
@NamedEntityGraph(
        name = "distributor-entity-graph",
        attributeNodes = {
                @NamedAttributeNode("location"),
        }
)
public class Distributor {
//spring data jpa methods
  @EntityGraph(value = "distributor-entity-graph", type = EntityGraph.EntityGraphType.LOAD)
    Optional<Distributor> findById(Long id);

如果使用这样的图表,我会在 Location 上获得单个左外连接,尽管它是一种更好的急切负载类型,但这仍然是急切负载。

到目前为止,我 have found 所做的一切似乎都是糟糕的解决方法。 是否有一些或多或少的巧妙方法可以做到这一点,或者是否更好(首先在性能方面)只是不创建关系并按需获取Location?急切的负载是一种气味,但是当我获取单个分发服务器时,在大多数情况下我都想这样做,但在某些情况下我不这样做,尤其是如果我这样做了 findAll()。

【问题讨论】:

  • 你用的是什么休眠版本?
  • 在这个测试环境中,我使用最新的 Spring 版本 2.3.0.RELEASE,它使用 hibernate v.5.4.15.Final(一切都在测试环境中测试),在 prod hibernate v.5.2.17决赛(早春)。

标签: spring hibernate jpa spring-data-jpa


【解决方案1】:

Hibernate 忽略每个设置了mappedBy(或使用JoinColumn 并且是可选的)的双向一对一映射的父端的LAZY,因为它需要知道何时初始化具有 null 或代理的字段。

您的问题是,即使您在子级 (Location) 上使用 MapsId,父级仍然使用 mappedBy

以下是实现延迟加载双向关联的方法(虽然它有缺点,请阅读到底)。

家长

@Entity
public class TestParent {

  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;

  @OneToOne(fetch = FetchType.LAZY, optional = false)
  @JoinColumn(name = "id")
  private TestChild child;
}
  • child 字段不是强制性的,它有点笨拙,您可以忽略它并在需要时使用存储库获取子项 - 您知道它是 id
  • 因为 这不是标准的父子映射,您不能在 child 字段上指定任何级联(这将反映您持久化它们的方式,因此请阅读到最后) ,否则持久化将失败,因为它无法将 id 分配给子实体。
  • optional = false 是必填项,否则无论如何都会急切地获取它。

儿童

public class TestChild {

  @Id
  private Long id;

  @OneToOne(fetch = FetchType.LAZY)
  @MapsId
  @JoinColumn(name = "id")
  private TestParent parent;
}

这是你对孩子的定义,没有什么重要的改变。

坚持

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

TestParent parent = new TestParent();
TestChild child = new TestChild();
parent.setChild(child);
child.setParent(parent);

em.persist(parent);
em.persist(child);

em.getTransaction().commit();
em.close();

由于父实体不再级联到子实体,因此您必须单独持久化子实体。

Read more.

【讨论】:

  • 在 Vlad Mihalcea 的文章中描述的方法不适用于 hibernate 5.3。我还没有为 v.5.2 分支测试过它。根据documentationif you really need to use a bidirectional association and want to make sure that this is always going to be fetched lazily, then you need to enable lazy state initialization bytecode enhancement and use the @LazyToOne annotation as well
  • 很高兴知道,这是一个错误,但在 5.4 中已修复
  • 问题是我不能以这种方式将 null 分配给位置,也不能分配具有 null 字段的位置对象(导致org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKL1QA7PX59VE6IOSS7PNVYIPHD: PUBLIC.REPOSITORY FOREIGN KEY(ID) REFERENCES PUBLIC.LOCATION(ID) (4)"; SQL statement: ),因为我坚持 Distributor(父类) 通过 Spring Data。
  • 哦,真的。在这种情况下,您可以进行延迟加载的一对一关联的唯一方法是使用字节码增强和@LazyToOne。请记住,如果您的项目已经很大(这可能很耗时),那么转向字节码增强可能会很痛苦。
  • 这个答案确实很有用,但它没有回答我原来的问题。此答案仅在与纯 Hibernate 一起使用时才有效。
猜你喜欢
  • 1970-01-01
  • 2017-08-21
  • 1970-01-01
  • 2021-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-11
  • 1970-01-01
相关资源
最近更新 更多