Atlassian uses cookies to improve your browsing experience, perform analytics and research, and conduct advertising. Accept all cookies to indicate that you agree to our use of cookies on your device. Atlassian cookies and tracking notice, (opens new window)
Embeddable with a primitive field cannot be set to null
Description
Hibernate behaviour changed from version 6.2.7 to 6.2.8 so that existing code breaks. The change is quite annoying, as Hibernate 6.2.9 is included in Spring Boot v3.1.4, and I did not expect such a behaviour change with a patch version update.
Problem 1: JpaSystemException: Null value was assigned to a property
Assume the following entity:
@Entity
data class EntityA(
@Id
val entityId: String,
@AttributeOverrides(
AttributeOverride(name = "amount", column = Column(name = "materialCost_amount")),
AttributeOverride(name = "currency", column = Column(name = "materialCost_currency")),
)
@Embedded
val materialCost: Cost? = null,
)
@Embeddable
@Access(AccessType.FIELD)
data class Cost(
val amount: Int,
val currency: String,
)
@Repository
interface EntityARepository : JpaRepository<EntityA, String>
With v6.2.7 we could save and update the entity as follows:
// initial save
val fixture = EntityA(
entityId = "id1",
materialCost = Cost(20, "USD")
)
val saved = repositoryA.save(fixture)
// update
val fixtureNoCosts = EntityA(
entityId = "id1",
materialCost = null
)
val updated = repositoryA.save(fixtureNoCosts)
With v6.2.8 the last save throws org.springframework.orm.jpa.JpaSystemException: Null value was assigned to a property [class com.example.demo.Cost.amount] of primitive type : com.example.demo.Cost.amount (setter)
Problem 2: Nullable @Embedded object handled as expected
Building on the above example, assume the following entity:
@Entity
data class EntityB(
@Id
val entityId: String,
@AttributeOverrides(
AttributeOverride(name = "amount", column = Column(name = "materialCost_amount")),
AttributeOverride(name = "currency", column = Column(name = "materialCost_currency")),
)
@Embedded
val materialCost: CostNullable? = null,
)
@Embeddable
@Access(AccessType.FIELD)
data class CostNullable(
val amount: Int?, // use nullable, so that a Java 'Integer' wrapper is used instead of 'int' to avoid Problem 1
val currency: String,
)
@Repository
interface EntityBRepository : JpaRepository<EntityB, String>
With v6.2.7 we could save and update the entity as follows:
// initial save
val fixture = EntityB(
entityId = "id1",
materialCost = CostNullable(20, "USD")
)
val saved = repositoryB.save(fixture)
// retrieve
var retrieved = repositoryB.findById(fixture.entityId).get()
// checks
Assertions.assertThat(saved).isEqualTo(fixture)
Assertions.assertThat(retrieved).isEqualTo(fixture)
// update
val fixtureNoCosts = EntityB(
entityId = "id1",
materialCost = null
)
val updated = repositoryB.save(fixtureNoCosts)
// check (works with Hibernate 6.2.7, fails with Hibernate 6.2.8)
// v6.2.8 returns: EntityB(entityId=id1, materialCost=CostNullable(amount=null, currency=null))
// instead of EntityB(entityId=id1, materialCost=null)
Assertions.assertThat(updated).isEqualTo(fixtureNoCosts)
// retrieve works as expected
retrieved = repositoryB.findById(fixture.entityId).get()
Assertions.assertThat(retrieved).isEqualTo(fixtureNoCosts)
With v6.2.8 the repositoryB.save(fixtureNoCosts) returns an entity that has the embedded object set to CostNullable(amount=null, currency=null) which is wrong. When retrieving the same object via findById the embedded object is null as expected.
To reproduce use attached demo project:
# run tests with Hibernate 6.2.7
mvn clean install -P 627
# run tests with Hibernate 6.2.8
mvn clean install -P 628
Hibernate behaviour changed from version 6.2.7 to 6.2.8 so that existing code breaks. The change is quite annoying, as Hibernate 6.2.9 is included in Spring Boot v3.1.4, and I did not expect such a behaviour change with a patch version update.
Problem 1: JpaSystemException: Null value was assigned to a property
Assume the following entity:
@Entity data class EntityA( @Id val entityId: String, @AttributeOverrides( AttributeOverride(name = "amount", column = Column(name = "materialCost_amount")), AttributeOverride(name = "currency", column = Column(name = "materialCost_currency")), ) @Embedded val materialCost: Cost? = null, ) @Embeddable @Access(AccessType.FIELD) data class Cost( val amount: Int, val currency: String, ) @Repository interface EntityARepository : JpaRepository<EntityA, String>
With v6.2.7 we could save and update the entity as follows:
// initial save val fixture = EntityA( entityId = "id1", materialCost = Cost(20, "USD") ) val saved = repositoryA.save(fixture) // update val fixtureNoCosts = EntityA( entityId = "id1", materialCost = null ) val updated = repositoryA.save(fixtureNoCosts)
With v6.2.8 the last
save
throwsorg.springframework.orm.jpa.JpaSystemException: Null value was assigned to a property [class com.example.demo.Cost.amount] of primitive type : com.example.demo.Cost.amount (setter)
Problem 2: Nullable @Embedded object handled as expected
Building on the above example, assume the following entity:
@Entity data class EntityB( @Id val entityId: String, @AttributeOverrides( AttributeOverride(name = "amount", column = Column(name = "materialCost_amount")), AttributeOverride(name = "currency", column = Column(name = "materialCost_currency")), ) @Embedded val materialCost: CostNullable? = null, ) @Embeddable @Access(AccessType.FIELD) data class CostNullable( val amount: Int?, // use nullable, so that a Java 'Integer' wrapper is used instead of 'int' to avoid Problem 1 val currency: String, ) @Repository interface EntityBRepository : JpaRepository<EntityB, String>
With v6.2.7 we could save and update the entity as follows:
// initial save val fixture = EntityB( entityId = "id1", materialCost = CostNullable(20, "USD") ) val saved = repositoryB.save(fixture) // retrieve var retrieved = repositoryB.findById(fixture.entityId).get() // checks Assertions.assertThat(saved).isEqualTo(fixture) Assertions.assertThat(retrieved).isEqualTo(fixture) // update val fixtureNoCosts = EntityB( entityId = "id1", materialCost = null ) val updated = repositoryB.save(fixtureNoCosts) // check (works with Hibernate 6.2.7, fails with Hibernate 6.2.8) // v6.2.8 returns: EntityB(entityId=id1, materialCost=CostNullable(amount=null, currency=null)) // instead of EntityB(entityId=id1, materialCost=null) Assertions.assertThat(updated).isEqualTo(fixtureNoCosts) // retrieve works as expected retrieved = repositoryB.findById(fixture.entityId).get() Assertions.assertThat(retrieved).isEqualTo(fixtureNoCosts)
With v6.2.8 the
repositoryB.save(fixtureNoCosts)
returns an entity that has the embedded object set toCostNullable(amount=null, currency=null)
which is wrong. When retrieving the same object viafindById
the embedded object is null as expected.To reproduce use attached demo project:
# run tests with Hibernate 6.2.7 mvn clean install -P 627 # run tests with Hibernate 6.2.8 mvn clean install -P 628