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.
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 {
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();
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:
https://springbootdev.com/2018/03/13/spring-data-jpa-auditing-with-createdby-createddate-lastmodifiedby-and-lastmodifieddate/
https://github.com/spring-projects/spring-boot/issues/10743#issuecomment-416777423