Isto tem mais cara de artigo do que pergunta, deveria ter postado num blog…
Em bancos legados muitas vezes nos deparamos com situações diversas na parte de geração de chaves primárias (PK), uma situação que tive em um banco onde trabalho era que a chave primária se tratava de uma String com com código dentro dela, e não havia uma trigger no banco de dados para criar uma chave a cada vez que inserisse um novo item. Qual a menira mais elegante para utilizar java para gerar essa chave primaria ?
Primeiro temos o trecho da entidade:
@Entity
@Table(name = "cadacess")
public class Cadacess implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column
@GenericGenerator(name = "geradorID", strategy = "br.com.teste.dao.util.GeraId", parameters = {@Parameter(name = "stdkey", value = "00000")})
@GeneratedValue(generator = "geradorID", strategy = GenerationType.IDENTITY)
private String codacessor;
@Column
private String nomeacess;
Se houver algo muito estranho nesse código podem me avisar, mas o foco nesse trecho é a parte de @GenericGenerator e @GeneratedValue , esses caras vão permitir que se gere a chave de forma automatica utilizando ferramentas do própio Hibernate, note que chego a utilizar parametro no gerador de PK, esse parametro é passado para meu gerador de chaves.
Com esse código estou avisando ao Hibernate qual a Classe que vai gerar minha chave primária com seus devidos parametros, há outros geradores de chave que o proprio hibernate disponibiliza.
@Entity
@Table(name = "contrato")
public class Contrato implements Serializable {
@Id
@Basic(optional = false)
@GenericGenerator(name="generator", strategy="increment")
@GeneratedValue(generator="generator")
@Column(name = "numcontrat")
private Integer numcontrat;
@Column(name = "descricao")
private String descricao;
Este exemplo usa um gerador incremental que vem com o proprio hibernate, ele simplismente pega o último item da tabela e soma um, este gerador aceita qualquer tipo de campo que seja numeral (Double, Int, etc). Ajuda muito se não há como modificar o banco de dados, pode acontecer que o banco de dados se comunique com sistemas antigos, e esse sistema já gere a chave internamente, então se utiliza esse tipo de alternativa para fazer com que os sistemas se comuniquem.
Vamos ao código do Gerador de chaves citado:
package br.com.teste.dao.util;
import java.io.Serializable;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.id.Configurable;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.type.Type;
public class GeraId implements Configurable, IdentifierGenerator{
private String pK;
private String entity;
private String stdkey;
private static String STDKEYTAG = "stdkey";
public Serializable generate(SessionImplementor session, Object o) throws HibernateException {
System.out.println("Chave: " + pK + " Tabela: " + entity);
String sql = "SELECT trim (to_char(coalesce(max(int4(" + pK + ")),0)+1,'" + stdkey + "')) as " + pK + " FROM " + entity + " as tabela";
return (String) ((Session) session).createQuery(sql).uniqueResult();
}
public void configure(Type type, Properties params, Dialect d) throws HibernateException {
stdkey = params.getProperty(STDKEYTAG, "0000");
pK = params.getProperty(PersistentIdentifierGenerator.PK);
entity = params.getProperty(PersistentIdentifierGenerator.ENTITY_NAME);
}
}
Vamos aos comentários:
Importante citar que esse código é estremamente modular, feito de forma genérica para atender a necessidade para diversas tabelas, inicalmente temos as interfaces implementadas pelo objeto gerador de chaves:
public class GeraId implements Configurable, IdentifierGenerator{
Para a inteface Configurable temos o metodo:
public void configure(Type type, Properties params, Dialect d) throws HibernateException
Este metodo extraimos dados da entidade, como tabela, classe da entidade, dialeto utilizado e os parametros definidos na entidade, para o caso em questão, a chave gerada tem uma quantidade de zeros a esquerda do número para adequar a PK, então utiliza-se uma mascara com a quantidade de zeros utilizados no campo.
Pode ser que haja uma forma melhor para fazer, pegar o tamanho do campo dinamicamente, mas isso não veio ao caso. E outra, utilizei uma consulta sql do Postgres para fazer a consulta sql e gerar uma nova chave (um presente do pessoal do sistema legado em VB6).
Este metodo é executado quando o hibernate precisa utilizar a entidade, então guarda em memoria esse objeto com suas variaveis iniciadas para utilizar quando for precisar gerar a chave primaria
public void configure(Type type, Properties params, Dialect d) throws HibernateException {
stdkey = params.getProperty(STDKEYTAG, "0000");
pK = params.getProperty(PersistentIdentifierGenerator.PK);
entity = params.getProperty(PersistentIdentifierGenerator.ENTITY_NAME);
}
Essas variaveis são guardadas para serem utilizadas no momento de gerar a pK (dados imprescindiveis).
stdkey -> Utilizo uma mascara padrão “0000”, caso alguém tenha colocado o parametro citado, utiliza-se outra mascara
pK -> Nome do Campo da chave primaria
entity -> Entidade utilizada
Vamos agora a melhor parte, a geração da chave em si, temos a inteface IdentifierGenerator temos o metodo:
public Serializable generate(SessionImplementor session, Object o) throws HibernateException {
Esse metodo é chamado toda vez que insere um novo item no banco, o objeto de retorno [i]Serializable[/i] pode ser qualquer tipo de dado serializavel, objetos ou tipos primitivos do java (String, int, etc). Vou colocar depois um exemplo para resolver o problema dos @EmbeddedId para chaves compostas.
public Serializable generate(SessionImplementor session, Object o) throws HibernateException {
System.out.println("Chave: " + pK + " Tabela: " + entity);
String sql = "SELECT trim (to_char(coalesce(max(int4(" + pK + ")),0)+1,'" + stdkey + "')) as " + pK + " FROM " + entity + " as tabela";
return (String) ((Session) session).createQuery(sql).uniqueResult();
}
Basicamente reutilizo a session do Hibernate para fazer minha consulta (se alguém puder testar isso utilizando Persistence agradeço muito pois não sei se funciona da mesma forma), não vou entrar no mérito de explicar como essa consulta sql funciona, digamos apenas dou os parametros da coluna que o sql vai utilizar para converter o número para inteiro, incrementa 1, reconverte para string utilizando a mascara de Zeros definida, usando a tabela que estou citando, deve ter algo sobrando aí.
Faço um cast para session (Talvez tenha como fazer cast para EntityManager), para poder utilizar o propio objeto de conexão do banco para fazer minha consulta sql, retornando um único resultado. Esse retorno vai direto para a Chave primária no momento que o hibernate está "preparando" a entidade para ser persistida.
Coloquei este tópico porque não achei lugar nenhum na internet que explicasse claramente como criar metodos de geração de chaves.