Uma visão geral dos identificadores no Hibernate
1. Introdução
Identificadores no Hibernate representam a chave primária de uma entidade. Isso implica que os valores são únicos para que possam identificar uma entidade específica, que não sejam nulos e que não sejam modificados.
O Hibernate fornece algumas maneiras diferentes de definir identificadores. Neste artigo, revisaremos cada método de mapeamento de IDs de entidade usando a biblioteca.
2. Identificadores Simples
A maneira mais direta de definir um identificador é usando a anotação@Id.
IDs simples são mapeados usando@Id para uma única propriedade de um destes tipos: tipos de wrapper primitivos e primitivos Java,String, Date, BigDecimal, BigInteger.
Vamos ver um exemplo rápido de definição de uma entidade com uma chave primária do tipolong:
@Entity
public class Student {
@Id
private long studentId;
// standard constructor, getters, setters
}
3. Identificadores Gerados
Se quisermos que o valor da chave primária seja gerado automaticamente para nós,we can add the @GeneratedValue annotation.
Isso pode usar 4 tipos de geração: AUTO, IDENTIDADE, SEQUÊNCIA, TABELA.
Se não especificarmos um valor explicitamente, o tipo de geração será AUTO.
3.1. Geração deAUTO
Se estivermos usando o tipo de geração padrão, o provedor de persistência determinará os valores com base no tipo do atributo de chave primária. Este tipo pode ser numérico ouUUID.
Para valores numéricos, a geração é baseada em uma sequência ou gerador de tabela, enquanto os valores deUUID usarão oUUIDGenerator.
Vejamos um exemplo de mapeamento de uma chave primária de entidade usando a estratégia de geração AUTO:
@Entity
public class Student {
@Id
@GeneratedValue
private long studentId;
// ...
}
Nesse caso, os valores da chave primária serão exclusivos no nível do banco de dados.
An interesting feature introduced in Hibernate 5 is the UUIDGenerator. Para usar isso, tudo o que precisamos fazer é declarar um id do tipoUUID com a anotação@GeneratedValue:
@Entity
public class Course {
@Id
@GeneratedValue
private UUID courseId;
// ...
}
O Hibernate irá gerar um ID no formato "8dd5f315-9788-4d00-87bb-10eed9eff566".
3.2. Geração deIDENTITY
Este tipo de geração depende doIdentityGenerator que espera valores gerados por uma colunaidentity no banco de dados, ou seja, são auto-incrementados.
Para usar este tipo de geração, precisamos apenas definir o parâmetrostrategy:
@Entity
public class Student {
@Id
@GeneratedValue (strategy = GenerationType.IDENTITY)
private long studentId;
// ...
}
Uma coisa a observar é que a geração de IDENTITY desativa as atualizações em lote.
3.3. Geração deSEQUENCE
Para usar um id baseado em sequência, o Hibernate fornece a classeSequenceStyleGenerator.
Este gerador usa sequências se forem suportadas por nosso banco de dados e muda para a geração de tabelas se não forem.
Para personalizar o nome da sequência, podemos usar a anotação@GenericGenerator comSequenceStyleGenerator strategy:
@Entity
public class User {
@Id
@GeneratedValue(generator = "sequence-generator")
@GenericGenerator(
name = "sequence-generator",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
@Parameter(name = "sequence_name", value = "user_sequence"),
@Parameter(name = "initial_value", value = "4"),
@Parameter(name = "increment_size", value = "1")
}
)
private long userId;
// ...
}
Neste exemplo, também definimos um valor inicial para a sequência, o que significa que a geração da chave primária começará em 4.
SEQUENCE é o tipo de geração recomendado pela documentação do Hibernate.
The generated values are unique per sequence. Se você não especificar um nome de sequência, o Hibernate irá reutilizar o mesmohibernate_sequence para diferentes tipos.
3.4. Geração de TABELA
OTableGenerator usa uma tabela de banco de dados subjacente que contém segmentos de valores de geração de identificadores.
Vamos personalizar o nome da tabela usando a anotação@TableGenerator:
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "table-generator")
@TableGenerator(name = "table-generator",
table = "dep_ids",
pkColumnName = "seq_id",
valueColumnName = "seq_value")
private long depId;
// ...
}
Neste exemplo, podemos ver que outros atributos, comopkColumnNameevalueColumnName, também podem ser personalizados.
A desvantagem desse método é que ele não é dimensionado bem e pode afetar negativamente o desempenho.
Para resumir, esses quatro tipos de geração resultarão na geração de valores semelhantes, mas usam mecanismos de banco de dados diferentes.
3.5. Gerador Personalizado
Se não quisermos usar nenhuma das estratégias out-of-the-box,we can define our custom generator by implementing the IdentifierGenerator interface.
Vamos criar um gerador que constrói identificadores contendo um prefixoString e um número:
public class MyGenerator
implements IdentifierGenerator, Configurable {
private String prefix;
@Override
public Serializable generate(
SharedSessionContractImplementor session, Object obj)
throws HibernateException {
String query = String.format("select %s from %s",
session.getEntityPersister(obj.getClass().getName(), obj)
.getIdentifierPropertyName(),
obj.getClass().getSimpleName());
Stream ids = session.createQuery(query).stream();
Long max = ids.map(o -> o.replace(prefix + "-", ""))
.mapToLong(Long::parseLong)
.max()
.orElse(0L);
return prefix + "-" + (max + 1);
}
@Override
public void configure(Type type, Properties properties,
ServiceRegistry serviceRegistry) throws MappingException {
prefix = properties.getProperty("prefix");
}
}
Neste exemplo,we override the generate() method from the IdentifierGenerator interfacee primeiro encontre o maior número das chaves primárias existentes da formaprefix-XX.
Em seguida, adicionamos 1 ao número máximo encontrado e acrescentamos a propriedadeprefix para obter o valor de id recém-gerado.
Nossa classe também implementa a interfaceConfigurable, para que possamos definir o valor da propriedadeprefix no métodoconfigure().
A seguir, vamos adicionar este gerador personalizado a uma entidade. Para isso,we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:
@Entity
public class Product {
@Id
@GeneratedValue(generator = "prod-generator")
@GenericGenerator(name = "prod-generator",
parameters = @Parameter(name = "prefix", value = "prod"),
strategy = "com.example.hibernate.pojo.generator.MyGenerator")
private String prodId;
// ...
}
Além disso, observe que definimos o parâmetro prefix para “prod”.
Vamos ver um rápido teste JUnit para uma compreensão mais clara dos valores de id gerados:
@Test
public void whenSaveCustomGeneratedId_thenOk() {
Product product = new Product();
session.save(product);
Product product2 = new Product();
session.save(product2);
assertThat(product2.getProdId()).isEqualTo("prod-2");
}
Aqui, o primeiro valor gerado usando o prefixo "prod" foi "prod-1", seguido por "prod-2".
4. Identificadores Compostos
Além dos identificadores simples que vimos até agora, o Hibernate também nos permite definir identificadores compostos.
Um ID composto é representado por uma classe de chave primária com um ou mais atributos persistentes.
A classe de chave primária deve cumprir várias condições:
-
deve ser definido usando anotações@EmbeddedId ou@IdClass
-
deve ser público, serializável e ter um construtor público sem argumentos
-
deve implementar os métodosequals()ehashCode()
Os atributos da classe podem ser básicos, compostos ou ManyToOne, evitando coleções e atributosOneToOne.
4.1. @EmbeddedId
Para definir um id usando@EmbeddedId, primeiro precisamos de uma classe de chave primária anotada com@Embeddable:
@Embeddable
public class OrderEntryPK implements Serializable {
private long orderId;
private long productId;
// standard constructor, getters, setters
// equals() and hashCode()
}
A seguir, podemos adicionar um id do tipoOrderEntryPK a uma entidade usando @EmbeddedId:
@Entity
public class OrderEntry {
@EmbeddedId
private OrderEntryPK entryId;
// ...
}
Vejamos como podemos usar este tipo de ID composto para definir a chave primária de uma entidade:
@Test
public void whenSaveCompositeIdEntity_thenOk() {
OrderEntryPK entryPK = new OrderEntryPK();
entryPK.setOrderId(1L);
entryPK.setProductId(30L);
OrderEntry entry = new OrderEntry();
entry.setEntryId(entryPK);
session.save(entry);
assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
}
Aqui, o objetoOrderEntry tem um ID primárioOrderEntryPK formado por dois atributos:orderIdeproductId.
4.2. @IdClass
A anotação@IdClass é semelhante a@EmbeddedId,, exceto que os atributos são definidos na classe de entidade principal usando@Id para cada um.
A classe de chave primária terá a mesma aparência de antes.
Vamos reescrever o exemploOrderEntry com um@IdClass:
@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
@Id
private long orderId;
@Id
private long productId;
// ...
}
Então podemos definir os valores de id diretamente no objetoOrderEntry:
@Test
public void whenSaveIdClassEntity_thenOk() {
OrderEntry entry = new OrderEntry();
entry.setOrderId(1L);
entry.setProductId(30L);
session.save(entry);
assertThat(entry.getOrderId()).isEqualTo(1L);
}
Observe que para ambos os tipos de IDs compostos, a classe de chave primária também pode conter atributos@ManyToOne.
O Hibernate também permite definir chaves primárias compostas de associações@ManyToOne combinadas com a anotação@Id. Nesse caso, a classe de entidade também deve atender às condições de uma classe de chave primária.
A desvantagem desse método é que não há separação entre o objeto de entidade e o identificador.
5. Identificadores Derivados
Os identificadores derivados são obtidos da associação de uma entidade usando a anotação@MapsId.
Primeiro, vamos criar uma entidadeUserProfile que deriva seu id de uma associação um-para-um com a entidadeUser:
@Entity
public class UserProfile {
@Id
private long profileId;
@OneToOne
@MapsId
private User user;
// ...
}
A seguir, vamos verificar se uma instânciaUserProfile tem o mesmo id que sua instânciaUser associada:
@Test
public void whenSaveDerivedIdEntity_thenOk() {
User user = new User();
session.save(user);
UserProfile profile = new UserProfile();
profile.setUser(user);
session.save(profile);
assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
}
6. Conclusão
Neste artigo, vimos as várias maneiras de definir identificadores no Hibernate.
O código-fonte completo dos exemplos pode ser encontradoover on GitHub.