A persistência é definida como a capacidade que uma linguagem ou ambiente de programação tem de permitir que os dados e objetos “sobrevivam” a execução de um processo, para mais tarde utilizá-los em outro processo. Atualmente o conceito inclui também algumas operações que podem ser realizadas sobre os dados persistentes, tais como ordenação, pesquisa e gerenciamento de concorrência/integridade.

Neste artigo apresentaremos as opções básicas para persistência de objetos Java: serialização, conexões JDBC, ferramentas de mapeamento e uso de Entity Beans.

Serialização

Consiste na transformação de objetos em memória em uma seqüência de bytes que pode ser salva e posteriormente recuperada, podendo ser considerada uma forma de persistência simples.

Durante a serialização de um objeto, todos os objetos alcançáveis a partir dele também são serializados, a fim de manter consistente todo o seu estado. Como exemplo, veja a figura 1: nela temos um objeto A, que aponta para 4 objetos (B,C,D,E), que por sua vez apontam para outros dois objetos (F,G). Em uma serialização de A, todos esses objetos (B,C,D,E,F,G) seriam incluídos na seqüência de bytes.

Os bytes serializados podem ser armazenados em um arquivo ou SGBD e posteriormente recuperados. O processo de leitura restaura na memória as estruturas dos objetos originais, mas não suas identidades, o que, dependendo da aplicação, pode causar problemas. Os objetos quando reconstruídos contém os mesmos dados, mas podem ocupar posições distintas de memória - implicando, por exemplo, em hashcodes diferentes.

Serialização de um objeto
Figura 1. Serialização de um objeto.

As funcionalidades de serialização estão disponíveis no pacote java.io. As classes candidatas a serialização têm que obrigatoriamente implementar a interface Serializable. Essa interface não possui métodos ou atributos e é utilizada como um marcador.

Para serializar um objeto é necessário passá-lo como parâmetro para o método writeObject da classe ObjectOutputStream. Para recuperá-lo deve-se utilizar o método readObject da classe ObjectInputStream. As streams de serialização possuem todo o mecanismo para obter e ajustar o estado dos objetos e percorrer e reconstruir o conjunto de objetos alcançáveis. Elas devem ser utilizadas em conjunto com outras streams que façam a ligação com um dispositivo de sistema, como arquivos, sockets ou pipes, ou juntamente com um buffer para armazenar a seqüência de bytes gerada.

É muito fácil utilizar a serialização. No exemplo a seguir abrimos um arquivo, associamos uma stream de saída e persistimos um objeto da classe Date

FileOutputStream arquivo = new FileOutputStream("DataHora");
            ObjectOutputStream sequencia = new ObjectOutputStream(arquivo);
            sequencia.writeObject(new Date());
            sequencia.flush();

Para recuperar o objeto basta fazer a operação inversa - ler o arquivo e associar uma stream de entrada:

FileInputStream entrada = new FileInputStream("DataHora");
            ObjectInputStream sequencia = new ObjectInputStream(entrada);
            Date data = (Date) sequencia.readObject();

Em aplicações com pequeno volume de dados e operações a serialização é um excelente recurso. No entanto, em aplicações comerciais seu uso é praticamente inviável; objetos que podem ser acessados por mais de um caminho são armazenados de forma única em uma serialização, mas de forma redundante se forem serializações diferentes, sem qualquer controle de consistência. Guardar todos os objetos da aplicação a partir de um objeto de coleção, para evitar a redundância, acarreta em um imenso impacto de desempenho, visto que somente é possível serializar ou recuperar todos os objetos de uma vez. Além disso, não há como fazer consultas ou permitir acesso concorrente aos objetos persistentes.

Java Database Connectivity

Com a API JDBC (Java Database Connectivity), desenvolver aplicações cliente-servidoras de banco de dados se tornou um processo mais simples, devido à maior independência de SGBDs provida pela API.

Essa independência é atingida através dos drivers JDBC. Cada fornecedor que deseja tornar seu SGBD acessível a partir de uma aplicação Java disponibiliza um driver JDBC. Este driver faz toda a comunicação com o banco, encapsulando quaisquer características particulares que o fabricante tenha implementado. Em alguns casos, pode ser necessário inclusive que o driver implemente funcionalidades não disponíveis no SGBD.

A JDBC se encontra nos pacotes java.sql, onde está o cerne da API, e javax.sql. Entre as classes e interfaces mais importantes podemos citar (figura 2): i) classe DriverManager, que gerencia o acesso a múltiplas fontes de dados; ii) interface Driver, que representa um driver JDBC; iii) interface Connection, que representa uma conexão com a fonte de dados; iv) interfaces Statement, PreparedStatement e CallableStatement que permitem a execução de comandos SQL e stored procedures; v) interface ResultSet que representa os dados obtidos do banco; vi) classe Types que define os tipos de dados JDBC; vii) interface DatabaseMetaData que obtém informações sobre as capacidades do banco que são disponibilizadas pelo driver em uso.

Arquitetura JDBC
Figura 2. Arquitetura JDBC.

A JDBC é baseada nos padrões X/Open SQL CLI, SQL92 e SQL99 e tem seu foco em acesso a dados relacionais. Entretanto, a especificação não restringe seu uso, sendo possível implementar drivers que acessem outros tipos de fontes de dados, tais como bancos de dados objeto-relacionais (BDORs), bancos de dados orientados a objetos (BDOOs), XML etc.

A forma mais simples de persistir objetos com JDBC é embutir chamadas SQL diretamente nas classes de negócio da aplicação. Essa abordagem, no entanto, diminui a reusabilidade das classes, já que elas se tornam dependentes do mecanismo de persistência utilizado. Por exemplo, se decidirmos substituir o uso de um banco de dados relacional por um banco de dados XML, teremos que alterar as chamadas JDBC em toda a aplicação.

Uma forma de minimizar esse problema é criar classes para encapsular a comunicação com o banco de dados, formando uma “camada de persistência”. A desvantagem nesse caso é que o desenvolvedor terá que perder muito tempo na implementação dessas novas classes, se desviando do negócio principal da aplicação.

NOTA. Saiba mais sobre a API JDBC no artigo “Dados com JDBC”, publicado na Java Magazine 1.
NOta: Muitos dos mecanismos mais avançados de persistência utilizam a JDBC para fazer a comunicação com o banco de dados.

Mapeamento Objeto-Relacional

Com um mecanismo de mapeamento objeto-relacional (MMOR) o desenvolvedor não precisa se preocupar com chamadas JDBC ou SQL; ele apenas configura em arquivos de metadados como as classes de negócio deverão ser armazenadas no banco (qual campo vai armazenar qual atributo etc.) e o mecanismo de mapeamento cuida do resto. Na maioria dos MMORs esses arquivos seguem o padrão XML.

Alguns MMORs disponibilizam ferramentas que, a partir do modelo de classes da aplicação, geram automaticamente o esquema para o SGBD e os metadados de mapeamento. Em alguns casos também é gerado código fonte com o esqueleto das classes, onde a lógica de negócio pode ser inserida (ou codificada em subclasses). Também é comum encontrar MMORs que oferecem atualização automática de objetos persistentes modificados e carga de dados sob demanda (lazy loading).

O tipo de MMOR mais simples é o que disponibiliza classes de persistência. Uma classe de persistência recebe um objeto (através de métodos) e cuida de armazená-lo/recuperá-lo do banco de dados. Ela faz a leitura, em tempo de execução, dos arquivos de metadados para identificar como o objeto deve ser salvo/lido do banco. Veja um exemplo de uso de classe de persistência na listagem 1.


            import ;


 


...


 


//exemplo de escrita


public void faz_algumacoisa() {


 


    //classe de persistencia do MMOR (fictício)


    Persistence persistence = new Persistence();  


 


    //criando e inicializando o objeto de negócio


    Cliente cli = new Cliente(); 


    cli.setNome(“Jose da Silva”);


    cli.setIdade(“22”);


    


    //tornando o objeto persistente


    persistence.Save(cli);


  


}


 


//exemplo de leitura


public void  faz_outracoisa() {


 


  Persistence persistence = new Persistence(); 


 


  Cliente cli = new Cliente(); 


  cli = (Cliente) persistence.Load(Cliente.class, “Jose da Silva”);


 


  //usa Cliente...


}


 


            

Outro tipo de MMOR são os pós-processadores de código. O pós-processamento adiciona às próprias classes de negócio a funcionalidade de persistência, de forma transparente para o desenvolvedor.

A principal vantagem dos MMORs é o acesso a dados legados em SGBDRs. Novas aplicações podem ser desenvolvidas e executadas de forma concomitante a aplicações legadas, permitindo a migração suave de tecnologia ou a criação de novos serviços.

A desvantagem é que em aplicações com modelos complexos os MMORs tendem a apresentar desempenho insatisfatório. Outras deficiências comuns são a ausência de ferramentas para evolução do modelo da aplicação e mecanismos de consulta ad hoc pouco integrados à linguagem de programação OO.

Entre os MMORs que trabalham com Java temos: Castor, Hibernate, OJB, Toplink, VBSF e CocoBase.

Nota: Para saber mais sobre o Hibernate confira a edição 2 da SQL Magazine.

Entity Beans

A especificação J2EE (Java 2 Platform Enterprise Edition) define um padrão de arquitetura para o desenvolvimento e ambiente de execução de aplicações, tratando questões como disponibilidade, escalabilidade, concorrência e segurança de forma transparente. Estes ambientes de execução, chamados containers, provêem serviços e abrigam componentes de aplicação.

Segundo a especificação J2EE dois tipos de containers estão previstos: containers web, que abrigam servlets e páginas JSP em um servidor HTTP; e containers EJB (Enterprise JavaBeans) que abrigam componentes de negócio.

Os Enterprise JavaBeans são um modelo de desenvolvimento de componentes distribuídos, utilizados em aplicações multicamadas - os EJBs permanecem na camada de lógica do negócio, ou segunda camada.

Um componente EJB é encapsulado por um container EJB (figura 3). Esse modelo reduz o trabalho do desenvolvedor, pois o container controla o ciclo de vida e execução do componente, gerenciando automaticamente processos de persistência, connection pooling, transações, conexão remota com a interface cliente (thin client), segurança, concorrência e distribuição.

Sistema multicamadas com EJB
Figura 3. Sistema multicamadas com EJB.

A especificação EJB 2.0 prevê três tipos de componentes:

  • Session bean: Representa uma ação, serviço ou tarefa da aplicação, como aluguel de livro ou venda de assinatura. Geralmente interage com Entity Beans.
  • Entity Beans Representa uma entidade do negócio e possui a persistência como característica inerente – como exemplo de entity beans temos Cliente ou Livro. Normalmente são manipulados por session beans.
  • Message-driven beans: Assim como os session beans, implementam processos que podem envolver outros componentes. A diferença é que são chamados por mensagens e suas execuções são assíncronas às chamadas.

A persistência de um entity bean pode ser de dois tipos:

  • Bean-managed persistence (BMP): O desenvolvedor implementa o código para armazenamento e recuperação de dados. O mais comum é a utilização da API JDBC para acessar SGBDs.
  • Container-managed persistence (CMP): O container é responsável pela implementação da persistência. Nesse caso, o fornecedor do container disponibiliza uma ferramenta para a configuração do mapeamento (qual campo recebe qual atributo, qual tabela representa uma classe etc.). Disponibiliza a EJB-QL (EJB Query Language), uma linguagem de consulta padrão.

Um dos atrativos de um entity bean é que o desenvolvedor não fica responsável por gerenciar quando a leitura/escrita deve ser feita na base de dados – essa tarefa é de responsabilidade do container EJB. Se for utilizado CMP, o desenvolvedor também fica livre da implementação da persistência. No entanto, por ser mais declarativo que procedural, é perceptível um certo desconforto nos desenvolvedores ao trabalhar com CMP sem passar pela experiência de criar entity beans BMP e entender o que está acontecendo “por debaixo dos panos”.

Outro cenário para implementação de persistência em componentes EJB é o uso de session beans com acesso direto a uma base de dados, através de JDBC ou outro mecanismo de ligação com SGBDs. Nesse caso o desenvolvedor fica responsável por implementar e definir quando a persistência será realizada.

A arquitetura EJB exige um modelo de desenvolvimento diferenciado, por muitas vezes guiado por padrões de projeto (design patterns) e que requer um aprendizado demorado por parte do desenvolvedor. Um modelo de objetos que tenha sido previamente especificado para uma aplicação deve ser adequado à arquitetura EJB, para que possa extrair maior desempenho desta plataforma. Ainda, a manutenção de componentes pode ser custosa devido à quantidade de trechos de código (classe do componente, interfaces remota e local, interfaces home) que podem passar por mudanças.

Apesar de todo o investimento que vem sendo feito, nem sempre a aplicação de EJBs é adequada e a escolha dessa arquitetura deve ser feita de forma criteriosa.

Conclusão

Em futuras edições da SQL Magazine veremos formas adicionais, que podem ser consideradas mais modernas, de tratar a persistência de objetos. Entre elas estão o JDO e um conceito novo, e um pouco controverso, chamado prevalência de objetos.

revista
Clique aqui para ler todos os artigos desta edição Métodos de Persistência em Java