Para uma determinada entidade do Hibernate, temos o requisito de armazenar seu horário de criação e a última vez em que foi atualizado. Como você projetaria isso?
Quais tipos de dados você usaria no banco de dados (assumindo o MySQL, possivelmente em um fuso horário diferente da JVM)? Os tipos de dados reconhecem o fuso horário?
Quais são os tipos de dados que você usaria em Java (
Date
,Calendar
,long
, ...)?A quem você seria responsável por definir os registros de data e hora - o banco de dados, a estrutura ORM (Hibernate) ou o programador de aplicativos?
Quais anotações você usaria para o mapeamento (por exemplo
@Temporal
)?
Não estou apenas procurando uma solução funcional, mas uma solução segura e bem projetada.
Você pode apenas usar
@CreationTimestamp
e@UpdateTimestamp
:fonte
TemporalType.DATE
no primeiro caso eTemporalType.TIMESTAMP
no segundo.@CreationTimestamp
e@UpdateTimestamp
um precisa de algum@Column(..., columnDefinition = "timestamp default current_timestamp")
, ou usa@PrePersist
e@PreUpdate
(este último também garante que os clientes não possam definir um valor diferente).nullable=false
de@Column(name = "create_date" , nullable=false)
trabalhadoTomando os recursos deste post juntamente com as informações tiradas à esquerda e à direita de diferentes fontes, eu vim com esta solução elegante, crie a seguinte classe abstrata
e faça com que todas as suas entidades o estendam, por exemplo:
fonte
not-null property references a null or transient value: package.path.ClassName.created
@Column(name = "updated", nullable = false, insertable = false)
fazê-lo funcionar. Interessante que esta resposta tem tantos upvotes ..1. Quais tipos de colunas do banco de dados você deve usar
Sua primeira pergunta foi:
No MySQL, o
TIMESTAMP
tipo de coluna muda do fuso horário local do driver JDBC para o fuso horário do banco de dados, mas ele pode armazenar apenas carimbos de data / hora até'2038-01-19 03:14:07.999999
, portanto, não é a melhor opção para o futuro.Portanto, use melhor
DATETIME
, que não tem essa limitação do limite superior. No entanto,DATETIME
não está ciente do fuso horário. Portanto, por esse motivo, é melhor usar o UTC no lado do banco de dados e usar ahibernate.jdbc.time_zone
propriedade Hibernate.2. Que tipo de propriedade da entidade você deve usar
Sua segunda pergunta foi:
No lado do Java, você pode usar o Java 8
LocalDateTime
. Você também pode usar o legadoDate
, mas os tipos Data / Hora do Java 8 são melhores, pois são imutáveis e não fazem um fuso horário mudar para o fuso horário local ao registrá-los.Agora, também podemos responder a esta pergunta:
Se você estiver usando a propriedade
LocalDateTime
oujava.sql.Timestamp
para mapear uma entidade de carimbo de data / hora, não precisará usá-lo,@Temporal
pois o HIbernate já sabe que essa propriedade deve ser salva como um carimbo de data / hora do JDBC.Somente se você estiver usando
java.util.Date
, precisará especificar a@Temporal
anotação, assim:Mas é muito melhor se você mapeá-lo assim:
Como gerar os valores da coluna de auditoria
Sua terceira pergunta foi:
Existem várias maneiras de atingir esse objetivo. Você pode permitir que o banco de dados faça isso.
Para a
create_on
coluna, você pode usar umaDEFAULT
restrição de DDL, como:Para a
updated_on
coluna, você pode usar um acionador de banco de dados para definir o valor da colunaCURRENT_TIMESTAMP()
toda vez que uma determinada linha for modificada.Ou use JPA ou Hibernate para defini-los.
Vamos supor que você tenha as seguintes tabelas de banco de dados:
E, cada tabela possui colunas como:
created_by
created_on
updated_by
updated_on
Usando o Hibernate
@CreationTimestamp
e@UpdateTimestamp
anotaçõesO Hibernate oferece as anotações
@CreationTimestamp
e@UpdateTimestamp
que podem ser usadas para mapear as colunascreated_on
eupdated_on
.Você pode usar
@MappedSuperclass
para definir uma classe base que será estendida por todas as entidades:E, todas as entidades estenderão o
BaseEntity
, assim:No entanto, mesmo se os
createdOn
eupdateOn
propriedades são definidas pelo específicos de hibernação@CreationTimestamp
e@UpdateTimestamp
anotações, acreatedBy
eupdatedBy
requerem registrar um retorno de chamada aplicação, tal como ilustrado pelo seguinte solução APP.Usando JPA
@EntityListeners
Você pode encapsular as propriedades de auditoria em um Incorporável:
E, crie um
AuditListener
para definir as propriedades de auditoria:Para registrar
AuditListener
, você pode usar a@EntityListeners
anotação JPA:fonte
datetime
maistimestamp
. Você deseja que seu banco de dados saiba o fuso horário de seus carimbos de data e hora. Isso evita erros de conversão de fuso horário.timestsmp
tipo não armazena informações de fuso horário. Ele apenas conversa do aplicativo TZ para o DB TZ. Na realidade, você deseja armazenar a TZ do cliente separadamente e fazer a conversa no aplicativo antes de renderizar a interface do usuário.timestamp
está sempre no UTC. O MySQL converteTIMESTAMP
valores do fuso horário atual em UTC para armazenamento e volta do UTC para o fuso horário atual para recuperação. Documentação do MySQL: Os tipos DATE, DATETIME e TIMESTAMPVocê também pode usar um interceptador para definir os valores
Crie uma interface chamada TimeStamped que suas entidades implementam
Defina o interceptador
E registre-o na fábrica de sessões
fonte
Com a solução de Olivier, durante as instruções de atualização, você pode encontrar:
Para resolver isso, adicione updatable = false à anotação @Column do atributo "created":
fonte
@Version
. Quando uma entidade é definida, duas chamadas são feitas, uma é para salvar e outra para atualizar. Eu estava enfrentando o mesmo problema por causa disso. Depois que adicionei@Column(updatable = false)
, resolvi meu problema.Obrigado a todos que ajudaram. Depois de fazer uma pesquisa (eu sou o cara que fez a pergunta), eis o que achei mais interessante:
Tipo de coluna do banco de dados: o número independente de fuso horário de milissegundos desde 1970 representado
decimal(20)
porque 2 ^ 64 tem 20 dígitos e o espaço em disco é barato; sejamos diretos. Além disso, não usareiDEFAULT CURRENT_TIMESTAMP
nem gatilhos. Não quero mágica no DB.Java tipo de campo:
long
. O registro de data e hora do Unix é bem suportado em várias bibliotecas,long
não apresenta problemas no Y2038, a aritmética do registro de data e hora é rápida e fácil (principalmente operador<
e operador+
, assumindo que não há dias / meses / anos envolvidos nos cálculos). E, mais importante, ambos os primitivoslong
s ejava.lang.Long
s são imutáveis -effectively passados por valor, ao contrário dejava.util.Date
s; Eu ficaria realmente chateado ao encontrar algo parecidofoo.getLastUpdate().setTime(System.currentTimeMillis())
ao depurar o código de outra pessoa.A estrutura ORM deve ser responsável por preencher os dados automaticamente.
Ainda não testei isso, mas apenas olhando os documentos que suponho que
@Temporal
farão o trabalho; não tenho certeza se devo usar@Version
para esse fim.@PrePersist
e@PreUpdate
são boas alternativas para controlar isso manualmente. Adicionar isso ao supertipo de camada (classe base comum) para todas as entidades é uma idéia interessante, desde que você realmente queira o carimbo de data e hora para todas as suas entidades.fonte
Caso você esteja usando a API da sessão, os retornos de chamada PrePersist e PreUpdate não funcionarão de acordo com esta resposta .
Estou usando o método persist () do Hibernate Session no meu código, para que a única maneira de fazer esse trabalho fosse com o código abaixo e seguindo esta postagem do blog (também postada na resposta ).
fonte
updated.clone()
de outro modo outros componentes pode manipular o estado interno (data)Agora também existem anotações @CreatedDate e @LastModifiedDate.
=> https://programmingmitra.blogspot.fr/2017/02/automatic-spring-data-jpa-auditing-saving-CreatedBy-createddate-lastmodifiedby-lastmodifieddate-automatically.html
(Quadro de primavera)
fonte
O código a seguir funcionou para mim.
fonte
protected Integer id;
comoprotected
na classe pai em geral, porque eu não poderia usá-lo nos meus casos de teste como.getId()
Uma boa abordagem é ter uma classe base comum para todas as suas entidades. Nesta classe base, você pode ter sua propriedade id se for comumente nomeada em todas as suas entidades (um design comum), suas propriedades de criação e data da última atualização.
Para a data de criação, você simplesmente mantém uma propriedade java.util.Date . Certifique-se de sempre inicializá-lo com a nova Data () .
Para o último campo de atualização, você pode usar uma propriedade Timestamp, é necessário mapeá-la com @Version. Com esta anotação, a propriedade será atualizada automaticamente pelo Hibernate. Cuidado que o Hibernate também aplicará bloqueio otimista (é uma coisa boa).
fonte
Apenas para reforçar:
java.util.Calender
não é para registros de data e hora .java.util.Date
por um momento, agnóstico de coisas regionais, como fusos horários. A maioria dos bancos de dados armazena as coisas dessa maneira (mesmo que pareçam não; isso geralmente é uma configuração de fuso horário no software cliente; os dados são bons)fonte
Como tipo de dados em JAVA, é altamente recomendável usar o java.util.Date. Eu tive problemas bastante desagradáveis de fuso horário ao usar o Calendário. Veja este tópico .
Para definir os registros de data e hora, eu recomendaria usar uma abordagem de AOP ou você poderia simplesmente usar Triggers na tabela (na verdade, essa é a única coisa que considero aceitável o uso de triggers).
fonte
Você pode considerar armazenar a hora como um DateTime e no UTC. Eu normalmente uso DateTime em vez de Timestamp devido ao fato de o MySql converter datas para UTC e voltar para a hora local ao armazenar e recuperar os dados. Prefiro manter esse tipo de lógica em um só lugar (camada de negócios). Tenho certeza de que há outras situações em que o uso do Timestamp é preferível.
fonte
Tivemos uma situação semelhante. Estávamos usando o Mysql 5.7.
Isso funcionou para nós.
fonte
@PrePersist
e@PrePersist
não cubra esse caso.Se estivermos usando @Transactional em nossos métodos, @CreationTimestamp e @UpdateTimestamp salvarão o valor no DB, mas retornarão nulos após o uso de save (...).
Nessa situação, o uso de saveAndFlush (...) fez o truque
fonte
Eu acho que é mais limpo não fazer isso no código Java, você pode simplesmente definir o valor padrão da coluna na definição da tabela MySql.
fonte