我有一个具有瞬态字段的实体。当我想创建对象的新实例时,我会丢失我的瞬态信息。以下示例演示了此问题。为了举例,我们可以说barness是一个瞬态场。
FooEntity fooEntity = new FooEntity();
fooEntity.setFoobosity(5);
fooEntity.setBarness(2);
fooEntity = fooEntityManager.merge(fooEntity);
System.out.println(fooEntity.getFoobosity()); //5
System.out.println(fooEntity.getBarness()); //0 (or whatever default barness is)
有没有办法维护我的瞬态信息?
答案 0 :(得分:21)
这或多或少都按设计工作。瞬态的语义恰恰是the data is not persisted。事实上,从entityManager.merge(obj)
返回的实体是一个全新的实体,它维护传递给合并的对象的状态(状态,在此上下文中,是不属于持久对象的任何东西)。这在the JPA spec中有详细说明。注意:可能有JPA实现在合并对象后维护瞬态字段(仅仅因为它们返回相同的对象),但规范不保证这种行为。
你可以做两件事:
决定保持瞬态场。如果在将类合并到持久化上下文后需要它,它似乎并不是暂时的。
维持持久对象之外的瞬态字段的值。如果这符合您的需求,您可能需要重新考虑域类的结构;如果这个字段不是域对象状态的一部分,它实际上不应该存在。
最后一件事:我在域类的瞬态字段中发现的主要用例是划分派生字段,即可以根据类的持久字段重新计算的字段。
答案 1 :(得分:3)
迟到加入讨论,但这就是我使用 spring AOP和JPA提供@PreUpdate注释(添加详细版本)
的方法使用案例
Db配置
package config;
import io.github.jhipster.config.JHipsterConstants;
import io.github.jhipster.config.liquibase.AsyncSpringLiquibase;
import liquibase.integration.spring.SpringLiquibase;
import org.h2.tools.Server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.env.Environment;
import org.springframework.core.task.TaskExecutor;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.sql.SQLException;
@Configuration
@EnableJpaRepositories("repository")
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
private final Logger log = LoggerFactory.getLogger(DatabaseConfiguration.class);
private final Environment env;
public DatabaseConfiguration(Environment env) {
this.env = env;
}
/* Other code */
}
用于注入用户名的SpringSecurityAuditorAware
package security;
import config.Constants;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
/**
* Implementation of AuditorAware based on Spring Security.
*/
@Component
public class SpringSecurityAuditorAware implements AuditorAware<String> {
@Override
public String getCurrentAuditor() {
String userName = SecurityUtils.getCurrentUserLogin();
return userName != null ? userName : Constants.SYSTEM_ACCOUNT;
}
}
抽象实体与JPA @PreUpdate
这实际上会设置@ LastModifiedBy和@LastModifiedDate字段的值
package domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.hibernate.envers.Audited;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import javax.persistence.PreUpdate;
import java.io.Serializable;
import java.time.Instant;
/**
* Base abstract class for entities which will hold definitions for created, last modified by and created,
* last modified by date.
*/
@MappedSuperclass
@Audited
@EntityListeners(AuditingEntityListener.class)
public abstract class AbstractAuditingEntity implements Serializable {
private static final long serialVersionUID = 1L;
@CreatedBy
@Column(name = "created_by", nullable = false, length = 50, updatable = false)
@JsonIgnore
private String createdBy;
@CreatedDate
@Column(name = "created_date", nullable = false)
@JsonIgnore
private Instant createdDate = Instant.now();
@LastModifiedBy
@Column(name = "last_modified_by", length = 50)
@JsonIgnore
private String lastModifiedBy;
@LastModifiedDate
@Column(name = "last_modified_date")
@JsonIgnore
private Instant lastModifiedDate = Instant.now();
private transient String backendAuditor;
private transient Instant backendModifiedDate;
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public Instant getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Instant createdDate) {
this.createdDate = createdDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Instant getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Instant lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
public String getBackendAuditor() {
return backendAuditor;
}
public void setBackendAuditor(String backendAuditor) {
this.backendAuditor = backendAuditor;
}
public Instant getBackendModifiedDate() {
return backendModifiedDate;
}
public void setBackendModifiedDate(Instant backendModifiedDate) {
this.backendModifiedDate = backendModifiedDate;
}
@PreUpdate
public void preUpdate(){
if (null != this.backendAuditor) {
this.lastModifiedBy = this.backendAuditor;
}
if (null != this.backendModifiedDate) {
this.lastModifiedDate = this.backendModifiedDate;
}
}
}
合并后合并保留数据的方面
这将拦截对象(实体)并重置字段
package aop.security.audit;
import domain.AbstractAuditingEntity;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.time.Instant;
@Aspect
@Component
public class ExternalDataInflowAudit {
private final Logger log = LoggerFactory.getLogger(ExternalDataInflowAudit.class);
// As per our requirements, we need to override @LastModifiedBy and @LastModifiedDate
// https://stackoverflow.com/questions/2581665/jpa-transient-information-lost-on-create?answertab=active#tab-top
@Around("execution(public !void javax.persistence.EntityManager.merge(..))")
private Object resetAuditFromExternal(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
AbstractAuditingEntity abstractAuditingEntity;
Instant lastModifiedDate = null;
String lastModifiedBy = null;
if (args.length > 0 && args[0] instanceof AbstractAuditingEntity) {
abstractAuditingEntity = (AbstractAuditingEntity) args[0];
lastModifiedBy = abstractAuditingEntity.getBackendAuditor();
lastModifiedDate = abstractAuditingEntity.getBackendModifiedDate();
}
Object proceed = joinPoint.proceed();
if (proceed instanceof AbstractAuditingEntity) {
abstractAuditingEntity = (AbstractAuditingEntity) proceed;
if (null != lastModifiedBy) {
abstractAuditingEntity.setLastModifiedBy(lastModifiedBy);
abstractAuditingEntity.setBackendAuditor(lastModifiedBy);
log.debug("Setting the Modified auditor from [{}] to [{}] for Entity [{}]",
abstractAuditingEntity.getLastModifiedBy(), lastModifiedBy, abstractAuditingEntity);
}
if (null != lastModifiedDate) {
abstractAuditingEntity.setLastModifiedDate(lastModifiedDate);
abstractAuditingEntity.setBackendModifiedDate(lastModifiedDate);
log.debug("Setting the Modified date from [{}] to [{}] for Entity [{}]",
abstractAuditingEntity.getLastModifiedDate(), lastModifiedDate, abstractAuditingEntity);
}
}
return proceed;
}
}
<强>用法强>
如果实体具有backendAuditor和/或backendModifiedDate设置,那么将使用此值,否则将采用Spring Audit提供的值。
最后归功于Jhipster,它简化了很多事情,使您可以专注于业务逻辑。
免责声明:我只是Jhipster的粉丝,无论如何都与它无关。
答案 2 :(得分:2)
基于@Prassed惊人的answer,我创建了一个更通用的代码:
我需要在实体上允许一些临时字段(我的意思是我们不保留在数据库中的字段,但是我们允许用户用我们发送给服务器的数据填充它们[使用@JsonSerialize
/ @JsonDeserialize
]并上传到文件存储。
这些字段将带有以下注释(RetentionPolicy。此处使用RUNTIME,因此我可以在运行时对这些字段使用反射):
@Retention(RetentionPolicy.RUNTIME)
public @interface PreservePostMerge { }
然后,我使用apache的FieldUtil遍历这些字段:
@Aspect
@Component
public class PreservePostMergeData {
private final Logger log = LoggerFactory.getLogger(PreservePostMergeData.class);
@Around("execution(public !void javax.persistence.EntityManager.merge(..))")
private Object preserveTransientDataPostMerge(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Object afterMerge = joinPoint.proceed();
if (args.length > 0) {
Object beforeMerge = args[0];
Field[] annotatedFieldsToPreserve = FieldUtils.getFieldsWithAnnotation(beforeMerge.getClass(), PreservePostMerge.class);
Arrays.stream(annotatedFieldsToPreserve).forEach(field -> {
try {
FieldUtils.writeField(field, afterMerge, FieldUtils.readField(field, beforeMerge, true), true);
} catch (IllegalAccessException exception) {
log.warn("Illegal accesss to field: {}, of entity: {}. Data was not preserved.", field.getName(), beforeMerge.getClass());
}
});
}
return afterMerge;
}
}