Desenvolvimento

21 fev, 2017

JPA/Hibernate – Como funciona a anotação @version?

Publicidade

Recentemente, um colega de trabalho me perguntou qual era função da anotação @version presente nas entidades do projeto. Achei interessante essa pergunta e resolvi fazer da resposta o meu primeiro artigo.

Para responder essa de pergunta, temos que lembrar que todo banco de dados possui um controle de concorrência entre transações (se necessário, solicitem nos comentários que faço um artigo explicando a fundo sobre isso). Para esse texto, só precisamos conhecer o método de controle Optimistic concurrency control (controle de concorrência otimista).

Optimistic concurrency control (OCC)

Optimistic concurrency control ou controle de concorrência otimista é um método aplicado nas transações de bancos de dados relacionais. Nesse método, se acredita que a probabilidade de duas transações utilizarem o mesmo objeto é minima, logo, nada é verificado enquanto a transação está sendo executada e isso faz com que o custo dela diminua.

Normalmente, essa técnica funciona bem em Frameworks ORM porque eles conseguem escalonar as transações para que ocorra pouca ou nenhuma interferência. Mas no caso de ocorrer uma interferência, o que acontece? Simples: alguma transação escolhida pelo Hibernate vai ser abortada e terá que ser recomeçada pelo cliente.

A anotação @version

Agora que sabemos superficialmente o que é um controle de concorrência e o método OCC, fica mais fácil compreender a importância do @version em nossas entidades. Tenha em mente que, ao adicionar um atributo com essa anotação, não precisamos nos preocupar em alterar seu valor porque o Hibernate fica encarregado dessa função.

Para usarmos o método de concorrência otimista só precisamos declarar um atributo com o nome version em nossa entidade. Normalmente, ele é do tipo numérico e também pode ser do tipo data, mas não recomendo essa prática e vou explicar o motivo no final deste artigo. Abaixo temos um exemplo da classe Consulta.

@Entity
@Table(name = "CONSULTAS", schema = "tableless")
public class Consultas{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID_CONSULTA")
private Integer numero;

@Temporal(TemporalType.TIMESTAMP)
@Column(name = "DATA_CONSULTA")
private Date dataConsulta;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "PACIENTE_CODIGO")
private Pacientes paciente;

@Version
private Integer version;

//getter e setter
}

Com esse mapeamento, o Hibernate vai usar automaticamente o número da versão para verificar se o objeto utilizado na transição foi atualizado desde a última vez em que ele foi requisitado. Pelo código SQL a baixo, podemos ver a presença do atributo version tanto no trecho do SET, como na cláusula WHERE. Isso acontece porque o Hibernate vai buscar o objeto no banco de dados usando também o número da versão e vai incrementar esse número ao mesmo tempo.

UPDATE CONSULTAS 
SET DATA_CONSULTA = ?, PACIENTE_CODIGO = ?, version = ?
WHERE ID_CONSULTA = ? 
AND version = ?

@version com Data

Usar um tipo data no atributo version é uma prática que não recomendo, porque no banco de dados a coluna será do tipo TIMESTAMP e isso pode acabar permitindo que duas ou mais transações possam ser executadas com o mesmo objeto ao mesmo tempo, dependendo da precisão do fuso horário configurada no banco de dados. Caso essa situação acabe acontecendo, vamos acabar tendo uma violação de normalização. Algumas pessoas preferem utilizar o tipo data para conseguirem visualizar “quando” aquele objeto foi alterado pela última vez, caso essa seja sua necessidade, recomendo que crie um novo atributo em sua classe ao invés de utilizar a anotação @version como data.

Por fim, espero que esse texto te ajude a trabalhar com controle de concorrência no Hibernate.

Dúvidas, sugestões, correções, elogios nos comentários ou no meu GitHub.