2015-05-26 13 views
13

Làm việc với REST dữ liệu Spring. NẾU bạn có mối quan hệ oneToMany hoặc ManyToOne, hoạt động PUT trả về 200 trên thực thể "không sở hữu" nhưng không thực sự tồn tại tài nguyên đã tham gia.Làm thế nào để duy trì các mối quan hệ hai chiều với Spring Data REST và JPA?

Thực thể mẫu.

@Entity(name = 'author') 
@ToString 
class AuthorEntity implements Author { 

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

    String fullName 

    @ManyToMany(mappedBy = 'authors') 
    Set<BookEntity> books 
} 


@Entity(name = 'book') 
@EqualsAndHashCode 
class BookEntity implements Book { 

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

    @Column(nullable = false) 
    String title 

    @Column(nullable = false) 
    String isbn 

    @Column(nullable = false) 
    String publisher 

    @ManyToMany(fetch = FetchType.LAZY, cascade = [CascadeType.ALL]) 
    Set<AuthorEntity> authors 
} 

Nếu bạn sao lưu chúng với PagingAndSortingRepository, bạn có thể được một cuốn sách, hãy làm theo các tác giả liên kết trên cuốn sách và làm một PUT với URI của một tác giả liên kết với. Bạn không thể đi theo cách khác.

Nếu bạn thực hiện GET trên Author và thực hiện PUT trên liên kết sách, phản hồi sẽ trả về 200, nhưng mối quan hệ không bao giờ được duy trì.

Đây có phải là hành vi mong đợi không?

+2

Các liên kết hai chiều phải được duy trì theo cách thủ công trong mô hình đối tượng thường được thực hiện bên trong bộ cài đặt. Spring Data REST sử dụng truy cập trường theo mặc định. Điều đó có nghĩa là sự thay đổi trong các hiệp hội không được phản ánh ở phía bên kia của hiệp hội theo định nghĩa. Bạn đã thử, kích hoạt đồng bộ hóa trong phương thức '@ PrePersist' /' @ PreUpdate' chưa? Hoặc chuyển sang quyền truy cập thuộc tính? –

+1

Tôi cũng sẽ bỏ phiếu để giải quyết hiệp hội hai chiều. Một 'Tác giả' có thể tồn tại mà không có 'Sách'. 'Sách' cho 'Tác giả' có thể được lấy ra bằng cách sử dụng một phương thức kho lưu trữ' Đặt BookRepository.findByAuthor (Tác giả tác giả) '. Điều này đơn giản hóa mô hình khá một chút vì bạn không phải tự duy trì các tham chiếu. –

+0

Tôi có thể thấy một giao diện thư viện nơi bạn sẽ cần phải tìm một tác giả, sau đó xem sách của nó. Điều này sẽ không được hỗ trợ nếu không có các hiệp hội hai hướng. Không phải là một fan hâm mộ của việc quản lý các hiệp hội thực thể nếu nó không phải là ý định của DATA REST. Cảm ơn bạn đã nhập và lọc sách của tác giả dường như là cách tiếp cận tốt nhất. Không giữ mọi thứ một chiều bất cứ khi nào có thể. :) – keaplogik

Trả lời

24

tl; dr

Chìa khóa để đó không phải là quá nhiều bất cứ điều gì trong mùa xuân dữ liệu REST - như bạn có thể dễ dàng có được nó để làm việc trong kịch bản của bạn - nhưng đảm bảo rằng mô hình của bạn giữ cả hai đầu của hiệp hội trong đồng bộ hóa.

Vấn đề

Vấn đề bạn thấy ở đây nảy sinh từ thực tế là mùa xuân dữ liệu REST của về cơ bản sẽ thay đổi các books tài sản của AuthorEntity của bạn. Chính bản thân nó không phản ánh bản cập nhật này trong thuộc tính authors của số BookEntity. Điều này phải được làm việc xung quanh một cách thủ công, đó không phải là một ràng buộc mà Spring Data REST tạo nên nhưng cách mà JPA hoạt động nói chung. Bạn sẽ có thể tái tạo hành vi sai lầm bằng cách đơn giản gọi thủ tục setters bằng tay và cố gắng duy trì kết quả.

Cách giải quyết vấn đề này?

Nếu loại bỏ liên kết hai chiều không phải là một lựa chọn (xem dưới đây lý do tại sao tôi khuyên bạn nên làm điều này) cách duy nhất để thực hiện công việc này là đảm bảo thay đổi liên kết được phản ánh trên cả hai mặt. Thông thường người chăm sóc này bằng cách thủ công thêm tác giả đến BookEntity khi một cuốn sách được bổ sung:

class AuthorEntity { 

    void add(BookEntity book) { 

    this.books.add(book); 

    if (!book.getAuthors().contains(this)) { 
     book.add(this); 
    } 
    } 
} 

Các bổ sung nếu khoản sẽ đã được thêm vào BookEntity bên cũng như nếu bạn muốn chắc chắn rằng thay đổi từ phía bên kia cũng được nhân giống. Các if về cơ bản là cần thiết nếu không hai phương pháp sẽ liên tục tự gọi mình.

REST dữ liệu mùa xuân, theo mặc định sử dụng truy cập trường để thực tế không có phương pháp nào bạn có thể đặt logic này vào. Một tùy chọn sẽ là chuyển sang quyền truy cập thuộc tính và đặt logic vào các bộ định tuyến. Một tùy chọn khác là sử dụng phương thức được chú thích bằng @PreUpdate/@PrePersist lặp lại qua các thực thể và đảm bảo rằng các sửa đổi được phản ánh trên cả hai mặt.

Xóa nguyên nhân gốc của sự cố

Như bạn có thể thấy, điều này thêm khá nhiều phức tạp cho mô hình miền.Như tôi đã nói đùa trên Twitter ngày hôm qua:

# 1 quy tắc của các hiệp hội hai chiều: không sử dụng chúng ... :)

Nó thường đơn giản hoá vấn đề này nếu bạn cố gắng không sử dụng hai chiều mối quan hệ bất cứ khi nào có thể và thay vào đó rơi trở lại một kho lưu trữ để có được tất cả các thực thể tạo nên mặt sau của hiệp hội.

Một chẩn đoán tốt để xác định bên nào cần cắt giảm là suy nghĩ về khía cạnh nào của liên kết thực sự cốt lõi và quan trọng đối với miền bạn đang lập mô hình. Trong trường hợp của bạn, tôi cho rằng nó là hoàn toàn tốt đẹp cho một tác giả để tồn tại mà không có cuốn sách được viết bởi cô ấy. Mặt khác, một cuốn sách không có tác giả không có ý nghĩa gì cả. Vì vậy, tôi muốn giữ authors tài sản trong BookEntity nhưng giới thiệu các phương pháp sau đây trên BookRepository:

interface BookRepository extends Repository<Book, Long> { 

    List<Book> findByAuthor(Author author); 
} 

Vâng, đó là yêu cầu tất cả các khách hàng mà trước đây chỉ có thể gọi author.getBooks() đến nay làm việc với một kho lưu trữ. Nhưng về mặt tích cực, bạn đã loại bỏ tất cả các cruft từ các đối tượng miền của bạn và tạo ra một hướng phụ thuộc rõ ràng từ cuốn sách cho tác giả trên đường đi. Sách phụ thuộc vào tác giả nhưng không phụ thuộc vào cách khác.

+4

"Mẫu" câu trả lời của bạn nên được chuẩn hóa bằng cách nào đó trên SO: P! Đồ tốt! – geoand

+0

Nếu tôi đọc bạn tốt, bạn đang gợi ý loại bỏ tất cả các mối quan hệ OneToMany và chỉ giữ lại mặt dây ManyToOne của họ? Đây sẽ là một thay đổi lớn thực sự trong cách tôi mô hình Thực thể ... –

+0

Không. Tôi không đề xuất bất kỳ điều gì trên cơ sở chú thích. Tôi khuyên bạn không nên sử dụng các hiệp hội hai hướng. Trong thực tế, tôi thậm chí không đề cập đến bất kỳ chú thích nào, vì vậy nó không hoàn toàn rõ ràng đối với tôi cách bạn nhận được giải thích đó. Bên nào của hiệp hội bạn cắt là rất phụ thuộc vào trường hợp sử dụng. Xin vui lòng đọc lại phần đề cập đến các chẩn đoán cho biện minh. –

1

Tôi gặp phải sự cố tương tự, trong khi gửi POJO (chứa ánh xạ hai hướng @OneToMany và @ManyToOne) dưới dạng JSON qua REST api, dữ liệu được lưu giữ trong cả cha lẫn mẹ nhưng quan hệ khóa ngoài không thành lập. Điều này xảy ra bởi vì các hiệp hội hai chiều cần được duy trì theo cách thủ công.

JPA cung cấp chú thích @PrePersist có thể được sử dụng để đảm bảo rằng phương pháp được chú thích với nó được thực thi trước khi thực thể được duy trì. Vì, JPA đầu tiên chèn thực thể cha mẹ vào cơ sở dữ liệu, theo sau là thực thể con, tôi đưa vào một phương thức có chú thích là @PrePersist sẽ lặp qua danh sách các thực thể con và đặt thủ công thực thể cha.

Trong trường hợp của bạn nó sẽ là một cái gì đó như thế này:

class AuthorEntitiy { 
    @PrePersist 
    public void populateBooks { 
     for(BookEntity book : books) 
      book.addToAuthorList(this); 
    } 
} 

class BookEntity { 
    @PrePersist 
    public void populateAuthors { 
     for(AuthorEntity author : authors) 
      author.addToBookList(this); 
    } 
} 

Sau này bạn có thể nhận được một lỗi đệ quy vô hạn, để tránh điều đó chú thích lớp cha mẹ của bạn với @JsonManagedReference và lớp con của bạn với @JsonBackReference. Giải pháp này làm việc cho tôi, hy vọng nó sẽ làm việc cho bạn quá.

Các vấn đề liên quan