JPA @PreUpdate @Persist seems not working as expected

Tags: , , ,



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.

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



Source: stackoverflow