Incorrect insert of an object with NotNull property

hibernate-core
5.6.9.Final

Odd behavior by Hibernate was discovered – we create a new object with null property value (the property has @NotNull annotation), persist the object, then fill the NotNull property and at the moment of flushing JdbcSQLIntegrityConstraintViolationException occurred. Looks like the object avoid the hibernate-validator in this case because the JdbcSQLIntegrityConstraintViolationException appears instead of ConstraintViolationException.

More details:

We have the Employee object with @NotNull property Name.

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@DynamicInsert(value = true)
@DynamicUpdate(value = true)
public class Employee {
    @Id
    @GeneratedValue
    private int id;

    @Column(nullable = false)
    @NotNull
    private String name;

    private Double salary;
}

 

If execute the code below – everything seems ok, the created object inserting correctly.

@Test
public void whenSetNameBeforePersist_thenSuccess(){
    Employee employee01 = new Employee();
    employee01.setName(employeeName);
    entityManager.persist(employee01);
    int idEmployee = employee01.getId();
    entityManager.flush();
    entityManager.clear();
    Employee employeeAfterCommit = entityManager.find(Employee.class, idEmployee);
    Assert.assertEquals(employeeAfterCommit.getName(), employeeName);
}

If we set name after persist, despite the fact that the property in our object has a not null value we get an error.

@Test
public void whenSetNameAfterPersist_thenSuccess(){
    Employee employee01 = new Employee();
    entityManager.persist(employee01);
    employee01.setName(employeeName);
    int idEmployee = employee01.getId();

    //There was expectation that the Employee object would be flushed successfully (like the whenSetNameBeforePersist_thenSuccess test does)
    //but instead a "org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "NAME"; SQL statement" error occurred.
    //It seems like Hibernate is trying to insert the version of the Employee object passed to persist method.
    //This looks incorrect, shouldn't Hibernate inserts current version of the Employee object instead to avoid the error?
    //In case if current behavior is correct
    //then why is the error not the ConstraintViolationException we get in the whenNameIsNull_thenConstraintViolationException test?
    //This looks like Hibernate is trying to insert the Employee object without any validation.
    entityManager.flush();
    entityManager.clear();
    Employee employeeAfterCommit = entityManager.find(Employee.class, idEmployee);
    Assert.assertEquals(employeeAfterCommit.getName(), employeeName);
}

It looks like the Hibernate is trying to generate sql insert statement with the created object that has not current version but the version generated at the persist moment.

Code below confirms the information. NotNull property was filled before persist. Now the error disappeared and we see sql insert statement and sql update statement in logs. Does not “update after insert” generate performance problems?

@Test
public void updateSalaryAfterPersist_thenSuccess(){
    Employee employee01 = new Employee();
    employee01.setName(employeeName);
    entityManager.persist(employee01);
    employee01.setSalary(100.0);
    int idEmployee = employee01.getId();
    entityManager.flush();
    entityManager.clear();
    Employee employeeAfterCommit = entityManager.find(Employee.class, idEmployee);
    Assert.assertEquals(employeeAfterCommit.getName(), employeeName);
}

2023-11-14 19:10:29.189 DEBUG 1484 — [ main] org.hibernate.SQL :
insert
into
employee
(name, id)
values
(?, ?)
2023-11-14 19:10:29.189 TRACE 1484 — [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [Employee01]

2023-11-14 19:10:29.189 TRACE 1484 — [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [2] as [INTEGER] - [1]

2023-11-14 19:10:29.204 DEBUG 1484 — [ main] org.hibernate.SQL :
update
employee
set
salary=?
where
id=?

If behavior above is correct in case when hibernate-validator is enabled we should get ConstraintViolationException instead of JdbcSQLIntegrityConstraintViolationException. And even if we fix the situation with exceptions why we get ConstraintViolationException when property is filled with not null value.

The HHH-17437 bug was registered accidentally before

Hibernate 5.6 is no longer supported, but I would suggest at least upgrading to the latest micro version 5.6.15.Final to see if the issue persists.

We’re no longer fixing bug against version 5, see the ORM website releases page for more information, so I would suggest upgrading to the latest major version as soon as possible or requesting commercial support.

Checked it out on the newest version “6.4.4.Final” and hibernate.validator.version 8.0.1.Final

The problem is the same.

We have the Employee object that has @notNull and @length annotations.

@Entity
public class Employee {
    @Id
    @GeneratedValue
    private int id;

    @Column(nullable = false, length = 3)
    @NotNull
    @Length(min = 0, max = 3)
    private String name;

    private Double salary;
}

and we have a test case where the name was set to an incorrect value (with length > 3). After persisting, the value was set to the correct value “Jon”

@Test
    public void whenSetCorrectNameAfterPersist_thenSuccess(){
        Employee employee01 = new Employee();
        employee01.setName("Employee01");
        entityManager.persist(employee01);
        employee01.setName(employeeName); //"Jon"
        int idEmployee = employee01.getId();
        Assert.assertThat(employee01.getName(), CharSequenceLength.hasLength(3));
        entityManager.flush();
        entityManager.clear();
        Employee employeeAfterCommit = entityManager.find(Employee.class, idEmployee);
        Assert.assertEquals(employeeAfterCommit.getName(), employeeName);
    }

As the result → at the flush moment we see that hibernate was trying to insert the persisted object with the incorrect value and then update it to the correct value. Is that correct behavior?

2024-03-21 18:46:19.260 DEBUG 15352 --- [           main] org.hibernate.SQL                        : 
    call next value for hibernate_sequence
2024-03-21 18:46:19.261 DEBUG 15352 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        employee
        (name, id) 
    values
        (?, ?)
2024-03-21 18:46:19.261 TRACE 15352 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Employee01]
2024-03-21 18:46:19.261 TRACE 15352 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [INTEGER] - [4]                      :

javax.persistence.PersistenceException: org.hibernate.exception.DataException: could not execute statement
Caused by: org.h2.jdbc.JdbcSQLDataException: Value too long for column "NAME VARCHAR(3)": "'Employee01' (10)"; SQL statement:

If set hibernate.validator.version from 8.0.1.Final to 6.2.5.Final the same error occurred with @NotNull annotation

@Test
    public void whenSetNullNameBeforePersist_thenSuccess(){
        Employee employee01 = new Employee();
        employee01.setName(null);
        entityManager.persist(employee01);
        employee01.setName(employeeName);
        int idEmployee = employee01.getId();
        entityManager.flush();
        entityManager.clear();
        Employee employeeAfterCommit = entityManager.find(Employee.class, idEmployee);
        Assert.assertEquals(employeeAfterCommit.getName(), employeeName);
    }
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "NAME"; SQL statement:
insert into employee (id) values (?) [23502-200]

You have to configure the jakarta.persistence.validation.mode configuration property to callback. Also see the documentation.