Hello, I'm migrating from hibernate 5.6.15 to hibernate 6.2 and I have noticed some issues. it seems that I can’t use an enum as a string with `@Enumerated( STRING )` when my enum is part of a composite primary key which itself is reference another composite primary key.
h2. Entities
h3. Product
{code:java}@Getter @IdClass(ProductPK.class) @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString(onlyExplicitlyIncluded = true) @NoArgsConstructor(access = PROTECTED) @Entity @OptimisticLocking(type = OptimisticLockType.DIRTY) @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(name = "OPERATOR_ID", nullable = false) @JoinColumn(name = "COUNTRY", nullable = false, columnDefinition = "varchar") private Operator operator;
@Column(name = "DESCRIPTION") @Setter private String description;
@Embedded private Benefits benefits;
@EqualsAndHashCode @ToString @Embeddable @NoArgsConstructor(access = PROTECTED) public static class ProductPK implements Serializable {
private String productId; @Embedded private Operator.OperatorPK operator;
public ProductPK(String productId, Operator.OperatorPK operator) { this.productId = productId; this.operator = operator; }
public ProductPK(String productId, String operatorID, Country country) { this.productId = productId; this.operator = new Operator.OperatorPK(operatorID, country); } }
@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; } }{code}
h3. Operator
{code:java} @Getter @Entity @ToString(onlyExplicitlyIncluded = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true) @NoArgsConstructor(access = PROTECTED) @Table(name = "OPERATORS") @IdClass(Operator.OperatorPK.class) @OptimisticLocking(type = OptimisticLockType.DIRTY) @DynamicUpdate @Cacheable @Cache(usage = READ_WRITE) public class Operator {
@EqualsAndHashCode.Include @ToString.Include @Id @Enumerated(STRING) @Column(name = "COUNTRY", nullable = false, columnDefinition = "varchar") private Country country;
@EqualsAndHashCode.Include @ToString.Include @Id @Column(name = "OPERATOR_ID", nullable = false) private String operatorId;
@ManyToOne @JoinColumn(name = "meta_operator_id", referencedColumnName = "ID") private MetaOperator metaOperator;
@OneToMany(mappedBy = "operator", cascade = { CascadeType.ALL PERSIST, MERGE, REMOVE }, orphanRemoval = true, fetch = FetchType.LAZY) private List<Product> products = new ArrayList<>();
public Operator(String operatorId) { this.operatorId = operatorId; // default country this.country = USA; }
public void setMetaOperator(MetaOperator metaOperator) { this.metaOperator = metaOperator; }
public void setProducts(List<Product> products) { this.products = products; }
@Embeddable @Value @AllArgsConstructor @NoArgsConstructor(access = PROTECTED) public static class OperatorPK implements Serializable { @NonFinal String operatorId; @NonFinal @Enumerated(STRING) Country country; }
}{code}
h4. note :
At first, I did not specify `columnDefinition = "varchar"` on Operator nor Product but I would get
{noformat}Error executing DDL "create table operators (country clob not null check (country in ('USA','FRA')), meta_operator_id varchar(255), operator_id varchar(255) not null, primary key (country, operator_id))" via JDBC [Fonctionnalité non supportée: "Index on column: ""COUNTRY"" CHARACTER LARGE OBJECT NOT NULL" Feature not supported: "Index on column: ""COUNTRY"" CHARACTER LARGE OBJECT NOT NULL";]{noformat}
even though I had {{@Enumerated(STRING)}} on Operator.country
h3. Country
{code:java}public enum Country { USA, FRA; } {code}
h2. Tests
h3. addProductTest
{code:java} @Test void addProductTest() { String string = "ID"; String operatorID = "operatorID"; ProductPK id = new ProductPK(string, operatorID, USA); String test = "test"; Operator operator = new Operator(operatorID); operatorService.addOperator(operator); Product product = new Product(string, operator); product.setDescription(test); productService.addProduct(product);
Optional<Product> byId = productService.getProduct(id); assertThat(byId.orElseThrow().getDescription()).isEqualTo(test); Optional<Product> byId2 = productService.readProduct(id); assertThat(byId2.orElseThrow().getOperator().getOperatorId()).isEqualTo(operatorID); }{code}
an exception occurs when {{productService.addProduct(product);}} is committed :
{noformat} at com.example.demo.service.ProductService$$SpringCGLIB$$0.addProduct(<generated>) at com.example.demo.service.ProductServiceTest.addProductTest(ProductServiceTest.java:43) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ... Caused by: jakarta.persistence.RollbackException: Error while committing the transaction at org.hibernate.internal.ExceptionConverterImpl.convertCommitException(ExceptionConverterImpl.java:65) at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104) at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:561) ... 80 more Caused by: java.lang.ClassCastException: class java.lang.Byte cannot be cast to class java.lang.String (java.lang.Byte and java.lang.String are in module java.base of loader 'bootstrap') at org.hibernate.type.descriptor.java.StringJavaType.unwrap(StringJavaType.java:27) at org.hibernate.type.descriptor.jdbc.VarcharJdbcType$1.doBind(VarcharJdbcType.java:108) ... ... 81 more{noformat}
h3. shouldDeleteOperator
{code:java} @Test @Order(2) void shouldDeleteOperator() { // Given String operatorID = "operatorID2"; Operator operator = new Operator(operatorID); operatorDao.save(operator); // When operatorService.deleteOperator(new Operator.OperatorPK(operatorID, USA));
//Then Optional<Operator> byId2 = operatorService.getOperator(operatorID); assertThat(byId2).isEmpty(); }{code}
this one fails when committing the transaction for {{operatorService.deleteOperator}}
{noformat}org.springframework.dao.DataIntegrityViolationException: could not execute statement [Erreur lors de la conversion de données "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)" Data conversion error converting "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)"; SQL statement: delete from operators where country=? and operator_id=? and meta_operator_id is null [22018-214]] [delete from operators where country=? and operator_id=? and meta_operator_id is null]; SQL [delete from operators where country=? and operator_id=? and meta_operator_id is ... Caused by: org.hibernate.exception.DataException: could not execute statement [Erreur lors de la conversion de données "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)" Data conversion error converting "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)"; SQL statement: delete from operators where country=? and operator_id=? and meta_operator_id is null [22018-214]] [delete from operators where country=? and operator_id=? and meta_operator_id is null] at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:53) at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56) at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:108) ... ... 80 more Caused by: org.h2.jdbc.JdbcSQLDataException: Erreur lors de la conversion de données "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)" Data conversion error converting "'USA' (PRODUCTS: ""OPERATOR_ID"" TINYINT NOT NULL)"; SQL statement: delete from operators where country=? and operator_id=? and meta_operator_id is null [22018-214]{noformat}
h2. Sources
source code can be found in branch {{cant_use_enum_as_string_in_join_columns}} : [https://github.com/emouty/hibernate-issues/tree/cant_use_enum_as_string_in_join_columns|https://github.com/emouty/hibernate-issues/tree/cant_use_enum_as_string_in_join_columns|smart-link]
for information all the tests in {{OperatorServiceTest}}, {{ProductServiceTest}}, {{ProductServiceWithCacheTest}} are failing. |
|