1. Qué tipos de columna de base de datos debe usar
Tu primera pregunta fue:
¿Qué tipos de datos usaría en la base de datos (suponiendo MySQL, posiblemente en una zona horaria diferente a la JVM)? ¿Los tipos de datos serán conscientes de la zona horaria?
En MySQL, el TIMESTAMP
tipo de columna cambia de la zona horaria local del controlador JDBC a la zona horaria de la base de datos, pero solo puede almacenar marcas de tiempo hasta '2038-01-19 03:14:07.999999
, por lo que no es la mejor opción para el futuro.
Entonces, mejor uso DATETIME
, que no tiene esta limitación de límite superior. Sin embargo, DATETIME
no es consciente de la zona horaria. Entonces, por esta razón, es mejor usar UTC en el lado de la base de datos y usar la hibernate.jdbc.time_zone
propiedad Hibernate.
Para obtener más detalles sobre la hibernate.jdbc.time_zone
configuración, consulte este artículo .
2. Qué tipo de propiedad de entidad debe usar
Su segunda pregunta fue:
¿Qué tipos de datos usarías en Java (Fecha, Calendario, largo, ...)?
En el lado de Java, puede usar Java 8 LocalDateTime
. También puede usar el legado Date
, pero los tipos de fecha y hora de Java 8 son mejores ya que son inmutables y no hacen un cambio de zona horaria a la zona horaria local al iniciar sesión.
Para obtener más detalles sobre los tipos de fecha / hora de Java 8 compatibles con Hibernate, consulte este artículo .
Ahora, también podemos responder esta pregunta:
¿Qué anotaciones usarías para el mapeo (por ejemplo @Temporal
)?
Si está utilizando LocalDateTime
o java.sql.Timestamp
para asignar una propiedad de entidad de marca de tiempo, entonces no necesita usarla @Temporal
ya que HIbernate ya sabe que esta propiedad debe guardarse como una marca de tiempo JDBC.
Solo si está utilizando java.util.Date
, debe especificar la @Temporal
anotación, así:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;
Pero, es mucho mejor si lo mapea así:
@Column(name = "created_on")
private LocalDateTime createdOn;
Cómo generar los valores de la columna de auditoría
Su tercera pregunta fue:
¿A quién responsabilizaría para configurar las marcas de tiempo: la base de datos, el marco ORM (Hibernate) o el programador de aplicaciones?
¿Qué anotaciones usarías para el mapeo (por ejemplo, @Temporal)?
Hay muchas maneras de lograr este objetivo. Puede permitir que la base de datos haga eso.
Para la create_on
columna, podría usar una DEFAULT
restricción DDL, como:
ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
Para la updated_on
columna, puede usar un desencadenador de base de datos para establecer el valor de la columna CURRENT_TIMESTAMP()
cada vez que se modifica una fila determinada.
O use JPA o Hibernate para configurarlos.
Supongamos que tiene las siguientes tablas de base de datos:
Y, cada tabla tiene columnas como:
created_by
created_on
updated_by
updated_on
Usando Hibernate @CreationTimestamp
y @UpdateTimestamp
anotaciones
Hibernate ofrece las anotaciones @CreationTimestamp
y @UpdateTimestamp
que se pueden utilizar para asignar las columnas created_on
y updated_on
.
Puede usar @MappedSuperclass
para definir una clase base que será extendida por todas las entidades:
@MappedSuperclass
public class BaseEntity {
@Id
@GeneratedValue
private Long id;
@Column(name = "created_on")
@CreationTimestamp
private LocalDateTime createdOn;
@Column(name = "created_by")
private String createdBy;
@Column(name = "updated_on")
@UpdateTimestamp
private LocalDateTime updatedOn;
@Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
Y, todas las entidades extenderán el BaseEntity
, así:
@Entity(name = "Post")
@Table(name = "post")
public class Post extend BaseEntity {
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
@ManyToMany
@JoinTable(
name = "post_tag",
joinColumns = @JoinColumn(
name = "post_id"
),
inverseJoinColumns = @JoinColumn(
name = "tag_id"
)
)
private List<Tag> tags = new ArrayList<>();
//Getters and setters omitted for brevity
}
Para obtener más detalles sobre el uso @MappedSuperclass
, consulte este artículo .
Sin embargo, incluso si los createdOn
y updateOn
propiedades se establecen por el Hibernate-específico @CreationTimestamp
y @UpdateTimestamp
anotaciones, el createdBy
y updatedBy
requieren el registro de una devolución de llamada de aplicación, como se ilustra por la siguiente solución JPA.
Usando JPA @EntityListeners
Puede encapsular las propiedades de auditoría en un Embeddable:
@Embeddable
public class Audit {
@Column(name = "created_on")
private LocalDateTime createdOn;
@Column(name = "created_by")
private String createdBy;
@Column(name = "updated_on")
private LocalDateTime updatedOn;
@Column(name = "updated_by")
private String updatedBy;
//Getters and setters omitted for brevity
}
Y cree un AuditListener
para establecer las propiedades de auditoría:
public class AuditListener {
@PrePersist
public void setCreatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
if(audit == null) {
audit = new Audit();
auditable.setAudit(audit);
}
audit.setCreatedOn(LocalDateTime.now());
audit.setCreatedBy(LoggedUser.get());
}
@PreUpdate
public void setUpdatedOn(Auditable auditable) {
Audit audit = auditable.getAudit();
audit.setUpdatedOn(LocalDateTime.now());
audit.setUpdatedBy(LoggedUser.get());
}
}
Para registrar el AuditListener
, puede usar la @EntityListeners
anotación JPA:
@Entity(name = "Post")
@Table(name = "post")
@EntityListeners(AuditListener.class)
public class Post implements Auditable {
@Id
private Long id;
@Embedded
private Audit audit;
private String title;
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
@OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
@ManyToMany
@JoinTable(
name = "post_tag",
joinColumns = @JoinColumn(
name = "post_id"
),
inverseJoinColumns = @JoinColumn(
name = "tag_id"
)
)
private List<Tag> tags = new ArrayList<>();
//Getters and setters omitted for brevity
}
Para obtener más detalles sobre la implementación de propiedades de auditoría con el JPA @EntityListener
, consulte este artículo .