Подтвердить что ты не робот

Как автогенерировать созданное или измененное поле метки времени?

Мой класс сущности:

@Entity
@Table(name = "user")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name = "USER_ID_GENERATOR", sequenceName = "USER_SEQ")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "USER_ID_GENERATOR")
    @Column(name = "user_id")
    private long userId;


    @Temporal(TemporalType.DATE)
    private Date created;

    @Temporal(TemporalType.DATE)
    private Date modified;

    //setters and getters...
}

Я бы хотел, чтобы поля CREATED и MODIFIED автоматически дополняли друг друга при создании или изменении объекта. Поля CREATED и MODIFIED должны иметь тип TIMESTAMP.

Как мне этого добиться?

4b9b3361

Ответ 1

Вы можете просто создать new Date() всякий раз, когда создается ваш экземпляр, а затем обновлять поле updated всякий раз, когда сущность получает обновление:

private Date created = new Date();
private Date updated = new Date();

@PreUpdate
public void setLastUpdate() {  this.updated = new Date(); }

Не предоставляйте сеттер для любого из этих методов, только геттеры.

Ответ 3

Мы делаем это с помощью PreInsertEventListener и PreUpdateEventListener:

public class TracabilityListener implements PreInsertEventListener,PreUpdateEventListener {
    private void setPropertyState(Object[] propertyStates, String[] propertyNames,String propertyName,Object propertyState) {
        for(int i=0;i<propertyNames.length;i++) {
            if (propertyName.equals(propertyNames[i])) {
                propertyStates[i]=propertyState;
                return;
            }
        }
    }
    private void onInsert(Object entity,Object[] state, String[] propertyNames) {
        if (entity instanceof DomainObject) {
            DomainObject domainObject = (DomainObject) entity;
            Date date=new Date();
            domainObject.setDateCreation(date);
            setPropertyState(state, propertyNames, "dateCreation", date);
            domainObject.setDateModification(date);
            setPropertyState(state, propertyNames, "dateModification", date);
        }
    }

    private void onUpdate(Object entity,Object[] state, String[] propertyNames) {
        if (entity instanceof DomainObject) {
            DomainObject domainObject = (DomainObject) entity;
            Date date=new Date();
            setPropertyState(state, propertyNames, "dateCreation", domainObject.getDateCreation());
            domainObject.setDateModification(date);
            setPropertyState(state, propertyNames, "dateModification", date);
        }
    }

    @Override
    public boolean onPreInsert(PreInsertEvent event) {
        onInsert(event.getEntity(), event.getState(), event.getPersister().getPropertyNames());
        return false;
    }

    @Override
    public boolean onPreUpdate(PreUpdateEvent event) {
        onUpdate(event.getEntity(), event.getState(), event.getPersister().getPropertyNames());
        return false;
    }
}

Но если вы хотите, чтобы ваши свойства были метками времени, тогда они должны быть аннотированы с помощью

@Temporal(TemporalType.TIMESTAMP)

Ответ 4

Вы можете использовать Spring Data JPA, Spring упростил использование аннотаций @CreatedBy, @CreatedDate, @LastModifiedBy, @LastModifiedDate для ваших полей. Вы можете следовать ниже простой пример

// Will need to enable JPA Auditing
@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorAware")
class JpaConfig {
    // Creating a bean of AuditorAwareImpl which will provide currently logged in user
    @Bean
    public AuditorAware<String> auditorAware() {
        return new AuditorAwareImpl();
    }
}

// Moving necessary fields to super class and providing AuditingEntityListener entity listner class
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
abstract class Auditable<U> {

    @CreatedBy
    protected U createdBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    protected Date createdDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TIMESTAMP)
    protected Date lastModifiedDate;

    // Getters and Setters
}

// Creating implementation of AuditorAware and override its methods to provide currently logged in user
class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
        return "Naresh";
        // Can use Spring Security to return currently logged in user
        // return ((User) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUsername()
    }
}

@Entity
class File extends Auditable<String> {
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private String content;

    // Getters and Setters
} 

Вы можете прочитать больше в моей статье Spring Data JPA Auditing: Сохранение CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate для получения дополнительной информации.

Ответ 5

Так как это общая проблема, и существует множество полуразрешенных решений, доступных для поиска, позвольте мне представить, на чем я остановился:

  • определяют две тривиальные аннотации полей, @CreatedDate и @ModifiedDate;
  • использовать их для аннотирования соответствующих полей на вашей сущности;
  • зарегистрируйте класс сущности с помощью слушателя Traceability, представленного ниже.

Вот как выглядит ваш класс сущностей:

@EntityListeners(Traceability.class)
public class MyEntity {
  @CreatedDate @Temporal(TIMESTAMP) public Date created;
  @ModifiedDate @Temporal(TIMESTAMP public Date modified;
  ....
}

Эти однострочные элементы определяют аннотации:

@Retention(RUNTIME) @Target(FIELD) public @interface CreatedDate {}
@Retention(RUNTIME) @Target(FIELD) public @interface ModifiedDate {}

Вы можете поместить их в свои собственные файлы, или вы можете связать их внутри какого-либо существующего класса. Я предпочитаю первое, поэтому я получаю для них более полное полное имя.

Здесь класс слушателя Entity:

public class Traceability
{
  private final ConcurrentMap<Class<?>, TracedFields> fieldCache = new ConcurrentHashMap<>();

  @PrePersist
  public void prePersist(Object o) { touchFields(o, true); }

  @PreUpdate
  public void preUpdate(Object o) { touchFields(o, false); }

  private void touchFields(Object o, boolean creation) {
    final Date now = new Date();
    final Consumer<? super Field> touch = f -> uncheckRun(() -> f.set(o, now));
    final TracedFields tf = resolveFields(o);
    if (creation) tf.created.ifPresent(touch);
    tf.modified.ifPresent(touch);
  }

  private TracedFields resolveFields(Object o) {
    return fieldCache.computeIfAbsent(o.getClass(), c -> {
      Field created = null, modified = null;
      for (Field f : c.getFields()) {
        if (f.isAnnotationPresent(CreatedDate.class)) created = f;
        else if (f.isAnnotationPresent(ModifiedDate.class)) modified = f;
        if (created != null && modified != null) break;
      }
      return new TracedFields(created, modified);
    });
  }

  private static class TracedFields {
    public final Optional<Field> created, modified;

    public TracedFields(Field created, Field modified) {
      this.created = Optional.ofNullable(created);
      this.modified = Optional.ofNullable(modified);
    }
  }

  // Java ill-conceived checked exceptions are even worse when combined with
  // lambdas. Below is what we need to achieve "exception transparency" (ability
  // to let checked exceptions escape the lambda function). This disables
  // compiler checking of exceptions thrown from the lambda, so it should be 
  // handled with utmost care.      

  public static void uncheckRun(RunnableExc r) {
    try { r.run(); }
    catch (Exception e) { sneakyThrow(e); }
  }

  public interface RunnableExc { void run() throws Exception; }

  public static <T> T sneakyThrow(Throwable e) { 
    return Traceability.<RuntimeException, T> sneakyThrow0(e); 
  }

  @SuppressWarnings("unchecked") 
  private static <E extends Throwable, T> T sneakyThrow0(Throwable t) throws E {
    throw (E) t;
  }
}

Наконец, если вы не работаете с JPA, но с классическим Hibernate, вам нужно активировать интеграцию модели событий JPA. Это очень просто, просто убедитесь, что путь к классу содержит файл META-INF/services/org.hibernate.integrator.spi.Integrator со следующей отдельной строкой в ​​его содержимом:

org.hibernate.jpa.event.spi.JpaIntegrator

Обычно для проекта Maven вам просто нужно поместить это в свой каталог src/main/resources.

Ответ 6

Поскольку @PrePersist и @PreUpdate игнорируются при использовании сеанса Hibernate, я сделал относительно простое решение с использованием Interceptors:

  • Определите интерфейс "Auditable":

    public interface Auditable {
      public void setUpdated_at(Date date);
      public void setCreated_at(Date date);
    }
    
  • Определите класс "AuditableInterceptor"

    public class AuditableInterceptor extends EmptyInterceptor {
    
        private static final long serialVersionUID = -3557239720502671471L;
    
        @override
        public boolean onFlushDirty(Object entity,
                Serializable id,
                Object[] currentState,
                Object[] previousState,
                String[] propertyNames,
                Type[] types) {
    
            if (entity instanceof Auditable) {
                for (int i = 0; i < propertyNames.length; i++) {
                    if ("updated_at".equals(propertyNames[i])) {
                        currentState[i] = new Date();
                        return true;
                    }
                }
            }
            return false;
        }
    
        @override
        public boolean onSave(Object entity,
                Serializable id,
                Object[] state,
                String[] propertyNames,
                Type[] types) {
    
            if (entity instanceof Auditable) {
                for (int i = 0; i < propertyNames.length; i++) {
                    if ("created_at".equals(propertyNames[i])) {
                        state[i] = new Date();
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
  • Укажите перехватчик, когда вы открываете новый сеанс (скорее всего, это будет в служебном классе)

    sessionFactory.openSession(new AuditableInterceptor());
    // sessionFactory.openSession();
    
  • Внедрите интерфейс Auditable в своих сущностях, например

    @Entity
    public class Product implements Auditable {
    
        ...
        private Date created_at;
        private Date updated_at;
        ...
    
        public Product() {
        }
    
        ...
    
        @Temporal(javax.persistence.TemporalType.TIMESTAMP)
        public Date getCreated_at() {
            return created_at;
        }
    
        public void setCreated_at(Date created_at) {
            this.created_at = created_at;
        }
    
        @Temporal(javax.persistence.TemporalType.TIMESTAMP)
        public Date getUpdated_at() {
            return updated_at;
        }            @Override
    
        @Override
        public void setUpdated_at(Date updated_at) {
            this.updated_at = updated_at;
        }
        ...
    }
    

Примечания:

  • В этом примере ожидаются свойства created_at и updated_at. Для разных имен также должны быть адаптированы имена методов.
  • Тип должен быть org.hibernate.type.Type!

Ответ 7

import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp;

. , .

@CreationTimestamp private Дата создания;

@UpdateTimestamp частная Дата изменения;

Ответ 8

Недавно я столкнулся с той же проблемой, и JPA-аннотации @PrePersist и @PreUpdate не будут работать при использовании hibernate sessionFactory.

Простым способом, который работал у меня с Hibernate 5, является объявление поля как @Version, которое будет правильно обновлять timestamp/localDateTime объекта каждый раз, когда вы обновляете экземпляр базы данных.