I have a problem for filling auditing fields by using @PreUpdate and @PrePersist. For instance When I’d like to update a client entity, the field updatedBy and updatedAt are still null; despite when I debug, the code of preUpdate() which is annotated with @PreUpdate is executed.
Below the code of AuditingField which is responsible for creating/updating the auditing fields in each JPA entity:
@Embeddable @Getter @Setter @NoArgsConstructor public class FieldAuditing implements Serializable { @Column(name = "DATE_CREATION", updatable = false) private Instant createdAt; @Column(name = "DATE_MODIFICATION", updatable = false) private Instant updatedAt; @Column(name = "DATE_SUPRESSION", updatable = false) private Instant deletedAt; @Column(name = "AUTEUR_CREATION", updatable = false, length = 100) private String createdBy; @Column(name = "AUTEUR_MODIFICATION", updatable = false, length = 100) private String updatedBy; @Column(name = "AUTEUR_SUPRESSION", updatable = false, length = 100) private String deletedBy; @Column(name = "IS_SUPPRIMER", nullable = false, updatable = false) private boolean isDeleted; @PrePersist public void prePersist() { setCreatedAt(Instant.now()); setCreatedBy(LoggedInUser.get()); } @PreUpdate public void preUpdate() { setUpdatedAt(Instant.now()); setUpdatedBy(LoggedInUser.get()); } @PreRemove public void preRemove() { setDeletedAt(Instant.now()); setDeleted(true); setDeletedBy(LoggedInUser.get()); } }
The client entity that contains embedded auditing fields:
@Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Builder @Table(name="TF_CLIENT", schema="dbo") public class Client implements Serializable { private static final long serialVersionUID = 8832848102370267801L; @Id @GeneratedValue(strategy = GenerationType.AUTO, generator="native") @GenericGenerator(name = "native", strategy = "native") @Column(name = "CLT_ID", nullable = false) private Long id; @Column(name = "CLT_LIBELLE", nullable = false, length = 50, unique = true) private String libelle; @Temporal(TemporalType.DATE) @Column(name = "CLT_DT_OUVERTURE", nullable = false) private Date dateOuverture; @Temporal(TemporalType.DATE) @Column(name = "CLT_DT_FERMETURE") private Date dateFermeture; @Column(name = "CLT_B_ACTIF") private boolean isActif; @Embedded private FieldAuditing fieldAuditing = new FieldAuditing() ; //... rest of another attributes }
The method that updates the client entity
private ClientDto save(ClientDto clientDto, Client client) { startDateShouldBeBeforeEndDate(clientDto); hasUniqueCodePaies(clientDto.getCodePaies()); Client clientSaved = clientRepository.save(clientMapper.toEntity(clientDto, client)); clientMapper.addOrRemoveClientActions(clientDto, clientSaved); clientMapper.addOrRemoveClientEtats(clientDto, clientSaved); clientRepository.save(clientSaved); clientDto.setId(clientSaved.getId()); return clientDto; }
And finally the persistence context configuration:
@Configuration @PropertySource({"classpath:application.yml"}) @EnableJpaRepositories( basePackages = "com.github.maaoutir.clientManager", entityManagerFactoryRef = "mainEntityManager") public class PersistenceContext { private final Environment env; public PersistenceContext(Environment env) { this.env = env; } @Bean @Primary public DataSource mainDataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(Objects.requireNonNull(env.getProperty("spring.datasource.driverClassName"))); dataSource.setUrl(env.getProperty("spring.datasource.url")); dataSource.setUsername(env.getProperty("spring.datasource.username")); dataSource.setPassword(env.getProperty("spring.datasource.password")); return dataSource; } @Bean @Primary public LocalContainerEntityManagerFactoryBean mainEntityManager() { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(mainDataSource()); em.setPackagesToScan("com.github.maaoutir.clientManager"); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); em.setJpaVendorAdapter(vendorAdapter); HashMap<String, Object> properties = new HashMap<>(); // properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto")); properties.put("hibernate.dialect", env.getProperty("spring.jpa.hibernate.dialect")); em.setJpaPropertyMap(properties); return em; } @Primary @Bean public PlatformTransactionManager transactionManager() { JpaTransactionManager transactionManager = new JpaTransactionManager(); transactionManager.setEntityManagerFactory(mainEntityManager().getObject()); return transactionManager; } }
I’m thankful for any help.
Advertisement
Answer
You are using updatable=false
on those columns:
@Column(name = "DATE_MODIFICATION", updatable = false) private Instant updatedAt; @Column(name = "AUTEUR_MODIFICATION", updatable = false, length = 100) private String updatedBy;
This means that JPA doesn’t use this field to update the column. From the JPA spec for updatable
:
Whether the column is included in SQL UPDATE statements generated by the persistence provider.
This make sense for the createdBy
or createdAt
columns, that are set on @PrePersist and persisted with the first INSERT, and you don’t want them to be modified afterwards. But columns updated in @PreUpdate (or @PreRemove) wont be updated with the UPDATE statement if updatable
is set to false