J'écris une application avec Spring Boot + JPA, en utilisant une base de données Postgres. J'ai une entité utilisateur et j'essaie d'obtenir un horodatage lorsque l'enregistrement utilisateur est sauvegardé et/ou modifié. Cela ne fonctionne pas. L'enregistrement en cours de sauvegarde a la valeur "null" pour createdDate et lastModifiedDate.
Voici tout le code pertinent dans 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
)
Mes requêtes SQL pour ajouter les champs de date ressemblent à ceci:
ALTER TABLE app_user
ADD COLUMN last_modified_date TIMESTAMP,
ADD COLUMN created_date TIMESTAMP;
J'ai aussi une classe de configuration
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
class JpaAuditingConfiguration {
@Bean
fun auditorProvider(): AuditorAware<String> {
return AuditorAware { Optional.of(SecurityContextHolder.getContext().authentication.name) }
}
}
Et ma classe de test:
@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()
}
}
MISE À JOUR:
Il semble que le problème ne survienne que dans les tests. Cela semble bien quand j'exécute le même code pour créer un utilisateur dans l'environnement de développement et non dans les tests.
Ai-je besoin d'une configuration supplémentaire dans les tests?
MISE À JOUR 2
J'ai également essayé d'utiliser le gestionnaire d'entités comme ceci:
@Autowired
lateinit var entityManager: TestEntityManager
Ensuite, dans le test, faites:
entityManager.persist(user)
entityManager.flush()
Et aussi
entityManager.persistAndFlush(user)
Ne remplit toujours pas les horodatages.
Les méthodes AuditingEntityListener sont appelées dans le @PrePersist
et @PreUpdate
phase.
Cela signifie qu'elles sont appelées juste avant l'exécution des instructions SQL d'insertion ou de mise à jour.
En savoir plus sur les événements JPA dans le document Hibernate: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#events-jpa-callbacks
Tests unitaires
Lors de l'utilisation dans les tests, vous devez également activer l'audit sur le test
@DataJpaTest
@RunWith(SpringRunner.class)
@EnableJpaAuditing
public class EntityListenerTest {
Ma solution préférée est un ensemble d'annotations multiples pour assurer le test de @CreatedBy
ou @CreatedDate
:
@RunWith(SpringRunner.class)
@DataJpaTest
@Import(JPAConfig.class)
@WithMockUser(username = "testuser")
public class MYRepositoryTest {
...
}
Mes entités ressemblent à:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class AbstractEntity {
@CreatedBy
private String createdBy;
@CreatedDate
private Date createdOn;
@LastModifiedDate
private Date updatedOn;
@LastModifiedBy
private String updatedBy;
}
Ma configuration JPA est:
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
public class JPAConfig {
@Bean
public AuditorAware<String> auditorAware() {
return () -> Optional.of(((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername());
}
}
Maintenant, vos champs d'audit sont également créés lors du test JPA:
assertThat(result.getCreatedBy()).isNotNull().isEqualTo("testuser");
assertThat(result.getCreatedOn()).isNotNull();
assertThat(result.getUpdatedBy()).isNotNull().isEqualTo("testuser");
assertThat(result.getUpdatedOn()).isNotNull();
J'ai eu un problème similaire (Spring Boot 2.2.1 et JUnit5) avec les valeurs null
. Ajouter @EnableJpaAuditing
pour tester la classe n'a pas fonctionné.
Mon exemple (n'a pas tout copié):
@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;
}
et classe respective:
@Getter
@Setter
@Entity
@Table(name="survey_records")
public class SurveyRecord extends Auditable<String> implements Serializable {
J'avais la configuration suivante pour l'audit:
@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() {
Obtenir @CreatedBy
et @CreatedDate
fonctionne uniquement à l'intérieur du test @Import
était nécessaire
@DataJpaTest
@Import(JpaAuditingConfiguration.class)
class SurveyRecordRepositoryTest {
Références utilisées: