Làm cách nào tôi có thể khiến mối quan hệ JPA OneToOne trở nên lười biếng


212

Trong ứng dụng này, chúng tôi đang phát triển, chúng tôi nhận thấy rằng một chế độ xem đặc biệt chậm. Tôi đã lược tả khung nhìn và nhận thấy rằng có một truy vấn được thực hiện bởi hibernate mất 10 giây ngay cả khi chỉ có hai đối tượng trong cơ sở dữ liệu để tìm nạp. Tất cả OneToManyManyToManyquan hệ là lười biếng vì vậy đó không phải là vấn đề. Khi kiểm tra SQL thực tế đang được thực thi, tôi nhận thấy rằng có hơn 80 phép nối trong truy vấn.

Kiểm tra thêm vấn đề, tôi nhận thấy rằng vấn đề được gây ra bởi sự phân cấp sâu sắc OneToOneManyToOnequan hệ giữa các lớp thực thể. Vì vậy, tôi nghĩ rằng, tôi sẽ chỉ khiến họ trở nên lười biếng, điều đó sẽ giải quyết vấn đề. Nhưng chú thích @OneToOne(fetch=FetchType.LAZY)hoặc @ManyToOne(fetch=FetchType.LAZY)có vẻ không hoạt động. Hoặc tôi nhận được một ngoại lệ hoặc sau đó chúng không thực sự được thay thế bằng một đối tượng proxy và do đó lười biếng.

Bất cứ ý tưởng làm thế nào tôi sẽ làm điều này để làm việc? Lưu ý rằng tôi không sử dụng persistence.xmlđể xác định quan hệ hoặc chi tiết cấu hình, mọi thứ đều được thực hiện trong mã java.

Câu trả lời:


218

Trước hết, một số giải thích cho câu trả lời của KLE :

  1. Liên kết một-một không bị ràng buộc (không thể bị ràng buộc) là liên kết duy nhất không thể được ủy quyền nếu không có công cụ mã byte. Lý do cho điều này là vì thực thể chủ sở hữu PHẢI biết liệu thuộc tính kết hợp nên chứa đối tượng proxy hay NULL và nó không thể xác định rằng bằng cách xem các cột của bảng cơ sở do một-một thường được ánh xạ thông qua PK được chia sẻ, vì vậy nó dù sao cũng phải háo hức tìm kiếm proxy. Đây là một lời giải thích chi tiết hơn .

  2. các hiệp hội nhiều-một (và một-nhiều, rõ ràng) không gặp phải vấn đề này. Chủ thể chủ sở hữu có thể dễ dàng kiểm tra FK của chính mình (và trong trường hợp một-nhiều, proxy bộ sưu tập trống được tạo ban đầu và được điền theo yêu cầu), vì vậy hiệp hội có thể lười biếng.

  3. Thay thế một-một bằng một-nhiều là khá nhiều không bao giờ là một ý tưởng tốt. Bạn có thể thay thế nó bằng nhiều tính năng độc nhất nhưng có các tùy chọn khác (có thể tốt hơn).

Rob H. có một điểm hợp lệ, tuy nhiên bạn có thể không thực hiện được tùy thuộc vào mô hình của mình (ví dụ: nếu liên kết một-một của bạn null).

Bây giờ, theo như câu hỏi ban đầu:

A) @ManyToOne(fetch=FetchType.LAZY)nên hoạt động tốt. Bạn có chắc chắn rằng nó không bị ghi đè trong chính truy vấn không? Có thể chỉ định join fetchtrong HQL và / hoặc đặt rõ ràng chế độ tìm nạp thông qua API Tiêu chí sẽ được ưu tiên hơn chú thích lớp. Nếu đó không phải là trường hợp và bạn vẫn gặp sự cố, vui lòng đăng các lớp, truy vấn và kết quả SQL của bạn để trò chuyện nhiều hơn.

B) @OneToOnelà khó khăn hơn. Nếu nó chắc chắn không thể rỗng, hãy đi với đề xuất của Rob H. và chỉ định nó như sau:

@OneToOne(optional = false, fetch = FetchType.LAZY)

Mặt khác, nếu bạn có thể thay đổi cơ sở dữ liệu của mình (thêm cột khóa ngoại vào bảng chủ sở hữu), hãy làm như vậy và ánh xạ nó thành "đã tham gia":

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name="other_entity_fk")
public OtherEntity getOther()

và trong OtherEntity:

@OneToOne(mappedBy = "other")
public OwnerEntity getOwner()

Nếu bạn không thể làm điều đó (và không thể sống với tìm nạp háo hức) thì thiết bị mã byte là lựa chọn duy nhất của bạn. Tôi phải đồng ý với CPerkins , tuy nhiên - nếu bạn có 80 !!! tham gia do các hiệp hội OneToOne háo hức, bạn đã gặp vấn đề lớn hơn sau đó :-)


Có thể có một tùy chọn khác, nhưng cá nhân tôi chưa thử nghiệm nó: về mặt không bị ràng buộc, hãy sử dụng one-to-onemột công thức như thế nào select other_entity.id from other_entity where id = other_entity.id. Tất nhiên, điều này không lý tưởng cho các màn trình diễn truy vấn.
Frédéric

1
tùy chọn = sai, không làm việc cho tôi. @OneToOne (fetch = FetchType.LAZY, mappedBy = "fundSeries", tùy chọn = false) private FundSeriesDetailEntity fundSeriesDetail;
Oleg Kuts

21

Để tải lười biếng làm việc trên các ánh xạ một-một không thể thực hiện được, bạn cần cho phép ngủ đông thực hiện công cụ biên dịch thời gian và thêm một @LazyToOne(value = LazyToOneOption.NO_PROXY)mối quan hệ một-một.

Ánh xạ ví dụ:

@OneToOne(fetch = FetchType.LAZY)  
@JoinColumn(name="other_entity_fk")
@LazyToOne(value = LazyToOneOption.NO_PROXY)
public OtherEntity getOther()

Ví dụ Phần mở rộng tệp Ant Build (để thực hiện công cụ thời gian biên dịch Hibernate):

<property name="src" value="/your/src/directory"/><!-- path of the source files --> 
<property name="libs" value="/your/libs/directory"/><!-- path of your libraries --> 
<property name="destination" value="/your/build/directory"/><!-- path of your build directory --> 

<fileset id="applibs" dir="${libs}"> 
  <include name="hibernate3.jar" /> 
  <!-- include any other libraries you'll need here --> 
</fileset> 

<target name="compile"> 
  <javac srcdir="${src}" destdir="${destination}" debug="yes"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </javac> 
</target> 

<target name="instrument" depends="compile"> 
  <taskdef name="instrument" classname="org.hibernate.tool.instrument.javassist.InstrumentTask"> 
    <classpath> 
      <fileset refid="applibs"/> 
    </classpath> 
  </taskdef> 

  <instrument verbose="true"> 
    <fileset dir="${destination}"> 
      <!-- substitute the package where you keep your domain objs --> 
      <include name="/com/mycompany/domainobjects/*.class"/> 
    </fileset> 
  </instrument> 
</target>

3
Tại sao LazyToOneOption.NO_PROXYvà không LazyToOneOption.PROXY?
Telmo Marques

Điều này không trả lời "tại sao", nhưng thực tế này cũng được khẳng định ở đây (đến cuối phần " Ánh
DanielM

12

Ý tưởng cơ bản của XToOnes trong Hibernate là hầu hết họ không lười biếng.

Một lý do là, khi Hibernate phải quyết định đặt proxy (với id) hoặc null,
dù sao nó cũng phải xem xét bảng khác để tham gia. Chi phí truy cập vào bảng khác trong cơ sở dữ liệu là đáng kể, do đó, nó cũng có thể tìm nạp dữ liệu cho bảng đó vào lúc đó (hành vi không lười biếng), thay vì tìm nạp trong yêu cầu sau đó sẽ yêu cầu quyền truy cập thứ hai vào cùng bàn.

Đã chỉnh sửa: để biết chi tiết, vui lòng tham khảo câu trả lời của ChssPly76 . Điều này là ít chính xác và chi tiết, nó không có gì để cung cấp. Cảm ơn ChssPly76.


Có một số điều sai ở đây - Tôi đã cung cấp một câu trả lời khác bên dưới với lời giải thích (quá nhiều nội dung, sẽ không phù hợp với nhận xét)
ChssPly76

8

Đây là một cái gì đó đã làm việc cho tôi (không có thiết bị):

Thay vì sử dụng @OneToOneở cả hai phía, tôi sử dụng @OneToManytrong phần nghịch đảo của mối quan hệ (phần có mappedBy). Điều đó làm cho thuộc tính trở thành một bộ sưu tập ( Listtrong ví dụ dưới đây), nhưng tôi dịch nó thành một mục trong getter, làm cho nó trong suốt cho khách hàng.

Thiết lập này hoạt động một cách lười biếng, nghĩa là, các lựa chọn chỉ được thực hiện khi getPrevious()hoặc getNext()được gọi - và chỉ một lựa chọn cho mỗi cuộc gọi.

Cấu trúc bảng:

CREATE TABLE `TB_ISSUE` (
    `ID`            INT(9) NOT NULL AUTO_INCREMENT,
    `NAME`          VARCHAR(255) NULL,
    `PREVIOUS`      DECIMAL(9,2) NULL
    CONSTRAINT `PK_ISSUE` PRIMARY KEY (`ID`)
);
ALTER TABLE `TB_ISSUE` ADD CONSTRAINT `FK_ISSUE_ISSUE_PREVIOUS`
                 FOREIGN KEY (`PREVIOUS`) REFERENCES `TB_ISSUE` (`ID`);

Lớp:

@Entity
@Table(name = "TB_ISSUE") 
public class Issue {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    protected Integer id;

    @Column
    private String name;

    @OneToOne(fetch=FetchType.LAZY)  // one to one, as expected
    @JoinColumn(name="previous")
    private Issue previous;

    // use @OneToMany instead of @OneToOne to "fake" the lazy loading
    @OneToMany(mappedBy="previous", fetch=FetchType.LAZY)
    // notice the type isnt Issue, but a collection (that will have 0 or 1 items)
    private List<Issue> next;

    public Integer getId() { return id; }
    public String getName() { return name; }

    public Issue getPrevious() { return previous; }
    // in the getter, transform the collection into an Issue for the clients
    public Issue getNext() { return next.isEmpty() ? null : next.get(0); }

}

7

Như tôi đã giải thích trong bài viết này , trừ khi bạn đang sử dụng Tăng cường Bytecode , bạn không thể tìm nạp một cách lười biếng @OneToOneliên kết phía phụ huynh .

Tuy nhiên, thông thường, bạn thậm chí không cần liên kết phía phụ huynh nếu bạn sử dụng @MapsIdở phía khách hàng:

@Entity(name = "PostDetails")
@Table(name = "post_details")
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    private Date createdOn;

    @Column(name = "created_by")
    private String createdBy;

    @OneToOne(fetch = FetchType.LAZY)
    @MapsId
    private Post post;

    public PostDetails() {}

    public PostDetails(String createdBy) {
        createdOn = new Date();
        this.createdBy = createdBy;
    }

    //Getters and setters omitted for brevity
}

Với @MapsId, thuộc idtính trong bảng con đóng vai trò là Khóa chính và Khóa ngoài đối với Khóa chính của bảng chính.

Vì vậy, nếu bạn có một tham chiếu đến Postthực thể mẹ , bạn có thể dễ dàng tìm nạp thực thể con bằng cách sử dụng định danh thực thể cha mẹ:

PostDetails details = entityManager.find(
    PostDetails.class,
    post.getId()
);

Bằng cách này, bạn sẽ không gặp sự cố truy vấn N + 1 có thể do mappedBy @OneToOneliên kết ở phía phụ huynh gây ra .


bằng cách này, chúng ta không thể thực hiện các thao tác xếp tầng từ cha mẹ sang con cái: /
Hamdi

Để duy trì, đó chỉ là một cuộc gọi kéo dài thêm, để xóa, bạn có thể sử dụng tầng DDL.
Vlad Mihalcea

6

Trong ánh xạ Hibernate XML nguyên gốc, bạn có thể thực hiện điều này bằng cách khai báo ánh xạ một-một với thuộc tính bị ràng buộc được đặt thành true. Tôi không chắc chắn tương đương với chú thích Hibernate / JPA là gì và việc tìm kiếm nhanh tài liệu không cung cấp câu trả lời, nhưng hy vọng điều đó sẽ giúp bạn tiếp tục.


5
+1 cho một đề xuất tốt; không may mắn, nó không phải lúc nào cũng có thể áp dụng vì mô hình miền thực sự có thể yêu cầu vô hiệu. Cách thích hợp để ánh xạ điều này qua các chú thích là@OneToOne(optional=false,fetch=FetchMode.LAZY)
ChssPly76

Tôi đã thử điều này và thấy không có cải thiện hiệu suất. Tôi vẫn thấy nhiều truy vấn trong đầu ra ngủ đông thông qua trình gỡ lỗi.
P.Brian.Mackey

3

Như đã giải thích một cách hoàn hảo bởi ChssPly76, proxy của Hibernate không giúp đỡ với không bị giới hạn (nullable) Các tổ thức one-to-one, NHƯNG có một trick giải thích ở đây để tránh để thiết lập thiết bị đo đạc. Ý tưởng là đánh lừa Hibernate rằng lớp thực thể mà chúng ta muốn sử dụng đã được sử dụng: bạn sử dụng nó theo cách thủ công trong mã nguồn. Dễ thôi! Tôi đã triển khai nó với CGLib với tư cách là nhà cung cấp mã byte và nó hoạt động (đảm bảo rằng bạn định cấu hình lazy = "no-proxy" và fetch = "select", không phải "tham gia", trong HBM của bạn).

Tôi nghĩ rằng đây là một sự thay thế tốt cho thiết bị thực (ý tôi là tự động) khi bạn chỉ có một mối quan hệ vô dụng một đối một mà bạn muốn làm cho lười biếng. Hạn chế chính là giải pháp phụ thuộc vào nhà cung cấp mã byte mà bạn đang sử dụng, vì vậy hãy nhận xét chính xác lớp của bạn vì bạn có thể phải thay đổi nhà cung cấp mã byte trong tương lai; Tất nhiên, bạn cũng đang sửa đổi mô hình bean của mình vì lý do kỹ thuật và điều này không ổn.


1

Câu hỏi này khá cũ, nhưng với Hibernate 5.1.10, có một số giải pháp mới thoải mái hơn.

Tải lười biếng hoạt động ngoại trừ phía phụ huynh của hiệp hội @OneToOne. Điều này là do Hibernate không có cách nào khác để biết nên gán null hoặc Proxy cho biến này. Thêm chi tiết bạn có thể tìm thấy trong bài viết này

  • Bạn có thể kích hoạt tải tăng cường mã byte lười biếng
  • Hoặc, bạn chỉ có thể xóa phía phụ huynh và sử dụng phía máy khách với @MapsId như được giải thích trong bài viết trên. Bằng cách này, bạn sẽ thấy rằng bạn không thực sự cần phía phụ huynh vì trẻ chia sẻ cùng id với cha mẹ để bạn có thể dễ dàng tìm nạp trẻ bằng cách biết id cha.

0

Nếu mối quan hệ không phải là hai chiều thì @EuityCollection có thể dễ dàng hơn so với sử dụng bộ sưu tập One2Many lười biếng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.