Anotações do Hibernate – Qual é o melhor, campo ou access à propriedade?

Esta questão é de alguma forma relacionada à questão da colocação de annotations do Hibernate .

Mas eu quero saber qual é melhor ? Acesso via propriedades ou access via campos? Quais são as vantagens e desvantagens de cada um?

Eu prefiro os acessadores, desde que eu possa adicionar alguma lógica de negócios aos meus acessadores sempre que eu precisar. Aqui está um exemplo:

@Entity public class Person { @Column("nickName") public String getNickName(){ if(this.name != null) return generateFunnyNick(this.name); else return "John Doe"; } } 

Além disso, se você lançar outras libs no mix (como alguma biblioteca de conversão JSON ou BeanMapper ou Dozer ou outro lib de mapeamento / clonagem de bean baseado nas propriedades getter / setter) você terá a garantia de que a lib está em sincronia com a persistência gerente (ambos usam o getter / setter).

Há argumentos para ambos, mas a maioria deles se origina de certos requisitos do usuário “e se você precisar adicionar lógica para” ou “xxxx quebra o encapsulamento”. No entanto, ninguém realmente comentou sobre a teoria e deu um argumento devidamente fundamentado.

O que o Hibernate / JPA está realmente fazendo quando persiste um object – bem, ele está persistindo no STATE do object. Isso significa armazená-lo de uma maneira que possa ser facilmente reproduzido.

O que é encapsulamento? Encapsulamentos significa encapsular os dados (ou estados) com uma interface que o aplicativo / cliente pode usar para acessar os dados com segurança – mantendo-os consistentes e válidos.

Pense nisso como o MS Word. O MS Word mantém um modelo do documento na memory – os documentos STATE. Ele apresenta uma interface que o usuário pode usar para modificar o documento – um conjunto de botões, ferramentas, comandos de teclado etc. No entanto, quando você opta por persistir (Salvar) esse documento, ele salva o estado interno, não o conjunto de pressionamentos de tecla e cliques do mouse usados ​​para gerá-lo.

Salvar o estado interno do object NÃO quebra o encapsulamento – caso contrário, você não entende realmente o que significa encapsulamento e por que ele existe. É exatamente como a serialização de objects.

Por esse motivo, NA MAIORIA DOS CASOS, é apropriado persistir os CAMPOS e não os ACESSÓRIOS. Isso significa que um object pode ser recriado com precisão do database exatamente como foi armazenado. Ele não deve precisar de nenhuma validação, porque isso foi feito no original quando foi criado e antes de ser armazenado no database (a menos que, Deus me livre, você esteja armazenando dados inválidos no database !!!!). Da mesma forma, não deve haver necessidade de calcular valores, pois eles já foram calculados antes do object ser armazenado. O object deve ficar exatamente como antes de ser salvo. De fato, adicionando coisas adicionais aos getters / setters, você está aumentando o risco de recriar algo que não é uma cópia exata do original.

Claro, essa funcionalidade foi adicionada por um motivo. Pode haver alguns casos de uso válidos para persistir os acessadores, no entanto, eles normalmente serão raros. Um exemplo pode ser que você queira evitar a persistência de um valor calculado, embora você possa querer fazer a pergunta por que você não o calcula sob demanda no getter do valor ou inicializa-o preguiçosamente no getter. Pessoalmente, não consigo pensar em nenhum bom caso de uso, e nenhuma das respostas aqui fornece uma resposta de “Engenharia de Software”.

Eu prefiro access de campo, porque dessa forma eu não sou forçado a fornecer getter / setter para cada propriedade.

Uma pesquisa rápida pelo Google sugere que o access ao campo é a maioria (por exemplo, http://java.dzone.com/tips/12-feb-jpa-20-why-accesstype ).

Acredito que o access ao campo é o idioma recomendado pela Spring, mas não consigo encontrar uma referência para comprovar isso.

Há uma questão SO relacionada que tentou medir o desempenho e chegou à conclusão de que “não há diferença”.

Aqui está uma situação em que você tem que usar os acessadores de propriedade. Imagine que você tenha uma class abstrata GENERIC com muita bondade de implementação para herdar em 8 subclasss concretas:

 public abstract class Foo { T oneThing; T anotherThing; // getters and setters ommited for brevity // Lots and lots of implementation regarding oneThing and anotherThing here } 

Agora exatamente como você deve anotar essa class? A resposta é que VOCÊ NÃO PODE anotá-lo com access de campo ou de propriedade porque não é possível especificar a entidade de destino neste momento. Você tem que anotar as implementações concretas. Mas como as propriedades persistentes são declaradas nessa superclass, você DEVE usar o access à propriedade nas subclasss.

O access ao campo não é uma opção em um aplicativo com superclasss genéricas abstratas.

Eu tenho a tendência de preferir e usar os acessadores de propriedade:

  • Eu posso adicionar lógica se houver necessidade (como mencionado na resposta aceita).
  • permite-me chamar foo.getId() sem inicializar um proxy (importante ao usar o Hibernate, até que o HHH-3718 seja resolvido).

Recua:

  • Isso torna o código menos legível, você tem, por exemplo, para procurar uma class inteira para ver se há @Transient por lá.

Isso realmente depende de um caso específico – ambas as opções estão disponíveis por um motivo. OMI resume-se a três casos:

  1. setter tem alguma lógica que não deve ser executada no momento de carregar uma instância de um database; por exemplo, alguma validação de valor acontece no setter, no entanto, os dados provenientes do database devem ser válidos (caso contrário, não chegaria lá (:); nesse caso, o access ao campo é mais apropriado;
  2. setter tem alguma lógica que deve sempre ser invocada, mesmo durante o carregamento de uma instância do db; por exemplo, a propriedade que está sendo inicializada é usada no cálculo de algum campo calculado (por exemplo, propriedade – um valor monetário, propriedade calculada – um total de várias propriedades monetárias da mesma instância); neste caso, o access à propriedade é obrigatório.
  3. Nenhum dos casos acima – então ambas as opções são aplicáveis, apenas fique consistente (ei se o access ao campo é a escolha nessa situação, então use-o o tempo todo em situação semelhante).

Eu recomendaria fortemente o access ao campo e NOT annotations sobre os getters (access à propriedade) se você quiser fazer algo mais nos setters do que apenas definir o valor (por exemplo, criptografia ou cálculo).

O problema com o access à propriedade é que os setters também são chamados quando o object é carregado. Isso funcionou bem para mim por muitos meses até que quiséssemos introduzir a criptografia. Em nosso caso de uso, queríamos criptografar um campo no setter e descriptografá-lo no getter. O problema agora com o access à propriedade era que quando o Hibernate carregava o object, ele também chamava o setter para preencher o campo e, assim, estava criptografando o valor criptografado novamente. Este post também menciona isto: Java Hibernate: Diferente comportamento da function do conjunto de propriedades dependendo de quem está chamando

Isso me causou dores de cabeça até que me lembrei da diferença entre o access ao campo e o access à propriedade. Agora eu mudei todas as minhas annotations do access à propriedade para o campo de access e funciona bem agora.

Acho que anotar a propriedade é melhor porque a atualização dos campos interrompe diretamente o encapsulamento, mesmo quando o ORM faz isso.

Aqui está um ótimo exemplo de onde ele vai te queimar: você provavelmente quer suas annotations para validação e persistência do hibernate no mesmo lugar (campos ou propriedades). Se você quiser testar suas validações validadas pelo validador de hibernação que estão anotadas em um campo, você não pode usar uma simulação de sua entidade para isolar seu teste de unidade apenas com o validador. Ai

Acredito que o access à propriedade versus o access ao campo é sutilmente diferente no que diz respeito à boot lenta.

Considere os seguintes mapeamentos para dois beans básicos:

                 

E os seguintes testes unitários:

 @Test public void testFieldBean() { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); FieldBean fb = new FieldBean("field"); Long id = (Long) session.save(fb); tx.commit(); session.close(); session = sessionFactory.openSession(); tx = session.beginTransaction(); fb = (FieldBean) session.load(FieldBean.class, id); System.out.println(fb.getId()); tx.commit(); session.close(); } @Test public void testPropBean() { Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); PropBean pb = new PropBean("prop"); Long id = (Long) session.save(pb); tx.commit(); session.close(); session = sessionFactory.openSession(); tx = session.beginTransaction(); pb = (PropBean) session.load(PropBean.class, id); System.out.println(pb.getId()); tx.commit(); session.close(); } 

Você verá a diferença sutil nas seleções necessárias:

 Hibernate: call next value for hibernate_sequence Hibernate: insert into FIELD_BEAN (message, id) values (?, ?) Hibernate: select fieldbean0_.id as id1_0_, fieldbean0_.message as message1_0_ from FIELD_BEAN fieldbean0_ where fieldbean0_.id=? 0 Hibernate: call next value for hibernate_sequence Hibernate: insert into PROP_BEAN (message, id) values (?, ?) 1 

Isto é, chamar fb.getId() requer um select, enquanto pb.getId() não.

Eu prefiro usar o access ao campo pelos seguintes motivos:

  1. O access à propriedade pode levar a erros muito desagradáveis ​​ao implementar equals / hashCode e campos de referência diretamente (ao contrário de seus getters). Isso ocorre porque o proxy é inicializado apenas quando os getters são acessados ​​e um access direto ao campo simplesmente retorna null.

  2. O access à propriedade requer que você anote todos os methods utilitários (por exemplo, addChild / removeChild) como @Transient .

  3. Com o access ao campo, podemos ocultar o campo @Version, não expondo nenhum getter. Um getter também pode levar à adição de um setter, e o campo de version nunca deve ser configurado manualmente (o que pode levar a problemas muito desagradáveis). Toda a incrementação de versão deve ser acionada através do bloqueio explícito OPTIMISTIC_FORCE_INCREMENT ou PESSIMISTIC_FORCE_INCREMENT .

Já estamos lá

Essa é uma apresentação antiga, mas Rod sugere que a anotação sobre o access à propriedade incentiva modelos de domínio anêmico e não deve ser a maneira “padrão” de anotar.

Outro ponto a favor do access de campo é que, de outra forma, você é forçado a expor setters para collections, o que, para mim, é uma má idéia, pois alterar a instância de coleção persistente para um object não gerenciado pelo Hibernate definitivamente quebra a consistência dos dados.

Então eu prefiro ter collections como campos protegidos inicializados para esvaziar implementações no construtor padrão e expor apenas seus getters. Então, apenas operações gerenciadas como clear() , remove() , removeAll() etc são possíveis e nunca tornarão o Hibernate inconsciente de mudanças.

Eu prefiro campos, mas encontrei uma situação que parece me forçar a colocar as annotations em getters.

Com a implementação do Hibernate JPA, o @Embedded parece não funcionar nos campos. Então isso tem que ir no getter. E uma vez que você coloca isso no getter, então as várias annotations do @Column têm que ir nos getters também. (Eu acho que o Hibernate não quer misturar campos e getters aqui.) E uma vez que você coloca @Column em getters em uma class, provavelmente faz sentido fazer isso por toda parte.

Sou a favor de assessores de campo. O código é muito mais limpo. Todas as annotations podem ser colocadas em uma seção de uma class e o código é muito mais fácil de ler.

Eu encontrei um outro problema com os acessadores de propriedade: se você tem methods getXYZ em sua class que NÃO são anotados como associados a propriedades persistentes, o hibernate gera sql para tentar obter essas propriedades, resultando em algumas mensagens de erro muito confusas. Duas horas desperdiçadas. Eu não escrevi este código; Eu sempre usei assessores de campo no passado e nunca encontrei esse problema.

Versões de hibernação usadas neste aplicativo:

  3.3.2.GA 3.4.0.GA 3.1.0.GA 3.4.0.GA 

Eu tive a mesma pergunta sobre o tipo de access no hibernate e encontrei algumas respostas aqui .

Eu tenho resolvido a boot lenta e access de campo aqui Hibernate one-to-one: getId () sem buscar o object inteiro

Criamos beans de entidade e usamos annotations getter. O problema que enfrentamos é o seguinte: algumas entidades têm regras complexas para algumas propriedades sobre quando elas podem ser atualizadas. A solução era ter alguma lógica de negócios em cada setter que determinasse se o valor real mudava e, em caso afirmativo, se a mudança deveria ou não ser permitida. É claro que o Hibernate pode sempre definir as propriedades, então acabamos com dois grupos de setters. Bonito feio.

Lendo postagens anteriores, também vejo que referenciar as propriedades de dentro da entidade pode levar a problemas com as cobranças que não são carregadas.

Linha de fundo, eu gostaria de anotar os campos no futuro.

Por padrão, os provedores JPA acessam os valores dos campos de entidade e mapeiam esses campos para as colunas do database usando os methods de acessador (getter) e mutator (setter) da propriedade JavaBean da entidade. Como tal, os nomes e tipos dos campos privados em uma entidade não importam para o JPA. Em vez disso, o JPA examina apenas os nomes e os tipos de retorno dos accessres da propriedade JavaBean. Você pode alterar isso usando a anotação @javax.persistence.Access , que permite especificar explicitamente a metodologia de access que o provedor JPA deve empregar.

 @Entity @Access(AccessType.FIELD) public class SomeEntity implements Serializable { ... } 

As opções disponíveis para o enum AccessType são PROPERTY (o padrão) e FIELD. Com PROPERTY, o provedor obtém e define valores de campo usando os methods de propriedade JavaBean. O FIELD faz com que o provedor obtenha e defina valores de campo usando os campos da instância. Como prática recomendada, você deve se ater ao padrão e usar as propriedades do JavaBean, a menos que tenha um motivo convincente para fazer o contrário.

Você pode colocar essas annotations de propriedade nos campos privados ou nos methods de access público. Se você usar AccessType.PROPERTY (padrão) e anotar os campos privados em vez dos acessadores JavaBean, os nomes de campo deverão corresponder aos nomes de propriedade JavaBean. No entanto, os nomes não precisam corresponder se você anotar os acessadores JavaBean. Da mesma forma, se você usar AccessType.FIELD e anotar os acessadores JavaBean em vez dos campos, os nomes dos campos também deverão corresponder aos nomes das propriedades JavaBean. Nesse caso, eles não precisam corresponder se você anotar os campos. É melhor ser consistente e anotar os acessadores JavaBean para AccessType.PROPERTY e os campos para AccessType.FIELD .

É importante que você nunca misture annotations de propriedade JPA e annotations de campo JPA na mesma entidade. Isso resulta em um comportamento não especificado e é muito provável que cause erros.

Normalmente beans são POJO, então eles têm acessadores de qualquer maneira.

Portanto, a questão não é “qual é o melhor?”, Mas simplesmente “quando usar o access ao campo?”. E a resposta é “quando você não precisa de um setter / getter para o campo!”.

Eu penso sobre isso e eu escolho o método accesor

porque?

porque o campo e methos accesor é o mesmo, mas se depois eu precisar de alguma lógica no campo de carga, salvo salvar todas as annotations colocadas nos campos

Saudações

Grubhart

Para tornar suas aulas mais limpas, coloque a anotação no campo e use @Access (AccessType.PROPERTY)

Ambos :

A especificação EJB3 exige que você declare as annotations no tipo de elemento que será acessado, ou seja, o método getter se você usar o access à propriedade, o campo se você usar o access ao campo.

https://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping

AccessType.PROPERTY: A implementação de persistência do EJB carregará o estado em sua class por meio dos methods “setter” do JavaBean e recuperará o estado de sua class usando os methods “getter” do JavaBean. Este é o padrão.

AccessType.FIELD: o estado é carregado e recuperado diretamente dos campos da sua turma. Você não precisa escrever “getters” e “setters” do JavaBean.