7

This is my entity:

@Builder
@Data
@Entity
@Table(name = "audit_log")
public class AuditEventEntity {
    @Id
    @GeneratedValue
    private UUID id;

    private long createdEpoch;

    @NotNull
    @Size(min = 1, max = 128)
    private String label;

    @NotNull
    @Size(min = 1)
    private String description;
}

And this is my repository:

@Repository
public interface AuditEventRepository extends PagingAndSortingRepository<AuditEventEntity, UUID> {
}

When I write the following unit test for the repository, the save is successful even though the "label" field is null!

@DataJpaTest
@RunWith(SpringRunner.class)
public class AuditRepositoryTest {
    @Test
    public void shouldHaveLabel() {
        AuditEventEntity entity = AuditEventEntity.builder()
                .createdEpoch(Instant.now().toEpochMilli())
                .description(RandomStringUtils.random(1000))
                .build();
        assertThat(entity.getLabel()).isNullOrEmpty();
        AuditEventEntity saved = repository.save(entity);
        // Entity saved and didn't get validated!
        assertThat(saved.getLabel()).isNotNull();
        // The label field is still null, and the entity did persist.
    }

    @Autowired
    private AuditEventRepository repository;
}

Whether I use @NotNull or @Column(nullable = false) the database is created with the not null flag on the column:

Hibernate: create table audit_log (id binary not null, created_epoch bigint not null, description varchar(255) not null, label varchar(128) not null, primary key (id))

I thought the validators would work automatically. What am I doing wrong here?

davidxxx
  • 125,838
  • 23
  • 214
  • 215
Paul
  • 2,698
  • 22
  • 27
  • Possible duplicate of [Confusion: @NotNull vs @Column(nullable = false)](https://stackoverflow.com/questions/7439504/confusion-notnull-vs-columnnullable-false) – Pospolita Nikita Jun 27 '18 at 14:36
  • @PospolitaNikita It looks like Spring will accept either annotation on the field. I tried both and can see Hibernate creating the database with `label varchar(128) not null` in each case. – Paul Jun 27 '18 at 14:53

1 Answers1

6

I thought the validators would work automatically. What am I doing wrong here?

You save the entity but you don't flush the state of the current entity manager.
So the validation of the entity is not performed yet.

You can refer to the Hibernate validator FAQ :

Why is my JPA entity not validated when I call persist()?

Why is my JPA entity not validated when I call persist()? The short answer is call EntityManager#flush() if you want the validation to be triggered.

Hibernate ORM and a few other ORMs try to batch as many operations as possible when accessing the database. It is likely that the actual entity "persist" operation only happens when you call flush() or when the transaction commits.

Also, knowing which entity is about to be persisted depends on your cascading strategy and the state of your object graph. Flush is when Hibernate ORM identifies all the entities that have been changed and require database actions (see also HHH-8028).

So use JpaRepository.saveAndFlush() instead of JpaRepository.save() to allow the entity to be validated.
Or alternatively inject an EntityManager or an TestEntityManager in the test class, invoke JpaRepository.save() and then invoke EntityManager/TestEntityManager.flush().

For information :

JpaRepository.save() invokes em.persist(entity)/em.merge(entity)
JpaRepository.saveAndFlush() invokes JpaRepository.save() and then em.flush()


To be able to invoke saveAndFlush(), you have to make your Repository interface extend JpaRepository such as :

public interface AuditEventRepository extends  JpaRepository<AuditEventEntity, UUID> {

As JpaRepository extends PagingAndSortingRepository, this change stays consistent with your existing declaration.


I would add that this assertion is not required :

assertThat(saved.getLabel()).isNotNull();

What you want to assert is that ValidationException is thrown and maybe that it contains the actual error message.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • I tried using `repository.saveAndFlush()` but the method doesn't appear to exist for my repository. should I inherit from something other than `PagingAndSortingRepository`? Also the assert is simply to demonstrate the problem. Once I understand why `save()` isn't failing I'll change the test. – Paul Jun 27 '18 at 14:56
  • For the first point, I updated. Just change the interface declaration and it should be fine. For the second point, fine in this case. – davidxxx Jun 27 '18 at 15:04
  • That was it! Extending from `JpaRepository` allowed me to use `saveAndFlush()` and the validations work as expected. – Paul Jun 27 '18 at 15:20