2010-04-05 18 views
11

Tengo una entidad con un campo transitorio. Cuando quiero crear una nueva instancia del objeto, pierdo mi información transitoria. El siguiente ejemplo demuestra el problema. Por el bien del ejemplo, digamos que barness es un campo transitorio.Información transitoria de JPA perdida en 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) 

¿Hay alguna forma de mantener mi información transitoria?

+0

En caso de que importe, Hibernate y MySQL son las tecnologías subyacentes. – Pace

Respuesta

20

Esto es, más o menos, trabajando según lo diseñado. La semántica de transitorios es precisamente eso the data is not persisted. La entidad devuelta desde entityManager.merge(obj) es, de hecho, una entidad completamente nueva que mantiene el estado del objeto pasado a fusión (estado, en este contexto, es cualquier cosa que no forme parte del objeto persistente). Esto se detalla en the JPA spec. Nota: Puede haber implementaciones de JPA que mantengan el campo transitorio después de que el objeto se haya fusionado (simplemente porque devuelven el mismo objeto), pero este comportamiento no está garantizado por la especificación.

Fundamentalmente, hay dos cosas que puede hacer:

  1. decide persistir el campo transitorio. En realidad, no parece ser transitorio si lo necesita después de fusionar la clase en el contexto de persistencia.

  2. Mantener el valor del campo transitorio fuera del objeto persistente. Si esto es lo que satisface sus necesidades, puede reconsiderar la estructura de su clase de dominio; si este campo no es parte del estado del objeto de dominio, realmente no debería estar allí.

Una última cosa: el caso de uso principal que he encontrado para campos transitorios en clases de dominio es para demarcar campos derivados, es decir, los campos que se pueden recalcular la base de los campos persistentes de la clase.

+1

Gracias por la confirmación. Hemos movido el campo fuera del objeto. – Pace

2

tarde para unirse a la discusión, pero así es como he conseguido usando AOP primavera y APP haya ofrecido anotación @PreUpdate (Adición versión detallada)

caso de uso

  1. Si los cambios hechos de la interfaz de usuario, deberíamos usar la función de auditoría provista por Spring para las entidades
  2. Si los cambios se realizan a través de una API y no a través de servicios front-end, queríamos que los valores (@LastModifiedBy y @LastModifiedDate) se sobrescriban itten con nuestro propio valor proporcionado por el cliente
  3. La entidad tiene valores transitorios (backendModifiedDate, backendAuditor) que deben fusionarse después de guardar (lamentablemente, Spec no lo garantiza). Estos dos campos guardarían los datos de auditoría de los servicios externos
  4. En nuestro caso, queríamos una solución genérica para auditar todas las entidades.

configuración 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 para inyectar el nombre de usuario

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

entidad abstracta con JPA @PreUpdate
Esto realmente establecer el valor de la @ LastM odifiedBy campos y @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; 
     } 
    } 
} 

Aspecto para la fusión de los datos para la retención después de combinación
Esto habría interceptar el objeto (entidad) y resetear los campos

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
si la entidad tiene backendAuditor y/o backendModifiedDate establecidos, entonces este valor se usaría, de lo contrario se tomarían los valores proporcionados por la auditoría de primavera.

Al final gracias a Jhipster que simplifica muchas cosas para que pueda concentrarse en la lógica de negocios.

Descargo de responsabilidad: Solo soy un fanático de Jhipster y no estoy relacionado con él en modo alguno.

Cuestiones relacionadas