0

This is a request for clarification after watching a video about Spring Data JDBC.

I have an aggregate made of Book (aggregate root), BookCopy and BookCopyQualityControl. A Book has many BookCopy, and a BookCopy has many BookCopyQualityControl. I want only the Book class to have an @Id (Long), because it is the aggregate root (this is suggested here).

In my database:

  • BOOK's primary key is ID;
  • BOOK_COPY's primary key is the couple (BOOK_ID, COPY_NUMBER), with BOOK_ID referencing BOOK(ID);
  • BOOK_COPY_QUALITY_CONTROL's primary key is the triple (BOOK_ID, COPY_NUMBER, QUALITY_CONTROL_NUMBER), with (BOOK_ID, COPY_NUMBER) referencing BOOK_COPY(BOOK_ID, COPY_NUMBER).

I am trying to implement what is explained here. I'll transcribe. "The way I would recommend to do it: the elements inside an aggregate have either, as a primary key, the same value as the aggregate root, in case of a one-to-one relationship, or they have the id of the aggregate root plus an additional column, or, if you have deeper nested elements, multiple additional columns that together make up a primary key, and that is used by Spring Data JDBC".

The deeper nested elements part seems exactly my case, but I don't understand how to do it.

In the Book class, it's easy:

public class Book {
 @Id
 private Long id;
 
 @MappedCollection(idColumn = "BOOK_ID")
 private Set<BookCopy> copies;
 
 // ...stuff...
}

But what should I do in the BookCopy class for the one-to-many relationship with BookCopyQualityControl? I tried this:

public class BookCopy {
 private Long copyNumber;
 
 @MappedCollection(idColumn = "COPY_NUMBER")
 private Set<BookCopyQualityControl> qualityControls;
 // ...stuff...
}

but, when saving the Book, Spring Data JDBC will produce an INSERT statement for the BOOK_COPY_QUALITY_CONTROL table that doesn't include the BOOK_ID column, that is mandatory. And since @MappedCollection accepts a single idColumn, I don't know how to proceed. For completeness, this is the BookCopyQualityControl class:

public class BookCopyQualityControl {
 private Long qualityControlNumber;
 
 // ...stuff...
}

EDIT: I am still stuck after about 2 months and I haven't received any comment or response yet. All the above is still valid, but I will paste below all the code, hoping to get some feedback. I am using spring-boot-starter-data-jdbc from Spring Boot 3.5.5 with Java 17. Thank you in advance.

Database DDL (H2 in memory with MODE=MYSQL)

CREATE TABLE BOOK(
 ID bigint AUTO_INCREMENT NOT NULL,
 TITLE varchar(50) NOT NULL,
 CONSTRAINT PK_BOOK PRIMARY KEY
 (
 ID ASC
 )
);
CREATE TABLE BOOK_COPY(
 COPY_NUMBER bigint AUTO_INCREMENT NOT NULL,
 BOOK_ID bigint NOT NULL,
 AUTHOR_MESSAGE varchar(50) NOT NULL,
 CONSTRAINT PK_BOOK_COPY PRIMARY KEY
 (
 COPY_NUMBER,
 BOOK_ID
 )
);
ALTER TABLE BOOK_COPY ADD CONSTRAINT FK_BOOK_COPY FOREIGN KEY(BOOK_ID) REFERENCES BOOK (ID);
CREATE TABLE BOOK_COPY_QUALITY_CONTROL(
 QUALITY_CONTROL_NUMBER bigint AUTO_INCREMENT NOT NULL,
 COPY_NUMBER bigint NOT NULL,
 BOOK_ID bigint NOT NULL,
 SCORE INT NOT NULL,
 CONSTRAINT UQ_QUALITY_CONTROL PRIMARY KEY
 (
 QUALITY_CONTROL_NUMBER,
 COPY_NUMBER,
 BOOK_ID
 )
);
ALTER TABLE BOOK_COPY_QUALITY_CONTROL ADD CONSTRAINT FK_BOOK_COPY_QUALITY_CONTROL FOREIGN KEY(COPY_NUMBER, BOOK_ID) REFERENCES BOOK_COPY (COPY_NUMBER, BOOK_ID);

BookRepository.java

public interface BookRepository extends CrudRepository<Book, Long>, PagingAndSortingRepository<Book, Long> { }

Book.java

public class Book {
 @Id
 private Long id;
 private String title;
 @MappedCollection(idColumn = "BOOK_ID")
 private Set<BookCopy> copies;
 public Book(String title) {
 this.title = title;
 this.copies = new HashSet<>();
 }
 public BookCopy addCopy(String authorMessage) {
 BookCopy newCopy = new BookCopy(authorMessage);
 this.copies.add(newCopy);
 return newCopy;
 }
 public BookCopyQualityControl addQualityControl(BookCopy copy, Integer score) {
 BookCopyQualityControl newQualityControl = copy.addQualityControl(score);
 return newQualityControl;
 }
 public Long getId() {
 return id;
 }
 public String getTitle() {
 return title;
 }
 public Set<BookCopy> getCopies() {
 return copies;
 }
}

BookCopy.java

public class BookCopy {
 private Long copyNumber;
 private final String authorMessage;
 @MappedCollection(idColumn = "COPY_NUMBER")
 private Set<BookCopyQualityControl> qualityControls;
 BookCopy(String authorMessage) {
 this.authorMessage = authorMessage;
 qualityControls = new HashSet<>();
 }
 BookCopyQualityControl addQualityControl(Integer score) {
 BookCopyQualityControl newQualityControl = new BookCopyQualityControl(score);
 this.qualityControls.add(newQualityControl);
 return newQualityControl;
 }
 public String getAuthorMessage() {
 return authorMessage;
 }
 public Long getCopyNumber() {
 return copyNumber;
 }
 public Set<BookCopyQualityControl> getQualityControls() {
 return qualityControls;
 }
}

BookCopyQualityControl.java

public class BookCopyQualityControl {
 private Long qualityControlNumber;
 private Integer score;
 BookCopyQualityControl(Integer score) {
 this.score = score;
 }
 public Long getQualityControlNumber() {
 return qualityControlNumber;
 }
 public Integer getScore() {
 return score;
 }
}

test

void myTest() {
 Book tales = new Book("Tales of Spring Data JDBC");
 BookCopy newCopy1 = tales.addCopy("I wish you a nice read!");
 tales.addQualityControl(newCopy1, 7);
 BookCopy newCopy2 = tales.addCopy("Hope you enjoy!");
 tales.addQualityControl(newCopy2, 9);
 bookRepository.save(tales);
}

error running the test

Caused by: org.h2.jdbc.JdbcBatchUpdateException: NULL not allowed for column "BOOK_ID"; SQL statement:
INSERT INTO "BOOK_COPY_QUALITY_CONTROL" ("COPY_NUMBER", "QUALITY_CONTROL_NUMBER", "SCORE") VALUES (?, ?, ?) [23502-232]
asked Aug 28 at 0:31

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.