简体   繁体   中英

How do I cascade persist an @OneToMany relationship with an @EmbeddedId using Spring Data / Hibernate

I've seen a lot of similar questions asked about this but haven't found a solution that fixes the problem I'm seeing, so apologies up front if this is a redundant question. In my situation I have various types of entities and they're each going to have their own tag associations. So I want a generic Tag class that won't have it's own id, but rather an id / composite key made of the id of the entity it's tagging, plus the tag type. To (attempt to) achieve this I made an @Embeddable id class:

@Embeddable
public class TagId implements Serializable {

  @Column(columnDefinition = "BINARY(16)")
  private UUID parentId;
  private String value;

  // Getters, setters...

}

That Id is in turn used by a @MappedSuperclass :

@MappedSuperClass
public class Tag {
 
  @EmbeddedId
  private TagId id;

  // Other attributes, getters, setters...

}

... and then when I want to tag a specific entity, for example using a BookTag, the table would have a book_id column as a foreign key to a Book table taking the place of parentId :

@Entity
@Table(name = "book_tag")
@AttributeOverride(name = "parentId", column = @Column(name = "book_id"))
public class BookTag extends Tag {

  // other attributes, getters, setters...

}

Then finally, I have a Book entity:

@Entity
@Table(name = "book")
public class Book {

  @Id
  @GeneratedValue
  @Column(columnDefinition = "Binary(16)")
  private UUID id;

  // other attributes, getters, setters...

  @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.parentId")
  private List<BookTag> tags;
}

When I then try to save a new Book, with a populated BookTag collection, using a Spring Data JPA repository to repo.save(book) , my desired behavior is that the Book is saved, then the id is copied to the BookTag objects, and those are saved. Unfortunately, what I'm seeing in the log is that Book is inserted as expected, then the inserts for the Tag objects are run, but book_id is being bound as null for each of the entries.

I've tried a few other approaches:

  1. @JoinColumn instead of mappedBy
  2. @MapsId with a @ManyToOne reference to Book on BookTag
  3. @GeneratedValue on parentId

None worked, but it is possible my syntax was off. Thanks in advance for anyone who knows how to tackle this problem.

To anyone who wants to do something similar, I finally found a solution that meets my criteria.

TagId was modified to this:

@Embeddable
public class TagId<T> implements Serializable {

  @ManyToOne
  private T parent;
  private String value;

  // Getters, setters...

}

...which leads to a slight modification to Tag...

@MappedSuperClass
public class Tag<T> {
 
  @EmbeddedId
  private TagId id;

  // Other attributes, getters, setters...

}

...and then BookTag...

@Entity
@Table(name = "book_tag")
@AttributeOverride(name = "parent", column = @Column(name = "book_id"))
public class BookTag extends Tag<Book> {

  // other attributes, getters, setters...

}

...and finally Book:

@Entity
@Table(name = "book")
public class Book {

  @Id
  @GeneratedValue
  @Column(columnDefinition = "Binary(16)")
  private UUID id;

  // other attributes, getters, setters...

  @JoinColumn(name = "book_id", referencedColumnName = "id")
  @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
  private List<BookTag> tags;
}

Now I can add 1...* BookTags to a Book, and in turn I have to set the Book on all the BookTags, but then it's one call to bookRepository.save() and everything cascades down. It would have been nicer to just do it with an id, but a generic is flexible enough. I'll just have it implement an interface so that toString/hashCode/equals can call getId on parent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM