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
@CreationTimestampy@UpdateTimestamp:fuente
TemporalType.DATEen el primer caso yTemporalType.TIMESTAMPen el segundo.@CreationTimestampy@UpdateTimestampuno necesita algo@Column(..., columnDefinition = "timestamp default current_timestamp"), o usa@PrePersisty@PreUpdate(este último también garantiza que los clientes no puedan establecer un valor diferente).nullable=falsedel@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
TIMESTAMPtipo 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,DATETIMEno 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_zonepropiedad 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
LocalDateTimeojava.sql.Timestamppara asignar una propiedad de entidad de marca de tiempo, entonces no necesita usarla@Temporalya 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@Temporalanotació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_oncolumna, podría usar unaDEFAULTrestricción DDL, como:Para la
updated_oncolumna, 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_bycreated_onupdated_byupdated_onUsando Hibernate
@CreationTimestampy@UpdateTimestampanotacionesHibernate ofrece las anotaciones
@CreationTimestampy@UpdateTimestampque se pueden utilizar para asignar las columnascreated_onyupdated_on.Puede usar
@MappedSuperclasspara 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
createdOnyupdateOnpropiedades se establecen por el Hibernate-específico@CreationTimestampy@UpdateTimestampanotaciones, elcreatedByyupdatedByrequieren el registro de una devolución de llamada de aplicación, como se ilustra por la siguiente solución JPA.Usando JPA
@EntityListenersPuede encapsular las propiedades de auditoría en un Embeddable:
Y cree un
AuditListenerpara establecer las propiedades de auditoría:Para registrar el
AuditListener, puede usar la@EntityListenersanotación JPA:fuente
datetimemá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.timestsmptipo 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.timestampsiempre está en UTC. MySQL convierte losTIMESTAMPvalores 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_TIMESTAMPni disparadores. No quiero magia en el DB.Java tipo de campo:
long. La marca de tiempo de Unix es compatible con varias bibliotecas,longno 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 primitivaslongs como lasjava.lang.Longs son inmutables, efectivamente pasadas por valor, a diferencia dejava.util.Dates; 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
@Temporalhará el trabajo; No estoy seguro de si podría usar@Versionpara este propósito.@PrePersisty@PreUpdateson 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;comoprotecteden 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.Calenderno es para marcas de tiempo .java.util.Datees, 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
@PrePersisty@PrePersistno 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