Issue
I was playing with Spring-Data-JDBC and encountered 2 issues. I have following entities with 1:N relationship.
------
DROP TABLE IF EXISTS product;
CREATE TABLE product (
product_id int AUTO_INCREMENT PRIMARY KEY,
name varchar(250) not null,
description varchar(512) not null
);
DROP TABLE IF EXISTS product_line;
CREATE TABLE product_line (
product_id int constraint fk_product_line_product references product(product_id),
label varchar(250) not null
);
----------
@Data
@Builder
public class Product {
@Id
private Long productId;
private String name;
private String description;
@Singular
@MappedCollection(idColumn = "product_id", keyColumn = "product_id")
private Set<ProductLine> lines;
}
@Data
@Builder
public class ProductLine {
private Long productId;
private String label;
}
Problem 1: Following test case fails because I was expecting to have the productId
populated in the ProductLine
object but it is not. Is this the expected behavior of Spring Data JDBC?
@SpringBootTest
class SpringDataJdbcApplicationTests {
@Autowired
private ProductRepository productRepository;
@Test
void saveTest() {
Product product = Product.builder()
.name("Product-1")
.description("Description")
.line(ProductLine
.builder()
.label("Line-label")
.build())
.build();
this.productRepository.save(product);
assertThat(product.getProductId()).isNotNull();
assertThat(product.getLines()).isNotNull().isNotEmpty().hasSize(1);
assertThat(product.getLines().stream().findFirst()).isPresent();
assertThat(product.getLines().stream().findFirst().get().getProductId()).isNotNull().isEqualTo(product.getProductId()); // -----> Fails here.
}
}
Problem 2: If I change Set<ProductLine>
to List<ProductLine>
, it fails due to JdbcSQLIntegrityConstraintViolationException
, which means the product id set to 0 as seen in the log snippet below.
2022-09-10 22:33:12.393 DEBUG 18460 --- [ main] o.s.jdbc.core.JdbcTemplate : Executing prepared SQL statement [INSERT INTO "PRODUCT_LINE" ("LABEL", "PRODUCT_ID") VALUES (?, ?)]
2022-09-10 22:33:12.393 TRACE 18460 --- [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 1, parameter value [Line-label], value class [java.lang.String], SQL type 12
2022-09-10 22:33:12.393 TRACE 18460 --- [ main] o.s.jdbc.core.StatementCreatorUtils : Setting SQL statement parameter value: column index 2, parameter value [0], value class [java.lang.Integer], SQL type 4
Solution
Following test case fails because I was expecting to have the productId populated in the ProductLine object but it is not. Is this the expected behavior of Spring Data JDBC?
Yes, if you want a productId
you have to (and can easily) populate it yourself using plain Java code.
But you really shouldn't need the productId
in the first place since if you follow Domain Driven Design, you will access a ProductLine
exclusively from a Product
which already has the id at hand.
The article https://spring.io/blog/2021/09/22/spring-data-jdbc-how-do-i-make-bidirectional-relationships might be helpful.
If I change
Set<ProductLine>
toList<ProductLine>
, it fails due toJdbcSQLIntegrityConstraintViolationException
, which means the product id set to 0 as seen in the log snippet below.
You have two problems here:
You already have two sources for the
product_id
field: The relation from the aggregate root and the simple field, which may cause problems.You mapped both the back reference to the aggregate root
idColumn
and the index of the listkeyColumn
to the same database column. Together with the simple field from above these are three values all mapped to the same column. Not good. The value that seems to win is the list index, resulting in the exception. In order to fix that, create an additional column in theproduct_line
table and map the list index to it.
Answered By - Jens Schauder
Answer Checked By - David Goodson (JavaFixing Volunteer)