【问题标题】:How to autogenerate created or modified timestamp field?如何自动生成创建或修改的时间戳字段?
【发布时间】:2011-07-12 13:52:04
【问题描述】:

我的实体类:

@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。

我如何做到这一点?

【问题讨论】:

    标签: hibernate orm jpa


    【解决方案1】:

    在带有 JPA 的 4.3 Hibernate 中,可以直接在日期字段中使用“@CreationTimestamp”和“@UpdateTimestamp”

    CreationTimestamp java doc

    UpdateTimestamp java doc

    【讨论】:

    • 这两个字段都必须有 getter,否则会发生奇怪的事情。
    • "@CreationTimestamp" 和 "@UpdateTimestamp" 不支持 OffsetDateTime 等新类型
    【解决方案2】:

    您可以在创建实例时创建 new Date(),然后在实体更新时更新 updated 字段:

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

    不要为这些方法中的任何一个提供 setter,只提供 getter。

    【讨论】:

      【解决方案3】:

      您可以使用 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: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically了解更多详情。

      【讨论】:

      【解决方案4】:

      我们使用 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)
      

      【讨论】:

        【解决方案5】:
        import org.hibernate.annotations.CreationTimestamp;
        import org.hibernate.annotations.UpdateTimestamp;
        
        .
        .
        .
        
        @CreationTimestamp
        private Date created;
        
        @UpdateTimestamp
        private Date modified;
        

        【讨论】:

        • 我想在 PST 时区 中保存 'created''modified' 字段的详细信息。在我的应用程序中,我在 DAO 类中使用 org.joda.time.DateTime 类型。用于这 2 列的相应类型是 timestamp without time zone 。数据库是 PostgreSQL。让我知道如何实现这一目标?
        【解决方案6】:

        由于这是一个常见问题,并且通过搜索可以找到很多半生不熟的解决方案,让我介绍一下我的解决方案:

        1. 定义两个平凡的字段注解@CreatedDate@ModifiedDate
        2. 使用它们来注释您实体上的相应字段;
        3. 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 {}
        

        您可以将它们放在自己的文件中,也可以将它们放在某个现有的类中。我更喜欢前者,所以我可以为它们取一个更简洁的完全限定名称。

        这是实体侦听器类:

        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's 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's 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 目录下。

        【讨论】:

          【解决方案7】:

          由于在使用 Hibernate Session 时会忽略 @PrePersist 和 @PreUpdate,所以我使用拦截器做了一个相对简单的解决方案:

          1. 定义一个接口“Auditable”:

            public interface Auditable {
              public void setUpdated_at(Date date);
              public void setCreated_at(Date date);
            }
            
          2. 定义一个类“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;
                }
            }
            
          3. 在您打开新会话时指定拦截器(您可能会在实用程序类中使用它)

            sessionFactory.openSession(new AuditableInterceptor());
            // sessionFactory.openSession();
            
          4. 在您的实体中实现 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;
                }
                ...
            }
            

          注意事项:

          1. 此示例需要属性 created_at 和 updated_at。对于不同的名称,方法名称也必须进行调整。
          2. 类型必须是 org.hibernate.type.Type!

          【讨论】:

            【解决方案8】:

            最近我遇到了同样的问题,当使用 hibernate sessionFactory 时,JPA-Annotations @PrePersist@PreUpdate 将不起作用。

            对我来说使用 Hibernate 5 的一种简单方法是将字段声明为 @Version,这将在您每次更新数据库实例时正确更新实体的时间戳/localDateTime。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-11-03
              • 1970-01-01
              相关资源
              最近更新 更多