Spring Boot JPA@CreatedDate @LastModifiedDate not being populated when saving the object
Asked Answered
F

6

22

I am writing an app with Spring Boot + JPA, using a Postgres database. I have a User Entity and I am trying to get a timestamp when the user record is saved and/or modified. This is not working. The record that is being saved has "null" for both createdDate and lastModifiedDate.

Here is all the relevant code in Kotlin:

@Entity
@Table(name = "app_user")
@EntityListeners(AuditingEntityListener::class)
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "id")
        val id: UUID? = null,

        @NotNull
        @Column(unique = true)
        val email: String,
        val name: String,
        private val password: String,

        @CreatedDate
        @Column(name = "created_date", nullable = false, updatable = false)
        var createdDate: LocalDateTime? = null,

        @LastModifiedDate
        @Column(name = "last_modified_date", nullable = false)
        var lastModifiedDate: LocalDateTime? = null
)

My SQL queries to add the date fields look like this:

ALTER TABLE app_user
ADD COLUMN last_modified_date TIMESTAMP,
ADD COLUMN created_date TIMESTAMP;

I also have a configuration class

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {

    @Bean
    fun auditorProvider(): AuditorAware<String> {
        return AuditorAware { Optional.of(SecurityContextHolder.getContext().authentication.name) }
    }

}

And my test class:

  @Autowired
    private lateinit var userRepository: UserRepository

    val user = User(
            email = "email",
            name = "name",
            password = "password"
    )

    @Test
    fun `it sets the timestamps when a user is created`() {
        userRepository.save(user)
        user.let {
            assertThat(it.createdDate).isNotNull()
            assertThat(it.lastModifiedDate).isNotNull()
        }
    }

UPDATE:

It seems the problem only happens in tests. It seems to be fine when i execute the same code to create a user in the development environment and not in tests.

Do i need some additional config in the tests?

UPDATE 2

I also tried to use the entity manager like this:

@Autowired
lateinit var entityManager: TestEntityManager

Then in the test do:

entityManager.persist(user)
entityManager.flush()

And also

entityManager.persistAndFlush(user)

Still is not populating the timestamps.

Frequent answered 6/8, 2018 at 14:16 Comment(1)
I've edited my question. I am unable to get the date fields auto-populated. Basically my test fails. I end up with a null for the record instead of the timestampsFrequent
K
26

The AuditingEntityListener methods are called in the @PrePersist and @PreUpdate phase.

This means they are called just before the insert or update SQL statements are executed.

Read more about JPA events in the Hibernate doc: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#events-jpa-callbacks

Unit Tests

When using in Tests you have to enable auditing as well on the test

@DataJpaTest
@RunWith(SpringRunner.class)
@EnableJpaAuditing
public class EntityListenerTest {
Katabasis answered 6/8, 2018 at 15:12 Comment(4)
Thanks for the answer. I've written an update. I think injecting and using the repository should work for storing the user model exactly as it would outside of the test environment. I also tried to use the TestEntityManager instance to do this. It contains the methods you are describing here.Frequent
I edited my question. You have to explicitly enable auditing in the testKatabasis
In case that you are using audit fields like @CreatedBy you need also to enable some configuration to configure your @EnableJpaAuditing stuff like AuditorAware. In this case just import additionaly @Import(YourJPAConfiguration.class) in test class. Maybe add also @WithMockUser(username = "testuser"). See also #43706220 (answer from user Sydney)Ossicle
For anyone else who landed here but still had issues... it's easy to overlook that the Entity class that uses the @CreatedDate annotation (or other auditing annotations) MUST have the @EntityListeners(AuditingEntityListener.class) annotation at the top of that Entity class. I realize the OP had that in their example code, but I overlooked it.Misadventure
A
8

I had similar issue (Spring Boot 2.2.1 and JUnit5) with null values. Adding @EnableJpaAuditing to test class did not work.

My example (did not copy everything):

@Getter(AccessLevel.PROTECTED)
@Setter(AccessLevel.PROTECTED)
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class Auditable<U> {

    @CreatedBy
    @Column(name = "created_by", nullable = false)
    private U createdBy;

    @CreatedDate
    @Column(name = "created", nullable = false)
    private OffsetDateTime created;
}

and respective class:

@Getter
@Setter
@Entity
@Table(name="survey_records")
public class SurveyRecord extends Auditable<String> implements Serializable {

I had following configuration for auditing:

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider", dateTimeProviderRef = "auditingDateTimeProvider")
public class JpaAuditingConfiguration {

    @Bean(name = "auditingDateTimeProvider")
    public DateTimeProvider dateTimeProvider() {
        return () -> Optional.of(OffsetDateTime.now());
    }

    @Bean
    public AuditorAware<String> auditorProvider() {

To get @CreatedBy and @CreatedDate working inside test only @Import was needed

@DataJpaTest
@Import(JpaAuditingConfiguration.class)
class SurveyRecordRepositoryTest {

Used references:

Abbevillian answered 12/12, 2019 at 13:0 Comment(0)
H
4

I had similar issue, but the reason for my case is that I had @Transactional annotation on the test class, after removing that, everything works as expected.

Hayner answered 8/7, 2020 at 20:19 Comment(0)
O
3

My prefered solution is a set of multiple annotations to ensure test of @CreatedBy or @CreatedDate:

@RunWith(SpringRunner.class)
@DataJpaTest
@Import(JPAConfig.class)
@WithMockUser(username = "testuser")
public class MYRepositoryTest {
...
}

My Entities looks like:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class AbstractEntity {

    @CreatedBy
    private String createdBy;

    @CreatedDate
    private Date createdOn;

    @LastModifiedDate
    private Date updatedOn;

    @LastModifiedBy
    private String updatedBy;
}

My JPA configuration is:

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JPAConfig {

    @Bean
    public AuditorAware<String> auditorAware() {
        return () -> Optional.of(((User)     SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
    }
}

Now your audit fields also created during JPA test:

assertThat(result.getCreatedBy()).isNotNull().isEqualTo("testuser");
assertThat(result.getCreatedOn()).isNotNull();
assertThat(result.getUpdatedBy()).isNotNull().isEqualTo("testuser");
assertThat(result.getUpdatedOn()).isNotNull();
Ossicle answered 24/10, 2019 at 11:39 Comment(0)
M
0

Faced the same issue, but other solutions didn't help. (I'm using Kotlin) Then I've realized that I've declared the field with @LastModifiedBy as val, not var, i.e. readonly. It didn't fail to update it, just silently did nothing. When I changed the field declaration to var it started working ok.

Minh answered 21/8, 2023 at 21:30 Comment(0)
U
-3

you have to use

 @Column(name = "created_date", nullable = false, updatable = false)
 
 private LocalDate created = LocalDate.now() ;
Udella answered 14/4, 2022 at 7:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.