JPA的@PreUpdate和@Persist似乎不按预期工作。

huangapple go评论64阅读模式
英文:

JPA @PreUpdate @Persist seems not working as expected

问题

我遇到了一个使用 @PreUpdate@PrePersist 来填充审计字段的问题。例如,当我想更新客户实体时,字段 updatedByupdatedAt 仍然是 null;尽管在调试时,带有 @PreUpdate 注解的 preUpdate() 方法的代码被执行。

以下是负责在每个 JPA 实体中创建/更新审计字段的 AuditingField 类的代码:

@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());
    }
}

包含嵌入式审计字段的 client 实体:

@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();

   //... 其他属性
}

更新 client 实体的方法:

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;
}

最后是持久性上下文配置:

@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 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 = &quot;DATE_CREATION&quot;, updatable = false)
private Instant createdAt;
@Column(name = &quot;DATE_MODIFICATION&quot;, updatable = false)
private Instant updatedAt;
@Column(name = &quot;DATE_SUPRESSION&quot;, updatable = false)
private Instant deletedAt;
@Column(name = &quot;AUTEUR_CREATION&quot;, updatable = false, length = 100)
private String createdBy;
@Column(name = &quot;AUTEUR_MODIFICATION&quot;, updatable = false, length = 100)
private String updatedBy;
@Column(name = &quot;AUTEUR_SUPRESSION&quot;, updatable = false, length = 100)
private String deletedBy;
@Column(name = &quot;IS_SUPPRIMER&quot;, 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=&quot;TF_CLIENT&quot;, schema=&quot;dbo&quot;)
public class Client implements Serializable {
private static final long serialVersionUID = 8832848102370267801L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator=&quot;native&quot;)
@GenericGenerator(name = &quot;native&quot;, strategy = &quot;native&quot;)
@Column(name = &quot;CLT_ID&quot;, nullable = false)
private Long id;
@Column(name = &quot;CLT_LIBELLE&quot;, nullable = false, length = 50, unique = true)
private String libelle;
@Temporal(TemporalType.DATE)
@Column(name = &quot;CLT_DT_OUVERTURE&quot;, nullable = false)
private Date dateOuverture;
@Temporal(TemporalType.DATE)
@Column(name = &quot;CLT_DT_FERMETURE&quot;)
private Date dateFermeture;
@Column(name = &quot;CLT_B_ACTIF&quot;)
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({&quot;classpath:application.yml&quot;})
@EnableJpaRepositories(
basePackages = &quot;com.github.maaoutir.clientManager&quot;,
entityManagerFactoryRef = &quot;mainEntityManager&quot;)
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(&quot;spring.datasource.driverClassName&quot;)));
dataSource.setUrl(env.getProperty(&quot;spring.datasource.url&quot;));
dataSource.setUsername(env.getProperty(&quot;spring.datasource.username&quot;));
dataSource.setPassword(env.getProperty(&quot;spring.datasource.password&quot;));
return dataSource;
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean mainEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(mainDataSource());
em.setPackagesToScan(&quot;com.github.maaoutir.clientManager&quot;);
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap&lt;String, Object&gt; properties = new HashMap&lt;&gt;();
// properties.put(&quot;hibernate.hbm2ddl.auto&quot;, env.getProperty(&quot;hibernate.hbm2ddl.auto&quot;));
properties.put(&quot;hibernate.dialect&quot;, env.getProperty(&quot;spring.jpa.hibernate.dialect&quot;));
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.

答案1

得分: 2

你正在对这些列使用 updatable=false

@Column(name = "DATE_MODIFICATION", updatable = false)
private Instant updatedAt;

@Column(name = "AUTEUR_MODIFICATION", updatable = false, length = 100)
private String updatedBy;

这意味着 JPA 不会使用该字段来更新列。根据 JPA 规范中关于 updatable 的说明:

> 是否在持久化提供程序生成的 SQL UPDATE 语句中包含该列。

这对于 createdBycreatedAt 列是有意义的,它们在 @PrePersist 中设置,并在第一次 INSERT 时持久化,之后您不希望对它们进行修改。但是,如果将列更新在 @PreUpdate(或 @PreRemove)中,并且将 updatable 设置为 false,则不会使用 UPDATE 语句更新这些列。

英文:

You are using updatable=false on those columns:

@Column(name = &quot;DATE_MODIFICATION&quot;, updatable = false)
private Instant updatedAt;
@Column(name = &quot;AUTEUR_MODIFICATION&quot;, 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

答案2

得分: 0

请检查您的配置中是否有 @EnableJpaAuditing,以及您的实体类是否有 @EntityListeners(AuditingEntityListener.class)

英文:

Check if you have @EnableJpaAuditing on your configuration, and @EntityListeners(AuditingEntityListener.class) on your entity class.

答案3

得分: 0

在类@Embeddable中创建一个带有super的构造函数。

对我有效。

public FieldAuditing(){
  super();
}
英文:

Create a constructor in the class @Embeddable with super.

It worked for me.

public FieldAuditing(){
super();
}

huangapple
  • 本文由 发表于 2020年5月4日 19:31:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/61591225.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定