NHibernate: Arquitetura, fundamentos e recursos - Revista .NET Magazine 102

Este artigo aborda a arquitetura do framework NHibernate, assim como os seus principais conceitos e recursos.

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.

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.

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"

    [...] continue lendo...

Artigos relacionados