Erwan Moutymbo (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c...
) *updated* an issue
Hibernate ORM (
https://hibernate.atlassian.net/browse/HHH?atlOrigin=eyJpIjoiYTk4YmE0Yzhm...
) / Bug (
https://hibernate.atlassian.net/browse/HHH-17020?atlOrigin=eyJpIjoiYTk4Ym...
) HHH-17020 (
https://hibernate.atlassian.net/browse/HHH-17020?atlOrigin=eyJpIjoiYTk4Ym...
) Can't use enum as string in join column when field is part of composite primary key
(
https://hibernate.atlassian.net/browse/HHH-17020?atlOrigin=eyJpIjoiYTk4Ym...
)
Change By: Erwan Moutymbo (
https://hibernate.atlassian.net/secure/ViewProfile.jspa?accountId=640210c...
)
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_i...]
for information all the tests in {{OperatorServiceTest}}, {{ProductServiceTest}},
{{ProductServiceWithCacheTest}} are failing.
(
https://hibernate.atlassian.net/browse/HHH-17020#add-comment?atlOrigin=ey...
) Add Comment (
https://hibernate.atlassian.net/browse/HHH-17020#add-comment?atlOrigin=ey...
)
Get Jira notifications on your phone! Download the Jira Cloud app for Android (
https://play.google.com/store/apps/details?id=com.atlassian.android.jira....
) or iOS (
https://itunes.apple.com/app/apple-store/id1006972087?pt=696495&ct=Em...
) This message was sent by Atlassian Jira (v1001.0.0-SNAPSHOT#100233- sha1:b4f309b )