Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/jpa/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
JPA瞬态信息在创建时丢失_Jpa - Fatal编程技术网

JPA瞬态信息在创建时丢失

JPA瞬态信息在创建时丢失,jpa,Jpa,我有一个带有瞬变场的实体。当我想创建一个新的对象实例时,我会丢失暂时的信息。下面的示例演示了这个问题。为了便于示例,我们假设barness是一个瞬态场 FooEntity fooEntity = new FooEntity(); fooEntity.setFoobosity(5); fooEntity.setBarness(2); fooEntity = fooEntityManager.merge(fooEntity); System.out.println(fooEntity.getFoob

我有一个带有瞬变场的实体。当我想创建一个新的对象实例时,我会丢失暂时的信息。下面的示例演示了这个问题。为了便于示例,我们假设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)

有什么方法可以维护我的临时信息吗?

这或多或少是按设计工作的。transient的语义正是如此。从
entityManager.merge(obj)
返回的实体实际上是一个全新的实体,它维护传递到merge中的对象的状态(在本文中,状态是不属于持久对象的任何内容)。详细信息请参见。注意:可能有JPA实现在对象合并后维护瞬态字段(仅仅因为它们返回相同的对象),但是规范不保证这种行为

基本上你可以做两件事:

  • 决定保持瞬态场。如果在将类合并到持久性上下文中之后需要它,那么它实际上似乎不是暂时的

  • 在持久对象外部保持瞬态字段的值。如果这满足了您的需求,那么您可能需要重新考虑域类的结构;如果此字段不是域对象状态的一部分,则它实际上不应该存在


  • 最后一件事:我发现域类上的临时字段的主要用例是划分派生字段,即可以根据类的持久字段重新计算的字段。

    加入讨论较晚,但这就是我使用spring AOP和JPA提供的@PreUpdate annotation实现它的方式。(增加详细版本)

    用例

    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;
        }
    }
    
  • 如果从UI进行了更改,我们应该使用Spring提供的实体审计
  • 如果更改是通过API而不是通过前端服务完成的,我们希望使用客户端提供的我们自己的值覆盖这些值(@LastModifiedBy和@LastModifiedDate)
  • 实体具有临时值(backendModifiedDate、backendAuditor),需要在保存后合并这些值(遗憾的是,规范不保证这一点)。 这两个字段将保存来自外部服务的审核数据
  • 在我们的例子中,我们需要一个用于审计所有实体的通用解决方案
  • 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;
        }
    }
    
    合并数据以在合并后保留的方面
    这将截取对象(实体)并重置字段

    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的粉丝,与它没有任何关系。

    基于惊人的发现,我创建了一个更通用的代码:

    我需要在实体上允许一些临时字段(我指的是我们不保留在数据库中的字段,但我们允许用户使用我们发送到服务器[使用/]并上传到文件存储的数据填充这些字段)

    这些字段将使用以下注释进行注释(.RUNTIME在此处使用,因此我可以在运行时对这些字段使用反射):

    然后,我使用apache遍历这些字段:


    如果有必要,Hibernate和MySQL是底层技术。感谢您的确认。我们已经将字段移到了object之外。
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PreservePostMerge { }
    
    @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;
        }
    }