O que é Hibernar? Fundamentos da implementação do Hibernate Core
Publicados: 2013-04-17Hibernate é um projeto de framework de persistência Java de código aberto. Execute mapeamento relacional de objeto poderoso e bancos de dados de consulta usando HQL e SQL.
Em geral, as bibliotecas amplamente utilizadas são bem projetadas e implementadas, e é muito interessante aprender com elas algumas práticas recomendadas de codificação.
Vamos dar uma olhada dentro da biblioteca principal de hibernação e descobrir algumas de suas chaves de design.
Neste post o Hibernate Core é analisado pelo JArchitect
para aprofundar seu design e implementação.
Pacote por recurso
Package-by-feature
usa pacotes para refletir o conjunto de recursos. Ele coloca todos os itens relacionados a um único recurso (e somente esse recurso) em um único diretório/pacote. Isso resulta em pacotes com alta coesão e alta modularidade, e com acoplamento mínimo entre pacotes. Os itens que funcionam juntos são colocados próximos uns dos outros.
O núcleo do Hibernate contém muitos pacotes, cada um relacionado a um recurso específico hql, sql e outros.
Acoplamento
O baixo acoplamento é desejável porque uma mudança em uma área de uma aplicação exigirá menos mudanças em toda a aplicação. A longo prazo, isso pode aliviar muito tempo, esforço e custo associados à modificação e adição de novos recursos a um aplicativo.
Aqui estão três principais benefícios derivados do uso de interfaces:
- Uma interface fornece uma maneira de definir um contrato que promove a reutilização. Se um objeto implementa uma interface, então esse objeto deve estar em conformidade com um padrão. Um objeto que usa outro objeto é chamado de consumidor. Uma interface é um contrato entre um objeto e seu consumidor.
- Uma interface também fornece um nível de abstração que torna os programas mais fáceis de entender. As interfaces permitem que os desenvolvedores comecem a falar sobre a maneira geral como o código se comporta sem precisar entrar em muitos detalhes específicos.
- Uma interface impõe baixo acoplamento entre os componentes, o que facilita a proteção do consumidor da interface de quaisquer mudanças de implementação nas classes que implementam as interfaces.
Vamos buscar todas as interfaces definidas pelo Hibernate Core, para isso usamos CQLinq
para consultar a base de código.
1 |
from t in Types where t . IsInterface select t |
Se nosso objetivo principal é impor baixo acoplamento, há um erro comum ao usar interfaces que podem matar a utilidade de usá-las. É o uso das classes concretas ao invés de interfaces, e para explicar melhor esse problema vamos pegar o seguinte exemplo:
A classe A implementa a Interface IA que contém o método calculate(), a classe consumidora C é implementada assim
1 2 3 4 5 6 7 8 9 10 11 |
public class C < br > { … . public void calculate ( ) { … . . m_a . calculate ( ) ; … . } A m_a ; } |
A Classe C ao invés de referenciar a interface IA, ela referencia a classe A, neste caso perdemos o benefício de baixo acoplamento, e esta implementação tem duas grandes desvantagens:
- Se decidirmos usar outra implementação de IA, devemos alterar o código da classe C.
- Se alguns métodos forem adicionados a A não existentes em IA e C os usarem, também perderemos o benefício contratual de usar interfaces.
O C# introduziu o recurso de implementação de interface explícita na linguagem para garantir que um método do IA nunca seja chamado de uma referência a classes concretas, mas apenas de uma referência à interface. Essa técnica é muito útil para proteger os desenvolvedores de perder o benefício de usar interfaces.
Com o JArchitect podemos verificar esse tipo de erro usando CQLinq
, a ideia é buscar todos os métodos de classes concretas usadas diretamente por outros métodos.
1 2 3 4 5 6 7 8 9 |
from m in Methods where m . NbMethodsCallingMe > 0 && m . ParentType . IsClass && ! m . ParentType . IsThirdParty && ! m . ParentType . IsAbstract let interfaces = m . ParentType . InterfacesImplemented from i in interfaces where i . Methods . Where ( a = > a . Name == m . Name && a . ParentType ! = m . ParentType ) . Count ( ) > 0 select new { m , m . ParentType , i } |
Por exemplo, o método getEntityPersister de SessionFactoryImpl que implementa a interface SessionFactoryImplementor está preocupado com este problema.
Vamos procurar métodos que invoquem diretamente SessionFactoryImpl.getEntityPersister.
1 2 3 |
from m in Methods where m . IsUsing ( "org.hibernate.internal.SessionFactoryImpl.getEntityPersister(String)" ) select new { m , m . NbBCInstructions } |

Métodos como SessionImpl.instantiate invocam diretamente getEntityPersister, ao invés de passar por interface, o que quebra o benefício de usar interfaces. Felizmente, o núcleo de hibernação não contém muitos métodos com esse problema.
Acoplamento com frascos externos
Quando libs externas são usadas, é melhor verificar se podemos facilmente alterar uma lib de terceiros por outra sem afetar toda a aplicação, existem muitos motivos que podem nos encorajar a alterar uma lib de terceiros.
A outra lib poderia:
- Tenha mais recursos
- Mais poderoso
- Mais seguro
Vamos pegar o exemplo da antlr lib
que costumava analisar as consultas hql, e imaginar que outro analisador mais poderoso que o antlr foi criado, poderíamos trocar o antlr pelo novo analisador facilmente?
Para responder a essa pergunta, vamos pesquisar quais métodos do hibernate o usam diretamente:
1 2 3 |
from m in Methods where m . IsUsing ( "antlr-2.7.7" ) select new { m , m . NbBCInstructions } |
E quais usaram indiretamente:
1 2 3 4 |
from m in Projects . WithNameNotIn ( "antlr-2.7.7" ) . ChildMethods ( ) let depth0 = m . DepthOfIsUsing ( "antlr-2.7.7" ) where depth0 > 1 orderby depth0 select new { m , depth0 } |
Muitos métodos usam o antlr diretamente o que torna o core hibernate altamente acoplado a ele, e trocar o antlr por outro não é uma tarefa fácil. este fato não significa que temos um problema no design de hibernação, mas temos que ter cuidado ao usar uma lib de terceiros e verificar se uma lib de terceiros deve ser baixa ou não com a aplicação.
Coesão
O princípio da responsabilidade única afirma que uma classe deve ter um, e apenas um, motivo para mudar. Essa classe é dita coesa. Um valor alto de LCOM
geralmente indica uma classe pouco coesa. Existem várias métricas LCOM. O LCOM assume seus valores no intervalo [0-1]. O LCOMHS (HS significa Henderson-Sellers) assume seus valores no intervalo [0-2]. Observe que a métrica LCOMHS é frequentemente considerada mais eficiente para detectar tipos não coesos.
O valor de LCOMHS superior a 1 deve ser considerado alarmante.
Em geral, as classes mais preocupadas com a coesão são as classes que possuem muitos métodos e campos.
Vamos procurar por tipos com muitos métodos e campos.
1 2 3 4 |
from t in Types where ( t . Methods . Count ( ) > 40 | | t . Fields . Count ( ) > 40 ) && t . IsClass orderby t . Methods . Count ( ) descending select new { t , t . InstanceMethods , t . Fields , t . LCOMHS } |
Apenas alguns tipos estão preocupados com esta consulta, e para todos eles o LCOMHS é menor que 1.
Usando anotações
O desenvolvimento baseado em anotação alivia os desenvolvedores Java da dor da configuração incômoda. E nos dê um recurso poderoso para liberar o código-fonte do código clichê. O código resultante também é menos provável de conter bugs.
Vamos procurar todas as anotações definidas pelo núcleo de hibernação.
1 |
from t in Types where t . IsAnnotationClass && ! t . IsThirdParty select t |
Muitas anotações são definidas, o que torna a hibernação fácil de usar pelos desenvolvedores, e a dor de cabeça dos arquivos de configuração é evitada.
Conclusão
O Hibernate Core é um bom exemplo de projetos de código aberto para aprender, não hesite em dar uma olhada nele.