2015-05-26 13 views
13

Utilizzo di Spring Data REST. Se si dispone di una relazione oneToMany o ManyToOne, l'operazione PUT restituisce 200 sull'entità "non proprietario", ma in realtà non mantiene la risorsa unita.Come mantenere le relazioni bidirezionali con Spring Data REST e JPA?

Entità di esempio.

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

Se li indietro con un PagingAndSortingRepository, si può prendere un libro, seguono gli autori dei collegamenti sul libro e fare un PUT con l'URI di un autore da associare. Non puoi andare dall'altra parte.

Se si esegue un GET su un autore e si esegue un PUT sul collegamento dei libri, la risposta restituisce 200, ma la relazione non viene mai mantenuta.

È questo il comportamento previsto?

+2

Le associazioni bidirezionali devono essere mantenute manualmente nel modello di oggetto che viene solitamente eseguito all'interno dei setter. Spring Data REST utilizza l'accesso al campo per impostazione predefinita. Ciò significa che il cambiamento delle associazioni non si riflette sull'altro lato dell'associazione per definizione. Hai provato, attivando la sincronizzazione in un metodo '@ PrePersist' /' PreUpdate'? O passare all'accesso alla proprietà? –

+1

Vorrei anche votare per risolvere l'associazione bidirezionale. Un 'Autore' può esistere senza un' Libro'. 'Book's per un' Author' può essere recuperato utilizzando un metodo di repository 'Set BookRepository.findByAuthor (autore autore)'. Questo semplifica il modello un po 'perché non è necessario mantenere manualmente i riferimenti. –

+0

Potrei vedere un'interfaccia di libreria dove è necessario trovare un autore, quindi visualizzare i suoi libri. Questo non sarebbe supportato senza associazioni bidirezionali. Non è un fan della gestione delle associazioni di entità se non è l'intento di DATA REST. Grazie per l'input e il filtro per gli autori di libri sembra essere l'approccio migliore. Non mantenere le cose unidirezionali quando possibile. :) – keaplogik

risposta

24

tl; dr

La chiave che non è tanto nulla in primavera dati REST - come si può facilmente farlo funzionare nello scenario - ma fare in modo che il vostro modello mantiene entrambe le estremità della associazione in sync.

Il problema

Il problema che vedete qui deriva dal fatto che la primavera dati REST modifica sostanzialmente la proprietà books del vostro AuthorEntity. Questo non riflette questo aggiornamento nella proprietà authors di BookEntity. Questo deve essere risolto manualmente, il che non è un vincolo di Spring Data REST ma il modo in cui funziona JPA in generale. Sarai in grado di riprodurre il comportamento errato semplicemente richiamando setter manualmente e cercando di mantenere il risultato.

Come risolvere questo?

Se rimuovere l'associazione bidirezionale non è un'opzione (vedere di seguito il motivo per cui lo consiglierei) l'unico modo per farlo funzionare è assicurarsi che le modifiche all'associazione si riflettano su entrambi i lati. Di solito le persone prendono cura di questo aggiungendo manualmente l'autore al BookEntity quando si aggiunge un libro:

class AuthorEntity { 

    void add(BookEntity book) { 

    this.books.add(book); 

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

L'ulteriore se la clausola avrebbe fatto da aggiungere sul lato BookEntity pure se si vuole fare in modo che anche i cambiamenti dall'altra parte vengono propagati. Il numero if è fondamentalmente richiesto in quanto altrimenti i due metodi si chiamerebbero costantemente.

Spring Data REST, per impostazione predefinita utilizza l'accesso al campo in modo che in realtà non vi sia alcun metodo in cui inserire questa logica. Un'opzione sarebbe quella di passare all'accesso delle proprietà e inserire la logica nei setter. Un'altra opzione consiste nell'utilizzare un metodo annotato con @PreUpdate/@PrePersist che itera sulle entità e assicura che le modifiche siano riflesse su entrambi i lati.

eliminazione della causa principale del problema

Come si può vedere, questo aggiunge un bel po 'di complessità al modello di dominio.Come ho scherzato su Twitter ieri:

# 1 regola delle associazioni bi-direzionale: non usarli ... :)

solito semplifica la questione se si tenta di non usare bidirezionale relazione quando possibile e ricorrere piuttosto a un deposito per ottenere tutte le entità che costituiscono il retro dell'associazione.

Una buona euristica per determinare quale lato tagliare è pensare a quale parte dell'associazione è davvero fondamentale e cruciale per il dominio che si sta modellando. Nel tuo caso, direi che è assolutamente perfetto per un autore esistere senza libri scritti da lei. Il rovescio della medaglia, un libro senza un autore non ha molto senso. Così mi piacerebbe mantenere la proprietà authors in BookEntity ma introdurre il seguente metodo sul BookRepository:

interface BookRepository extends Repository<Book, Long> { 

    List<Book> findByAuthor(Author author); 
} 

Sì, che impone a tutti i clienti che in precedenza potevano solo hanno invocato author.getBooks() di lavorare ora con un repository. Ma sul lato positivo hai rimosso tutto il cruft dai tuoi oggetti di dominio e hai creato una chiara direzione di dipendenza dal libro all'autore lungo la strada. I libri dipendono dagli autori ma non viceversa.

+4

Il "modello" delle vostre risposte dovrebbe essere standardizzato in qualche modo su SO: P! Roba buona! – geoand

+0

Se ti leggo bene, stai suggerendo di rimuovere tutte le relazioni OneToMany e di mantenere solo i loro ciondoli ManyToOne? Questo sarebbe un vero grande cambiamento nel modo in cui modifico Entità ... –

+0

No. Non raccomando nulla su base annotazione. Raccomando di non usare associazioni bidirezionali. In effetti, non ho nemmeno menzionato alcuna annotazione, quindi non è del tutto ovvio per me come si ricava questa interpretazione. Quale parte dell'associazione si taglia è altamente dipendente dal caso d'uso. Si prega di rileggere la sezione che menziona l'euristica per la giustificazione. –

1

Ho riscontrato un problema simile, mentre inviavo il mio POJO (contenente la mappatura bidirezionale @OneToMany e @ManyToOne) come JSON tramite API REST, i dati erano persistenti sia nelle entità padre che in quelle secondarie, ma la relazione di chiave esterna non era stabilito. Questo accade perché le associazioni bidirezionali devono essere mantenute manualmente.

JPA fornisce un'annotazione @PrePersist che può essere utilizzata per assicurarsi che il metodo annotato con esso sia eseguito prima che l'entità venga mantenuta. Poiché, JPA dapprima inserisce l'entità padre nel database seguito dall'entità figlio, ho incluso un metodo annotato con @PrePersist in grado di scorrere l'elenco di entità figlio e impostare manualmente l'entità padre su di esso.

Nel tuo caso, sarebbe qualcosa di simile:

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

Dopo questo si potrebbe ottenere un errore ricorsione infinita, per evitare che annotare la classe genitore con @JsonManagedReference e la classe bambino con @JsonBackReference. Questa soluzione ha funzionato per me, spero che funzionerà anche per te.

Problemi correlati