2010-04-05 16 views
11

Ho un'entità con un campo transitorio. Quando voglio creare una nuova istanza dell'oggetto, perdo le mie informazioni transitorie. Nell'esempio seguente viene illustrato il problema. Per fare un esempio, diciamo che il barness è un campo transitorio.Informazioni sui transitori JPA perse creando

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) 

C'è un modo per mantenere le mie informazioni transitorie?

+0

In caso di dubbio, Hibernate e MySQL sono le tecnologie di base. – Pace

risposta

20

Questo è, più o meno, funzionante come progettato. La semantica dei transienti è precisamente quella the data is not persisted. L'entità restituita da entityManager.merge(obj) è, in effetti, un'entità completamente nuova che mantiene lo stato dell'oggetto passato in unione (lo stato, in questo contesto, è qualsiasi cosa che non fa parte dell'oggetto persistente). Questo è dettagliato in the JPA spec. Nota: potrebbero esserci implementazioni JPA che mantengono il campo transitorio dopo che l'oggetto è stato unito (semplicemente perché restituiscono lo stesso oggetto), ma questo comportamento non è garantito dalle specifiche.

Ci sono essenzialmente due cose che puoi fare:

  1. decide di persistere nel campo transitorio. Non sembra essere transitorio se ne hai bisogno dopo aver fuso la classe nel contesto di persistenza.

  2. Mantenere il valore del campo transitorio all'esterno dell'oggetto persistente. Se questo è ciò che soddisfa le tue esigenze, potresti voler ripensare la struttura della tua classe di dominio; se questo campo non fa parte dello stato dell'oggetto dominio, in realtà non dovrebbe esserci.

Un'ultima cosa: il caso d'uso principale che ho trovato per i campi transienti su classi di dominio è quello di delimitare i campi derivati, vale a dire, i campi che possono essere ricalcolati in base ai campi persistenti della classe.

+1

Grazie per la conferma. Abbiamo spostato il campo al di fuori dell'oggetto. – Pace

2

tardi per partecipare alla discussione, ma questo è come ho raggiunto utilizzando primavera AOP e JPA fornito annotazione @PreUpdate (Aggiunta versione dettagliata)

Use Case

  1. Se le modifiche fatte da l'interfaccia utente, dovremmo utilizzare Audit fornito da Spring per le entità
  2. Se le modifiche vengono eseguite tramite un'API e non tramite i servizi front-end, desideriamo che i valori (@LastModifiedBy e @LastModifiedDate) vengano sovrascritti itten con il nostro valore fornito dal cliente
  3. L'entità ha valori transitori (backendModifiedDate, backendAuditor) che devono essere uniti dopo il salvataggio (sfortunatamente Spec non garantisce questo). Questi due campi salverebbero i dati di controllo da servizi esterni
  4. Nel nostro caso volevamo una soluzione generica per il controllo di tutte le entità.

configurazione 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 per iniettare l'utente

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

entità astratta con JPA @PreUpdate
Questo effettivamente impostare il valore per il @ LastM campi odifiedBy e @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; 
     } 
    } 
} 

Aspect per fondere i dati per la conservazione dopo l'unione
Questo sarebbe intercettare l'oggetto (entità) e reimpostare i campi

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

Uso
se l'entità ha backendAuditor eo backendModifiedDate set allora questo valore verrebbe utilizzato altrimenti i valori forniti da Spring Audit sarebbero presi.

Alla fine grazie a Jhipster che semplifica molte cose in modo che tu possa concentrarti sulla logica di business.

Disclaimer: Sono solo un fan di Jhipster e da nessuna parte correlata ad esso in alcun modo.

Problemi correlati