JPA переходная информация теряется при создании - PullRequest
14 голосов
/ 06 апреля 2010

У меня есть объект с переходным полем. Когда я хочу создать новый экземпляр объекта, я теряю свою временную информацию. Следующий пример демонстрирует проблему. Для примера скажем, что 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)

Есть ли способ сохранить мою временную информацию?

Ответы [ 3 ]

21 голосов
/ 06 апреля 2010

Это, более или менее, работает как задумано.Семантика переходного процесса заключается в том, что данные не сохраняются .Сущность, возвращаемая из entityManager.merge(obj), фактически является совершенно новой сущностью, которая поддерживает состояние объекта, переданного в слияние (в данном контексте состояние - это все, что не является частью постоянного объекта).Это подробно описано в спецификации JPA .Примечание. Могут быть реализации JPA, которые поддерживают переходное поле после объединения объекта (просто потому, что они возвращают один и тот же объект), но это поведение не гарантируется спецификацией.

Существуют две вещи, которые вы можетеможно сделать:

  1. Решить сохранить переходное поле.Это на самом деле не кажется временным, если вам это нужно после слияния класса с контекстом постоянства.

  2. Поддерживайте значение переходного поля вне постоянного объекта.Если это то, что соответствует вашим потребностям, вы можете переосмыслить структуру класса вашего домена;если это поле не является частью состояния объекта домена, его там действительно не должно быть.

И последнее: основной вариант использования, который я нашел для переходных полей в доменеклассы предназначены для разграничения производных полей, т. е. полей, которые могут быть пересчитаны на основе постоянных полей класса.

3 голосов
/ 17 октября 2017

Поздно присоединиться к обсуждению, но так я добился этого, используя Spring AOP и JPA предоставили аннотацию @PreUpdate (Добавление подробной версии)

Вариант использования

  1. Если в пользовательский интерфейс были внесены изменения, мы должны использовать предоставляемый Spring Audit для сущностей
  2. Если изменения производятся через API, а не через интерфейсные службы, мы хотели, чтобы значения (@LastModifiedBy и @LastModifiedDate) были перезаписаны нашим собственным значением, предоставленным клиентом
  3. У объекта есть временные значения (backendModifiedDate, backendAuditor), которые необходимо объединить после сохранения (к сожалению, Spec не гарантирует этого). Эти два поля сохранят данные аудита из внешних служб
  4. В нашем случае мы хотели универсальное решение для аудита всех юридических лиц.

Конфигурация 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;
        }
    }
}

Аспект слияния данных для хранения после слияния
Это перехватит объект (Entity) и сбросит поля

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
    // /2142992/jpa-perehodnaya-informatsiya-teryaetsya-pri-sozdaniitab-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 голосов
/ 03 января 2019

На основе @ Prassed Удивительно ответ Я создал более общий код:

Мне нужно разрешить некоторые переходные поля в сущности (я имею в виду поля, которые мы не храним в БД, но мы разрешаем пользователю заполнять их данными, которые мы отправляем на сервер [с @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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...