NHibernate – Arquitetura, fundamentos e recursos: É muito comum encontrarmos materiais sobre NHibernate explicando em um exemplo básico como realizar um mapeamento simples, para realização de um cadastro ou de uma consulta pequena. A ideia deste artigo é ir um pouco além, abordando a arquitetura deste framework, assim como os seus principais conceitos e recursos. Primeiramente mostraremos como é a arquitetura do mesmo, suas principais classes, como ele consegue se conectar com diversos SGDBs e em seguida veremos como configurar uma conexão com o NHibernate. Uma vez configurada a conexão, falaremos sobre o gerenciamento de sessão do NH. Abordaremos ainda os tipos de mapeamento, demonstrando os pontos fortes e fracos de cada um. Chegaremos então ao conceito de proxies onde explicaremos como o NHibernate cria suas proxies dinamicamente para permitir o uso do recurso de lazy load. Após isso falaremos sobre mapeamento de objetos e sobre as possíveis estratégias que você pode utilizar para mapear heranças. Para finalizar falaremos um pouco sobre como recuperar objetos da base de dados utilizando NHibernate.


Em que situação o tema é útil
: Este tema é útil a qualquer equipe que trabalhe com NHibernate ou que tenha a intenção de utilizá-lo em seus projetos. Com este artigo, muitas dúvidas serão sanadas e o leitor poderá extrair o máximo do poder do NHibernate em seus projetos, ganhando em qualidade de código, produtividade e performance.

Há alguns anos atrás, quando falávamos sobre frameworks ORM no mundo .NET, muitos desenvolvedores não saberiam do que se tratava. No Java esse assunto é mais antigo e difundido, no .NET ele começou a ficar mais popular com o lançamento do Entity Framework, framework ORM nativo da Microsoft. Por outro lado, em paralelo a isto, surgiu na comunidade a iniciativa do NHibernate, que trata-se da portabilidade do famoso Hibernate, do Java, para a plataforma .NET.

Desde então outros frameworks surgiram e estes dois foram amadurecendo cada vez mais, tornando-se os mais populares e queridos da plataforma .NET. Por outro lado, o que é muito comum de se ver no mercado é a subutilização destes frameworks, talvez pelo fato de ser relativamente simples persistir e recuperar um objeto, os desenvolvedores acabam deixando de lado conceitos fundamentais dos mesmos como sua arquitetura, proxies dinâmicas, tipos de mapeamento, gerenciamento de sessão etc... e acabam utilizando apenas os recursos mais básicos, muitas vezes gerando consultas desnecessárias, impactos de performance, dentre outros problemas.

Nossa proposta com este artigo é irmos um pouco além, falando sobre a arquitetura, conceitos de sessão, proxies, estratégias de mapeamento, tipos de mapeamento etc...

Arquitetura do NHibernate

O conceito chave por trás do ORM é o mapeamento dos elementos da aplicação (orientada a objetos) para os elementos da base de dados (relacional). Além disso, temos a questão da abstração da base de dados, de forma que nossa aplicação persista e recupere dados sem saber exatamente que tipo de SGDB está sendo utilizado. O NHibernate nos fornece ferramentas para atingir estes dois objetivos.

Arquitetura do NHibernate

Figura 1. Arquitetura do NHibernate

Na Figura 1 podemos observar a arquitetura do NHibernate. Na primeira camada temos a representação da nossa aplicação. Nele temos nossas classes de domínio que serão mapeadas para o SGDB. É nesta camada que teremos também o mapeamento destas classes, que será passado para o NHibernate. Vale ressaltar que esta figura representa a arquitetura do NHibernate e não da aplicação que consome ele, por este motivo a aplicação está concentrada em apenas um bloco. Em um cenário real teríamos N camadas lógicas na nossa aplicação.

Na segunda camada, de cor laranja, temos a representação do NHibernate, com suas classes de gerenciamento de sessão e conexão. Nesta camada é interessante notar que o NHibernate não acessa diretamente a base de dados, mas faz uso da terceira camada para isso.

Esta terceira camada representa as abstrações de acesso a dados do .NET, ou seja, o NHibernate faz uso destas interfaces do ADO.NET para se comunicar com a base de dados. Sendo assim, qualquer SGDB que tenha uma implementação para o ADO.NET poderá ser utilizado com o NHibernate.

Por fim, na quarta camada temos o database de fato, onde os dados serão persistidos.

  • ISessionFactory – Classe responsável por criar as sessões do NHibernate e manter o mapeamento das classes em memória. É responsável também pelo cache de segundo nível. Este é o objeto mais caro do NHibernate e por este motivo é altamente recomendável que tenhamos apenas uma instância do mesmo em nossa aplicação.
  • ISession – Classe responsável por gerenciara comunicação entre a aplicação e a base de dados. É ela quem encapsula uma conexão com o ADO.NET connection, fornecendo métodos para persistência e manipulação de objetos, além de gerenciamento de transações com a base de dados. Nela fica o cache de primeiro nível do NHibernate.
  • ITransaction – Classe responsável por abstrair a ADO.NET Transaction, possibilitando o uso de transações atômicas para realização de operações na base de dados.
  • IConnectionProvider – Classe interna do NHibernate que serve como factory para ADO.NET connections e Commands. O objetivo dela é abstrair as implementações concretas das interfaces IDBConnection e IDBCommand.
  • IDriver – Classe que encapsula as diferenças entre os providers ADO.NET, como convenções para nomes de parâmetros e recursos suportados por cada SGDB.
  • TransactionFactory – Classe interna do NHibernate para criação de transações.

Entendendo as configurações do NHibernate

Devido à abstração de diversas bases de dados, o NHibernate nos fornece a possibilidade de configurar quase tudo na nossa conexão e persistência de dados.

A configuração inicial é realizada através do objeto NHibernate.Cfg.Configuration, sendo este objeto responsável por criar o SessionFactory.

As configurações podem ser definidas diretamente no web.config (Listagem 1) ou em um arquivo padrão de configuração do NHibernate (Listagem 2).

Listagem 1. Exemplo de configuração no Web.Config


<configuration>
  <configSections>
    <section name="hibernate-configuration" type="NHibernate.Cfg.Configuration SectionHandler, NHibernate"/>
  </configSections>
  <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
    <session-factory>
      <property name="connection.provider"> NHibernate.Connection.DriverConnection Provider</property>
      <property name="connection.driver_class"> NHibernate.Driver.MySqlDataDriver</property>
      <property name="dialect">NHibernate.Dialect. MySQL5Dialect</property>
      <property name="hbm2ddl.auto">none</property>
      <property name="connection.connection_string">
        Database=my_database_net_102;
        Data Source=LOCALHOST;
        User Id=root;
        Password=devmedia;
      </property>
    </session-factory>
  </hibernate-configuration>
</configuration>

Listagem 2. Exemplo de configuração no hibernate.cfg.xml


<?xml version="1.0" encoding="utf-8"?>
<hibernate-configuration  xmlns="urn:nhibernate-configuration-2.2" >
  <session-factory>
    <property name="connection.provider"> NHibernate.Connection.DriverConnection Provider</property>
    <property name="connection.driver_class"> NHibernate.Driver.MySqlDataDriver </property>
    <property name="dialect">NHibernate.Dialect. MySQL5Dialect</property>
    <property name="hbm2ddl.auto">none</property>
    <property name="connection.connection_string">
      Database=my_database_net_102;
      Data Source=LOCALHOST;
      User Id=root;
      Password=devmedia;
    </property>
  </session-factory>
</hibernate-configuration>

O que muda entre as Listagens 1 e 2 são os nós containers da configuração, onde na Listagem 1 temos a configuração da session factory dentro de configuration enquanto que na Listagem 2 a mesma está diretamente no corpo do arquivo.

Observando a Listagem 2 podemos ver na linha 4 que definimos o uso do DriverConnectionProvider, o que indica para o NHibernate que utilizaremos um de seus Drivers para conexão. Lembrando que este Driver deverá implementar os métodos da classe IDriver. Na linha 5 temos a definição de qual driver será utilizado. No exemplo em questão nós temos o Driver NHibernate.Driver.MySqlDataDriver.

Com relação a este driver, temos um ponto interessante a observar, neste momento começamos a entender que não existe mágica por trás de um framework ORM. O valor informado NHibernate.Driver.MySqlDataDriver nada mais é do que o nome de uma classe que implementa a interface IDriver e herda de um driver padrão do NHibernate chamado DriverBase, como podemos ver na Figura 2.

Estrutura de classes de IDriver

Figura 2. Estrutura de classes de IDriver

A Figura 2 contém a hierarquia de classes de drivers do NHibernate. Ela foi reduzida a estes três drivers para caber no artigo. Além dos drivers MySqlDataDriver, NpgsqlDriver e OracleClientDriver, temos atualmente mais de 15 drivers implementados no NHibernate. É interessante destacar também que, como o NHibernate é open source, caso exista algum SGDB que não tenha ainda seu driver implementado no NHibernate, você mesmo pode implementá-lo, bastando criar uma classe que herde da classe base do mesmo e sobrescrever as características da mesma de acordo com o SGDB que estiver implementando. Na classe DriverBase nós temos as principais configurações das bases de dados, na classe ReflectionBasedDriver nós temos alguns métodos que padronizam a carga de drivers via reflection e nas classes concretas temos as questões específicas de cada base de dados, como mostrado na Listagem 3.

Listagem 3. Código da classe MySqlDataDriver do NHibernate


using System;
    
namespace NHibernate.Driver
{
       public class MySqlDataDriver : ReflectionBasedDriver
       {
             public MySqlDataDriver() : base(
                    "MySql.Data.MySqlClient",
                    "MySql.Data",
                    "MySql.Data.MySqlClient.MySqlConnection",
                    "MySql.Data.MySqlClient.MySqlCommand")
             {
             }
    
             public override bool UseNamedPrefixInSql
             {
                    get { return true; }
             }
    
              public override bool UseNamedPrefixInParameter
             {
                    get { return true; }
             }
    
             public override string NamedPrefix
             {
                    get { return "?"; }
             }
    
            public override bool SupportsMultipleOpenReaders
             {
                    get { return false; }
             }
    
             protected override bool SupportsPreparingCommands
             {
                    get { return false; }
             }
    
             public override IResultSetsCommandGetResultSetsCommand(Engine.ISessionImplementor session)
             {
                    return new BasicResultSetsCommand(session);
             }
    
             public override bool SupportsMultipleQueries
             {
                    get { return true; }
             }
       }
}

Na Listagem 3 podemos observar primeiramente a sobrecarga do construtor na linha 7. Observe que são passados quatro parâmetros, sendo eles:

  • O nome do provider MySql.Data.MySqlClient
  • O nome do assembly de onde serão carregados os tipos que implementam IDbConnection e IDbCommand
  • O nome completo da classe que implementa o IDdConnection para o MySQL
  • O nome completo da classe que implementa o IDdCommand para o MySQL

Este construtor está implementado na classe ReflectionBasedDriver. Esta classe usará estas configurações para saber quais tipos e em qual assembly deverá buscar os mesmos, para criar objetos de conexão e de execução de comandos SQL. Observando os demais métodos podemos ver algumas particularidades do MySQL como, por exemplo, na linha 20 onde definimos que o banco de dados usa prefixos para parâmetros e na linha 25 onde definimos que o caracter usado como prefixo é o “?”.

Voltando à Listagem 2, na linha 6 temos a definição do dialeto a ser utilizado pelo NHibernate. Neste caso definimos NHibernate.Dialect.MySQL5Dialect como sendo o dialeto a ser utilizado. Este dialeto é fundamental para que o NHibernate consiga fazer a “mágica” de se comunicar com diferentes SGDBs. É nesta classe que estão definidas as particularidades e os comandos, com sua respectiva sintaxe para cada SGDB. Todos os dialetos concretos do NHibernate precisam herdar da classe abstrata Dialect, onde temos uma série de definições.

Diagrama dos dialetos do NHibernate

Figura 3. Diagrama dos dialetos do NHibernate

Na Figura 3 nós temos uma representação da arquitetura dos dialetos do NHibernate, onde podemos observar a estratégia de especialização do NHibernate. No caso do Oracle, por exemplo, temos um dialeto para a versão 8, depois temos uma especialização para a versão 9 e depois mais duas especializações para as versões 10g e OracleLite. Sendo assim, podemos perceber que, caso uma nova versão de um SGDB seja lançada, com mudanças que possam impactar nossa aplicação, podemos nós mesmos implementar um novo dialeto com estas novas características, fazendo com que o NHibernate passe a dar suporte à esta nova versão.

O código da classe Dialect é muito extenso, pois contempla aquilo que é comum para todos, ou pelo menos para a maioria dos SGDBs, por isso vamos mostrar apenas alguns métodos para exemplificar o que estamos explicando.

Listagem 4. ...

Quer ler esse conteúdo completo? Tenha acesso completo