2010-04-05 9 views
11

Ich habe eine Entität mit einem transienten Feld. Wenn ich eine neue Instanz des Objekts erstellen möchte, verliere ich meine transienten Informationen. Das folgende Beispiel veranschaulicht das Problem. Für das Beispiel nehmen wir an, dass die Barness ein transientes Feld ist.JPA transiente Informationen verloren auf create

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) 

Gibt es eine Möglichkeit, meine transienten Informationen zu verwalten?

+0

Wenn es darauf ankommt, sind Hibernate und MySQL die zugrunde liegenden Technologien. – Pace

Antwort

20

Dies funktioniert mehr oder weniger wie geplant. Die Semantik der Transienten ist genau das the data is not persisted. Die von entityManager.merge(obj) zurückgegebene Entität ist tatsächlich eine vollständig neue Entität, die den Zustand des Objekts beibehält, das in die Zusammenführung übergeben wurde (Zustand, in diesem Kontext, ist alles, was nicht Teil des persistenten Objekts ist). Dies wird ausführlich in the JPA spec beschrieben. Anmerkung: Es gibt JPA-Implementierungen, die das transiente Feld nach dem Zusammenführen des Objekts beibehalten (weil sie das gleiche Objekt zurückgeben), aber dieses Verhalten wird durch die Spezifikation nicht garantiert.

Es gibt im Wesentlichen zwei Dinge, die Sie tun können:

  1. Entscheiden die transiente Feld zu bestehen. Es scheint nicht wirklich vorübergehend zu sein, wenn Sie es nach dem Zusammenführen der Klasse in den Persistenzkontext benötigen.

  2. Pflegen Sie den Wert des transienten Feldes außerhalb des persistenten Objekts. Wenn dies Ihren Anforderungen entspricht, sollten Sie die Struktur Ihrer Domänenklasse überdenken. Wenn dieses Feld nicht Teil des Status des Domänenobjekts ist, sollte es wirklich nicht dort sein.

Eine letzte Sache: Der Hauptanwendungsfall I für transiente Felder auf Domain-Klassen gefunden habe, ist abgeleitete Felder abgrenzen, das heißt Felder, die auf der Grundlage der persistenten Felder der Klasse neu berechnet werden können.

+1

Danke für die Bestätigung. Wir haben das Feld außerhalb des Objekts verschoben. – Pace

2

spät, um die Diskussion zu beteiligen, aber das ist, wie ich es Frühling AOP und JPA bereitgestellt @PreUpdate Anmerkung

  1. Wenn Änderungen von (Hinzufügen ausführliche Version)

    Use Case mit erreicht die UI sollten wir Spring bereitgestellten Audit für Entitäten verwenden

  2. Wenn Änderungen über eine API und nicht über Front-End-Dienste vorgenommen werden, wollten wir die Werte (@LastModifiedBy und @LastModifiedDate), um überschrieben werden itten mit unserem eigenen Wert vom Client
  3. Entity hat transiente Werte (BackendModifiedDate, BackendAuditor), die nach dem Speichern zusammengeführt werden mussten (leider Spec garantiert dies nicht). Diese beiden Felder würden die Audit-Daten von externen Diensten speichern
  4. In unserem Fall wollten wir eine generische Lösung für die Prüfung aller Entitäten.

Db Konfiguration

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 zum Einspritzen des Benutzername

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

abstrakte Entität mit JPA @PreUpdate
Dies wird den Wert für die tatsächlich eingestellte @ LetzteM odifiedBy und @LastModifiedDate Felder

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

Aspect für die Zusammenführung der Daten nach der für die Retention fusionieren
Dies würde das Objekt (Entity) abfangen und Zurücksetzen, um die Felder

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

Usage
wenn Hat die Entität backendAuditor und/oder backendModifiedDate festgelegt, dann würde dieser Wert verwendet werden, sonst würden Spring Audit-Werte verwendet werden.

Am Ende dank Jhipster, die eine Menge Dinge vereinfacht, so dass Sie sich auf die Geschäftslogik konzentrieren können.

Haftungsausschluss: Ich bin nur ein Fan von Jhipster und nirgendwo damit in irgendeiner Weise verbunden.

Verwandte Themen