Para una determinada entidad de Hibernate, tenemos el requisito de almacenar su hora de creación y la última vez que se actualizó. ¿Cómo diseñarías esto?
¿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?
¿Qué tipos de datos usaría en Java (
Date
,Calendar
,long
, ...)?¿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
)?
No solo busco una solución de trabajo, sino una solución segura y bien diseñada.
Puedes usar
@CreationTimestamp
y@UpdateTimestamp
:fuente
TemporalType.DATE
en el primer caso yTemporalType.TIMESTAMP
en el segundo.@CreationTimestamp
y@UpdateTimestamp
uno necesita algo@Column(..., columnDefinition = "timestamp default current_timestamp")
, o usa@PrePersist
y@PreUpdate
(este último también garantiza que los clientes no puedan establecer un valor diferente).nullable=false
del@Column(name = "create_date" , nullable=false)
trabajoTomando los recursos en esta publicación junto con la información tomada de izquierda a derecha de diferentes fuentes, llegué con esta elegante solución, crea la siguiente clase abstracta
y haga que todas sus entidades lo extiendan, por ejemplo:
fuente
not-null property references a null or transient value: package.path.ClassName.created
@Column(name = "updated", nullable = false, insertable = false)
para que funcione. Es interesante que esta respuesta haya recibido tantos votos positivos ..1. Qué tipos de columna de base de datos debe usar
Tu primera pregunta fue:
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 lahibernate.jdbc.time_zone
propiedad Hibernate.2. Qué tipo de propiedad de entidad debe usar
Su segunda pregunta fue:
En el lado de Java, puede usar Java 8
LocalDateTime
. También puede usar el legadoDate
, 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.Ahora, también podemos responder esta pregunta:
Si está utilizando
LocalDateTime
ojava.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í:Pero, es mucho mejor si lo mapea así:
Cómo generar los valores de la columna de auditoría
Su tercera pregunta fue:
Hay muchas maneras de lograr este objetivo. Puede permitir que la base de datos haga eso.
Para la
create_on
columna, podría usar unaDEFAULT
restricción DDL, como:Para la
updated_on
columna, puede usar un desencadenador de base de datos para establecer el valor de la columnaCURRENT_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
anotacionesHibernate ofrece las anotaciones
@CreationTimestamp
y@UpdateTimestamp
que se pueden utilizar para asignar las columnascreated_on
yupdated_on
.Puede usar
@MappedSuperclass
para definir una clase base que será extendida por todas las entidades:Y, todas las entidades extenderán el
BaseEntity
, así:Sin embargo, incluso si los
createdOn
yupdateOn
propiedades se establecen por el Hibernate-específico@CreationTimestamp
y@UpdateTimestamp
anotaciones, elcreatedBy
yupdatedBy
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:
Y cree un
AuditListener
para establecer las propiedades de auditoría:Para registrar el
AuditListener
, puede usar la@EntityListeners
anotación JPA:fuente
datetime
mástimestamp
. Desea que su base de datos conozca la zona horaria de sus marcas de tiempo. Esto evita errores de conversión de zona horaria.timestsmp
tipo no almacena información de zona horaria. Simplemente hace una conversación de la aplicación TZ a DB TZ. En realidad, desea almacenar el TZ del cliente por separado y conversar en la aplicación antes de representar la interfaz de usuario.timestamp
siempre está en UTC. MySQL convierte losTIMESTAMP
valores de la zona horaria actual a UTC para el almacenamiento, y vuelve de UTC a la zona horaria actual para la recuperación. Documentación de MySQL: los tipos DATE, DATETIME y TIMESTAMPTambién puede usar un interceptor para establecer los valores
Cree una interfaz llamada TimeStamped que implementen sus entidades
Definir el interceptor.
Y regístralo en la fábrica de sesiones
fuente
Con la solución de Olivier, durante las declaraciones de actualización puede toparse con:
Para resolver esto, agregue Updatable = false a la anotación @Column del atributo "creado":
fuente
@Version
. Cuando se establece una entidad, se realizan dos llamadas, una para guardar y otra para actualizar. Estaba enfrentando el mismo problema debido a esto. Una vez que lo agregué@Column(updatable = false)
resolvió mi problema.Gracias a todos los que ayudaron. Después de investigar un poco (soy el tipo que hizo la pregunta), esto es lo que encontré que tiene más sentido:
Tipo de columna de la base de datos: el número de milisegundos agnósticos de zona horaria desde 1970 representado como
decimal(20)
porque 2 ^ 64 tiene 20 dígitos y el espacio en disco es económico; seamos directos. Además, no usaréDEFAULT CURRENT_TIMESTAMP
ni disparadores. No quiero magia en el DB.Java tipo de campo:
long
. La marca de tiempo de Unix es compatible con varias bibliotecas,long
no tiene problemas con Y2038, la aritmética de marca de tiempo es rápida y fácil (principalmente operador<
y operador+
, suponiendo que no haya días / meses / años involucrados en los cálculos). Y, lo que es más importante, tanto las primitivaslong
s como lasjava.lang.Long
s son inmutables, efectivamente pasadas por valor, a diferencia dejava.util.Date
s; Realmente me enojaría encontrar algo comofoo.getLastUpdate().setTime(System.currentTimeMillis())
cuando depuro el código de otra persona.El marco ORM debe ser responsable de completar los datos automáticamente.
Todavía no he probado esto, pero solo mirando los documentos supongo que
@Temporal
hará el trabajo; No estoy seguro de si podría usar@Version
para este propósito.@PrePersist
y@PreUpdate
son buenas alternativas para controlar eso manualmente. Agregar eso al supertipo de capa (clase base común) para todas las entidades, es una buena idea, siempre y cuando realmente desee la marca de tiempo para todas sus entidades.fuente
En caso de que esté utilizando la API de sesión, las devoluciones de llamada PrePersist y PreUpdate no funcionarán de acuerdo con esta respuesta .
Estoy usando el método persist () de Hibernate Session en mi código, por lo que la única forma de hacer que esto funcione es con el código a continuación y siguiendo esta publicación de blog (también publicada en la respuesta ).
fuente
updated.clone()
si no, otros componentes pueden manipular el estado interno (fecha)Ahora también hay anotaciones @CreatedDate y @LastModifiedDate.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Marco de primavera)
fuente
El siguiente código funcionó para mí.
fuente
protected Integer id;
comoprotected
en la clase para padres en general, porque no podría usarlo en mis casos de prueba como.getId()
Un buen enfoque es tener una clase base común para todas sus entidades. En esta clase base, puede tener su propiedad de identificación si se nombra comúnmente en todas sus entidades (un diseño común), sus propiedades de creación y fecha de última actualización.
Para la fecha de creación, simplemente mantenga una propiedad java.util.Date . Asegúrese de inicializarlo siempre con una nueva fecha () .
Para el último campo de actualización, puede usar una propiedad de marca de tiempo, debe asignarla con @Version. Con esta anotación, Hibernate actualizará automáticamente la propiedad. Tenga en cuenta que Hibernate también aplicará un bloqueo optimista (es algo bueno).
fuente
Solo para reforzar:
java.util.Calender
no es para marcas de tiempo .java.util.Date
es, por un momento, agnóstico de cosas regionales como las zonas horarias. La mayoría de las bases de datos almacenan cosas de esta manera (incluso si parecen no serlo; esto generalmente es una configuración de zona horaria en el software del cliente; los datos son buenos)fuente
Como tipo de datos en JAVA, recomiendo utilizar java.util.Date. Me encontré con problemas de zona horaria bastante desagradables al usar Calendar. Ver este hilo .
Para configurar las marcas de tiempo, recomendaría usar un enfoque AOP o simplemente podría usar Triggers en la tabla (en realidad, esto es lo único que considero aceptable el uso de disparadores).
fuente
Puede considerar almacenar la hora como DateTime y en UTC. Por lo general, uso DateTime en lugar de Timestamp debido al hecho de que MySql convierte las fechas a UTC y de nuevo a la hora local al almacenar y recuperar los datos. Prefiero mantener cualquiera de ese tipo de lógica en un solo lugar (capa empresarial). Sin embargo, estoy seguro de que hay otras situaciones en las que es preferible usar Timestamp.
fuente
Tuvimos una situación similar. Estábamos usando Mysql 5.7.
Esto funcionó para nosotros.
fuente
@PrePersist
y@PrePersist
no cubras tal caso.Si estamos usando @Transactional en nuestros métodos, @CreationTimestamp y @UpdateTimestamp guardarán el valor en DB pero volverán nulos después de usar save (...).
En esta situación, usar saveAndFlush (...) hizo el truco
fuente
Creo que es mejor no hacer esto en el código Java, simplemente puede establecer el valor predeterminado de la columna en la definición de la tabla MySql.
fuente