Estou trabalhando com o JPA (implementação Hibernate) há algum tempo e, toda vez que preciso criar entidades, me deparei com problemas como AccessType, propriedades imutáveis, equals / hashCode, ....
Por isso, decidi tentar descobrir as melhores práticas gerais para cada problema e anotá-las para uso pessoal.
No entanto, eu não me importaria que alguém comentasse ou me dissesse onde estou errado.
Classe de entidade
implementar Serializable
Motivo: a especificação diz que você precisa, mas alguns provedores de JPA não impõem isso. O hibernar como provedor de JPA não impõe isso, mas pode falhar em algum lugar profundo no estômago com ClassCastException, se Serializable não tiver sido implementado.
Construtores
crie um construtor com todos os campos obrigatórios da entidade
Razão: Um construtor deve sempre deixar a instância criada em um estado sadio.
além deste construtor: tenha um construtor padrão privado do pacote
Razão: O construtor padrão é necessário para o Hibernate inicializar a entidade; private é permitido, mas a visibilidade do pacote privado (ou público) é necessária para a geração do proxy de tempo de execução e recuperação eficiente de dados sem a instrumentação de bytecode.
Campos / Propriedades
Use o acesso ao campo em geral e acesso à propriedade quando necessário
Razão: esta é provavelmente a questão mais discutível, pois não há argumentos claros e convincentes para um ou outro (acesso à propriedade versus acesso ao campo); no entanto, o acesso ao campo parece ser o favorito geral por causa do código mais claro, melhor encapsulamento e não há necessidade de criar setters para campos imutáveis
Omita setters para campos imutáveis (não é necessário para o campo de tipo de acesso)
- as propriedades podem ser privadas
Motivo: Uma vez ouvi dizer que protegido é melhor para o desempenho (Hibernate), mas tudo o que posso encontrar na web é: O Hibernate pode acessar métodos de acessador públicos, privados e protegidos, bem como campos públicos, privados e protegidos diretamente . A escolha é sua e você pode combiná-la para se adequar ao design do aplicativo.
Equals / hashCode
- Nunca use um ID gerado se esse ID estiver definido apenas ao persistir na entidade
- Por preferência: use valores imutáveis para formar uma Chave de Negócios exclusiva e use-a para testar a igualdade
- se uma Chave Comercial exclusiva não estiver disponível, use um UUID não transitório, criado quando a entidade é inicializada; Veja este ótimo artigo para obter mais informações.
- nunca se refira a entidades relacionadas (ManyToOne); se essa entidade (como uma entidade pai) precisar fazer parte da Chave de negócios, compare apenas os IDs. Chamar getId () em um proxy não acionará o carregamento da entidade, desde que você esteja usando o tipo de acesso à propriedade .
Entidade de exemplo
@Entity
@Table(name = "ROOM")
public class Room implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
@Column(name = "room_id")
private Integer id;
@Column(name = "number")
private String number; //immutable
@Column(name = "capacity")
private Integer capacity;
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "building_id")
private Building building; //immutable
Room() {
// default constructor
}
public Room(Building building, String number) {
// constructor with required field
notNull(building, "Method called with null parameter (application)");
notNull(number, "Method called with null parameter (name)");
this.building = building;
this.number = number;
}
@Override
public boolean equals(final Object otherObj) {
if ((otherObj == null) || !(otherObj instanceof Room)) {
return false;
}
// a room can be uniquely identified by it's number and the building it belongs to; normally I would use a UUID in any case but this is just to illustrate the usage of getId()
final Room other = (Room) otherObj;
return new EqualsBuilder().append(getNumber(), other.getNumber())
.append(getBuilding().getId(), other.getBuilding().getId())
.isEquals();
//this assumes that Building.id is annotated with @Access(value = AccessType.PROPERTY)
}
public Building getBuilding() {
return building;
}
public Integer getId() {
return id;
}
public String getNumber() {
return number;
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getNumber()).append(getBuilding().getId()).toHashCode();
}
public void setCapacity(Integer capacity) {
this.capacity = capacity;
}
//no setters for number, building nor id
}
Outras sugestões para adicionar a esta lista são mais que bem-vindas ...
ATUALIZAR
Desde a leitura deste artigo , adaptei minha maneira de implementar o eq / hC:
- se uma chave comercial simples imutável estiver disponível: use esse
- em todos os outros casos: use um uuid
final
(a julgar pela sua omissão de levantadores, acho que você também).notNull
vem?Respostas:
Tentarei responder a vários pontos-chave: isso é da longa experiência em Hibernate / persistência, incluindo vários aplicativos principais.
Classe de entidade: implementar Serializable?
Chaves precisa implementar Serializable. O material que será enviado no HttpSession, ou será enviado por RPC / Java EE, precisará implementar o Serializable. Outras coisas: nem tanto. Gaste seu tempo com o que é importante.
Construtores: crie um construtor com todos os campos obrigatórios da entidade?
Os construtores da lógica do aplicativo devem ter apenas alguns campos críticos de "chave estrangeira" ou "tipo / tipo" que sempre serão conhecidos ao criar a entidade. O resto deve ser definido chamando os métodos setter - é para isso que servem.
Evite colocar muitos campos em construtores. Os construtores devem ser convenientes e dar sanidade básica ao objeto. Nome, tipo e / ou pais geralmente são úteis.
OTOH, se as regras de aplicação (hoje) exigirem que um Cliente tenha um Endereço, deixe isso para um levantador. Esse é um exemplo de uma "regra fraca". Talvez na próxima semana, você queira criar um objeto Customer antes de ir para a tela Enter Details? Não se engane, deixe a possibilidade de dados desconhecidos, incompletos ou "parcialmente inseridos".
Construtores: também, empacote o construtor padrão privado?
Sim, mas use 'protected' em vez de package private. Subclassificar coisas é uma verdadeira dor quando os internos necessários não são visíveis.
Campos / Propriedades
Use o acesso ao campo 'property' para o Hibernate e de fora da instância. Dentro da instância, use os campos diretamente. Razão: permite que a reflexão padrão, o método mais simples e básico do Hibernate, funcione.
Quanto aos campos 'imutáveis' para o aplicativo - o Hibernate ainda precisa ser capaz de carregá-los. Você pode tentar tornar esses métodos 'privados' e / ou colocar uma anotação neles, para impedir que o código do aplicativo faça acesso indesejado.
Nota: ao escrever uma função equals (), use getters para valores na instância 'other'! Caso contrário, você encontrará campos não inicializados / vazios em instâncias de proxy.
Protegido é melhor para o desempenho (Hibernate)?
Improvável.
Igual a / HashCode?
Isso é relevante para trabalhar com entidades, antes que elas sejam salvas - o que é um problema espinhoso. Hashing / comparação de valores imutáveis? Na maioria dos aplicativos de negócios, não há nenhum.
Um cliente pode mudar de endereço, mudar o nome de seus negócios, etc etc - não é comum, mas acontece. Também é possível fazer correções quando os dados não foram inseridos corretamente.
As poucas coisas que normalmente são mantidas imutáveis, são Parentalidade e talvez Tipo / Tipo - normalmente o usuário recria o registro, em vez de alterá-lo. Mas estes não identificam exclusivamente a entidade!
Portanto, longo e curto, os dados "imutáveis" alegados não são realmente. Os campos Chave Primária / ID são gerados com a finalidade precisa de fornecer estabilidade e imutabilidade garantidas.
Você precisa planejar e considerar as fases de trabalho de comparação, hash e processamento de solicitações quando A) trabalhar com "dados alterados / vinculados" da interface do usuário se comparar / hash em "campos alterados com pouca frequência" ou B) trabalhando com " dados não salvos ", se você comparar / hash no ID.
Igual a / HashCode - se uma Chave Comercial exclusiva não estiver disponível, use um UUID não transitório, criado quando a entidade é inicializada
Sim, esta é uma boa estratégia quando necessário. Esteja ciente de que os UUIDs não são gratuitos, porém em termos de desempenho - e o agrupamento complica as coisas.
Equals / HashCode - nunca se refere a entidades relacionadas
"Se a entidade relacionada (como uma entidade pai) precisar fazer parte da Chave de Negócios, adicione um campo não inserível e não atualizável para armazenar a identificação pai (com o mesmo nome que o ManytoOne JoinColumn) e use esse ID na verificação de igualdade "
Parece um bom conselho.
Espero que isto ajude!
fonte
A especificação JPA 2.0 afirma que:
A especificação não contém requisitos sobre a implementação de métodos equals e hashCode para entidades, apenas para classes de chave primária e chaves de mapa, tanto quanto eu sei.
fonte
Minha adição de 2 centavos às respostas aqui são:
Com referência ao acesso ao campo ou à propriedade (longe das considerações de desempenho), ambos são legitimamente acessados por meio de getters e setters, portanto, a lógica do meu modelo pode configurá-los / obtê-los da mesma maneira. A diferença ocorre quando o provedor de tempo de execução de persistência (Hibernate, EclipseLink ou outro) precisa persistir / definir algum registro na Tabela A que possui uma chave estrangeira referente a alguma coluna na Tabela B. No caso de um tipo de acesso de propriedade, a persistência O sistema de tempo de execução usa meu método setter codificado para atribuir à célula na coluna da Tabela B um novo valor. No caso de um tipo de acesso ao Campo, o sistema de tempo de execução de persistência define a célula na coluna Tabela B diretamente. Essa diferença não é importante no contexto de um relacionamento unidirecional, no entanto, é DEVE usar meu próprio método de setter codificado (tipo de acesso de propriedade) para um relacionamento bidirecional, desde que o método de setter seja bem projetado para dar conta da consistência. A consistência é uma questão crítica para os relacionamentos bidirecionais.link para um exemplo simples de um setter bem projetado.
Com referência a Equals / hashCode: É impossível usar os métodos Equals / hashCode gerados automaticamente pelo Eclipse para entidades que participam de um relacionamento bidirecional, caso contrário, elas terão uma referência circular resultando em uma exceção de estouro de pilha. Depois de tentar um relacionamento bidirecional (por exemplo, OneToOne) e gerar automaticamente Equals () ou hashCode () ou até toString (), você será pego nessa exceção do stackoverflow.
fonte
Interface da entidade
A implementação básica para todas as entidades simplifica as implementações Equals / Hashcode:
Entidade da sala impl:
Não vejo sentido em comparar a igualdade de entidades com base em campos de negócios em todos os casos de entidades JPA. Isso pode ser mais um caso se essas entidades JPA forem consideradas como ValueObjects controlados por domínio, em vez de entidades controladas por domínio (para as quais esses exemplos de código servem).
fonte