1. Quais tipos de colunas do banco de dados você deve usar
Sua primeira pergunta foi:
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?
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 a hibernate.jdbc.time_zone
propriedade Hibernate.
Para mais detalhes sobre a hibernate.jdbc.time_zone
configuração, consulte este artigo .
2. Que tipo de propriedade da entidade você deve usar
Sua segunda pergunta foi:
Quais tipos de dados você usaria em Java (data, calendário, longo, ...)?
No lado do Java, você pode usar o Java 8 LocalDateTime
. Você também pode usar o legado Date
, 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.
Para obter mais detalhes sobre os tipos de data / hora do Java 8 suportados pelo Hibernate, consulte este artigo .
Agora, também podemos responder a esta pergunta:
Quais anotações você usaria para o mapeamento (por exemplo @Temporal
)?
Se você estiver usando a propriedade LocalDateTime
ou java.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:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_on")
private Date createdOn;
Mas é muito melhor se você mapeá-lo assim:
@Column(name = "created_on")
private LocalDateTime createdOn;
Como gerar os valores da coluna de auditoria
Sua terceira pergunta foi:
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)?
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 uma DEFAULT
restrição de DDL, como:
ALTER TABLE post
ADD CONSTRAINT created_on_default
DEFAULT CURRENT_TIMESTAMP() FOR created_on;
Para a updated_on
coluna, você pode usar um acionador de banco de dados para definir o valor da coluna CURRENT_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ções
O Hibernate oferece as anotações @CreationTimestamp
e @UpdateTimestamp
que podem ser usadas para mapear as colunas created_on
e updated_on
.
Você pode usar @MappedSuperclass
para definir uma classe base que será estendida por todas as 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
}
E, todas as entidades estenderão o BaseEntity
, assim:
@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 mais detalhes sobre o uso @MappedSuperclass
, confira este artigo .
No entanto, mesmo se os createdOn
e updateOn
propriedades são definidas pelo específicos de hibernação @CreationTimestamp
e @UpdateTimestamp
anotações, a createdBy
e updatedBy
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:
@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
}
E, crie um AuditListener
para definir as propriedades de auditoria:
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 AuditListener
, você pode usar a @EntityListeners
anotação 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 obter mais detalhes sobre como implementar propriedades de auditoria com a JPA @EntityListener
, consulte este artigo .