Hello, I'm migrating from hibernate 5.6.15 to hibernate 6.2 and I have noticed some issues while I perform a batch delete of some of my entities Entities Operator
@Getter
@Entity
@ToString(onlyExplicitlyIncluded = true)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@NoArgsConstructor(access = PROTECTED)
@Table(name = "OPERATORS")
@OptimisticLocking(type = OptimisticLockType.DIRTY)
@DynamicUpdate
@Cacheable
@Cache(usage = READ_WRITE)
public class Operator {
public Operator(String operatorId) {
this.operatorId = operatorId;
}
@EqualsAndHashCode.Include
@ToString.Include
@Id
@Column(name = "OPERATOR_ID", nullable = false)
private String operatorId;
@OneToMany(mappedBy = "operator", cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Product> products = new ArrayList<>();
public void setProducts(List<Product> products) {
this.products = products;
}
}
Product
@Getter
@IdClass(ProductPK.class)
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString(onlyExplicitlyIncluded = true)
@NoArgsConstructor(access = PROTECTED)
@Entity
@OptimisticLocking(type = OptimisticLockType.ALL)
@DynamicUpdate
@Cacheable
@Cache(usage = READ_WRITE)
@Table(name = "PRODUCTS")
public class Product {
public Product(String productId, Operator operator) {
this.productId = productId;
this.operator = operator;
}
public Product(String productId, Operator operator, Benefits benefits) {
this.productId = productId;
this.operator = operator;
this.benefits = benefits;
}
@EqualsAndHashCode.Include
@ToString.Include
@Id
@Column(name = "PRODUCT_ID", nullable = false)
private String productId;
@Id
@EqualsAndHashCode.Include
@ToString.Include
@Getter
@Setter
@Cache(usage = READ_WRITE)
@ManyToOne(fetch = LAZY, optional = false)
@JoinColumn(nullable = false)
private Operator operator;
@Column(name = "DESCRIPTION")
@Setter
private String description;
@Embedded
private Benefits benefits;
@AllArgsConstructor
@EqualsAndHashCode
@ToString
@NoArgsConstructor(access = PUBLIC)
public static class ProductPK implements Serializable {
private String productId;
private String operator;
}
@Embeddable
@Value
@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
public static class Benefits {
@Embedded
@NonFinal
@Setter
TypeOneBenefit credit;
@Embedded
@NonFinal
@Setter
TypeTwoBenefit data;
}
@Embeddable
@Value
@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
public static class TypeOneBenefit {
@NonFinal
@Column(name = "BENEFIT_ONE_BASE_AMOUNT")
BigDecimal baseAmount;
}
@Embeddable
@Value
@AllArgsConstructor
@NoArgsConstructor(access = PROTECTED)
public static class TypeTwoBenefit {
@NonFinal
@Column(name = "BENEFIT_TWO_BASE_AMOUNT")
String baseAmount;
}
}
Tests Batch delete
@Test
void shouldDeleteAllProductsWithBenefits() {
String productId1 = "ID1";
String productId2 = "ID2";
String operatorID = "operatorID2";
String test = "test";
Operator operator = new Operator(operatorID);
operatorService.addOperator(operator);
TypeOneBenefit typeOneBenefit = new TypeOneBenefit(BigDecimal.TEN);
Benefits benefits1 = new Benefits(typeOneBenefit, null);
Product product = new Product(productId1, operator, benefits1);
product.setDescription(test);
productService.addProduct(product);
TypeTwoBenefit typeTwoBenefit = new TypeTwoBenefit(BigDecimal.ONE.toString());
Benefits benefits2 = new Benefits(null, typeTwoBenefit);
Product product2 = new Product(productId2, operator, benefits2);
productService.addProduct(product2);
productService.deleteAllProducts();
ProductPK productPK = new ProductPK(productId1, operatorID);
Optional<Product> byId2 = productService.getProduct(productPK);
assertThat(byId2).isEmpty();
}
When I run this test, I get the following error :
It seems that the query used to remove the first product is reused for the second even though their benefits are not the same. The correct query for the second product should have been :
delete from products where operator_operator_id=? and product_id=? and benefit_two_base_amount=? and description=?
Cascade delete We get the same error when we delete the operator which cascade delete all of its products
@Test
void shouldDeleteProductsWithBenefitsFromOperator() {
String productId1 = "ID1";
String productId2 = "ID2";
String operatorID = "operatorID2";
String test = "test";
Operator operator = new Operator(operatorID);
operatorService.addOperator(operator);
TypeOneBenefit typeOneBenefit = new TypeOneBenefit(BigDecimal.TEN);
Benefits benefits1 = new Benefits(typeOneBenefit, null);
Product product = new Product(productId1, operator, benefits1);
product.setDescription(test);
productService.addProduct(product);
TypeTwoBenefit typeTwoBenefit = new TypeTwoBenefit(BigDecimal.ONE.toString());
Benefits benefits2 = new Benefits(null, typeTwoBenefit);
Product product2 = new Product(productId2, operator, benefits2);
productService.addProduct(product2);
ProductPK productPK = new ProductPK(productId1, operatorID);
operatorService.deleteOperator(operatorID);
Optional<Product> byId2 = productService.getProduct(productPK);
assertThat(byId2).isEmpty();
}
sources can be found in branch product_with_benefits : https://github.com/emouty/hibernate-issues/tree/product_with_benefits |