Autores: Carla Paxiúba, Regiane Brito, Valéria Sousa, Prof. Rodrigo Quites Reis e Profa. Carla Alessandra Lima Reis
Como apresentado no artigo “Banco de Dados Orientado a Objetos: Uma introdução”, publicado na edição anterior, um dos grandes problemas no estudo e utilização prática de Bancos de Dados Orientados a Objetos (OO) é a baixa quantidade de opções no mercado. A maioria dos sistemas existentes são comerciais e de custo elevado, o que impede a adoção maciça desta tecnologia por pequenas organizações. Além disso, a prática de ensino do paradigma de objetos para bancos de dados em instituições de ensino superior é relativamente limitada aos poucos produtos que oferecem licenças acadêmicas.
Este artigo tem o objetivo de fornecer uma apresentação inicial para o banco de dados open-source denominado Ozone [1], disponível para a linguagem Java.
1. Histórico do Projeto Ozone
Grande parte da documentação acerca do projeto é mantida no endereço http://www.ozone-db.org. Assim, esta seção apresenta um resumo daquilo que é fornecido online pelos participantes do projeto.
O Ozone permite o desenvolvimento de aplicações puramente orientada a objetos, baseadas em Java, que manipulem dados persistentes. O projeto não necessita de outro sistema de banco de dados para que o desenvolvimento de uma aplicação completa seja bem sucedido. Assim, a abordagem do Ozone é oposta ao Hibernate (também apresentado na segunda edição da SQL Magazine).
O banco teve suas raízes relacionadas com um projeto pessoal de estudo envolvendo multimídia. A idéia era fornecer uma arquitetura para integração, em um único documento, de dados obtidos a partir de diferentes fontes. A evolução desta idéia virou o projeto de uma arquitetura em que uma única instância do banco de dados reside no servidor, a qual pode ser controlada através de objetos Proxy. Para a aplicação cliente o Proxy se comporta como se fosse o próprio banco de dados.
O Proxy também abstrai a complexidade acerca da localização exata do objeto armazenado. Assim, para as aplicações cliente, as bibliotecas que compõem o Ozone atuam de forma similar a um protocolo de objetos distribuídos, tais como o Remote Method Invocation (RMI) da Sun [4] ou o CORBA do ODMG [5].
2. Características do Ozone
O fato de utilizar um banco de dados orientado a objetos (BDOO) no processo de desenvolvimento de software que utiliza a linguagem Java é, sem dúvida, atraente. Talvez o principal benefício consiste no fato de existir uma homogeneidade entre os modelos de dados utilizados pelo programa e pelo banco de dados. Assim, não há necessidade do mapeamento dos modelos de dados da linguagem usada na programação e do banco de dados, o que é comum quando são utilizadas linguagens Orientadas a Objetos com Bancos de Dados Relacionais. O mapeamento de tipo exige atenção especial dos desenvolvedores e/ou a utilização de ferramentas específicas e, além disso, adiciona maior complexidade para o entendimento e manutenção do sistema.
Esta seção descreve em linhas gerais o funcionamento de Ozone, privilegiando o uso de um exemplo simples obtido a partir do tutorial fornecido na documentação original do sistema [1].
2.1 Licença de Uso
Ozone é desenvolvido e distribuído segundo uma licença LGPL (GNU Library General Public License). Na prática, isto significa que pode ser usado gratuitamente tanto em situações comerciais quanto não-comerciais e o software desenvolvido com Ozone não implica em pagamento de royalties.
2.2 Objetos Ozone
Dois tipos principais de objetos são armazenados no Ozone. O primeiro tipo corresponde aos objetos Java “normais”, isto é, criados a partir de qualquer classe definida pelo usuário. Qualquer objeto Java pode ser armazenado no Ozone. A única restrição é que a classe do objeto deve implementar a interface Serializable.
Deve-se ressaltar o porquê da restrição mencionada acima. Serialização é um processo fornecido por linguagens como Java para transformar um objeto que existe em memória para um formato “serial”, conveniente para ser transmitido pela rede ou armazenado em um arquivo. Portanto, na prática, praticamente todos as classes construídas pelos usuários implementam a interface Serializable.
O segundo tipo de objeto a ser armazenado é o chamado “objeto Ozone”. Objetos Ozone são objetos construídos a partir de subclasses da classe OzoneObject, sendo acessados através de um Proxy. Os objetos Ozone, por sua vez, são divididos em dois tipos: aqueles que possuem um nome e os anônimos. O “nome” de um objeto Ozone é usado na recuperação de um objeto armazenado no banco de dados: somente objetos nomeados podem ser recuperados diretamente do banco de dados com uma chamada ao método objectForName().
Um exemplo, adaptado a partir de um exemplo contido na documentação original [1], é fornecido na Listagem 1. A primeira linha apresenta a recuperação do objeto “MyHouse” através do envio de uma mensagem objectForName para o objeto db (que representa o banco de dados Ozone aqui). O objeto recuperado é rotulado como “myH” no código Java apresentado. A segunda linha descreve que o objeto retornado a partir do envio da mensagem getDoor() no objeto myH será nomeado como frontDoor. Na terceira linha, ao objeto frontDoor é enviada a mensagem getKnob() a fim de se obter um objeto da classe Knob. Neste exemplo, todos os objetos myHouse (e, por sua vez, myH), frontDoor e doorKnob residem no banco de dados e são recuperados por demanda. Além disso, o objeto MyHouse necessariamente é um objeto que possui um nome no Ozone (visto que é obtido a partir da chamada objectForName), enquanto que frontDoor e doorKnob podem ser objetos Ozone ou objetos normais.
1. House myH = db.objectForName("MyHouse");
2. Door frontDoor = myH.getDoor();
3. Knob doorKnob = frontDoor.getKnob();
Listagem 1 Código-exemplo de recuperação de um objeto nomeado como MyHouse e manipulação dos seus componentes
O exemplo mostrado na listagem 1 serve para ilustrar a manipulação dos objetos recuperados a partir do Ozone: uma vez que o objeto é recuperado para memória, a manipulação ocorre tal como qualquer objeto Java, isto é, pode-se enviar mensagens para os objetos utilizando-se a notação comum existente na linguagem.
2.3 Instalação e Configuração
Não é objetivo deste texto descrever detalhadamente o processo de instalação e configuração do Ozone, visto que o leitor interessado pode obter informações detalhadas na página do projeto [1]. Entretanto, deve-se ressaltar que a tarefa de instalação e configuração do Java e suas aplicações é relativamente simples pois não há a complexidade inerente ao uso de drivers ODBC ou JDBC no acesso ao banco de dados.
Nenhum ambiente de gerenciamento do banco de dados é fornecido. Na realidade, toda funcionalidade do Ozone é fornecida a partir de uma biblioteca de classes que garantem a consistência no acesso remoto ao banco de dados feito com múltiplos usuários simultâneos e aplicativos multithreading.
3. Estudo de caso
A figura 1 apresenta o mesmo modelo de classes na notação UML que foi usado como exemplo ilustrativo no artigo anterior sobre o tema. O diagrama contém seis classes, denominadas Pessoa, PapelPessoaUniversidade, Turma, Professor, Aluno e RaizUniversidade. As classes RaizUniversidade e Pessoa são abstratas (i.e., são utilizadas somente para reutilização da sua estrutura, não permitindo a criação de objetos). Neste modelo, uma Pessoa pode assumir simultaneamente vários papéis no sistema, onde os papéis são definidos como sub-classes de PapelPessoaUniversidade. É importante observar que UniversidadeRaiz é uma classe que não existe no domínio do problema, tendo sido criada somente com o objetivo de definir uma super-classe única que possa ser eventualmente utilizada para acomodar mudanças necessárias para todas as classes do sistema.
Figura 1 Modelo de Classes parcial usado para exemplificar o uso de BDOO
3.1 Visão Geral do Modelo de Dados implementado no Ozone
A figura 2 apresenta o modelo de classes UML que representa a implementação construída com o Ozone. Deve-se observar as principais modificações inseridas no modelo em relação ao modelo original descrito na figura 1:
- Uma classe (OzoneObject) e uma interface (OzoneRemote), disponibilizadas no pacote Ozone, foram inseridas no modelo;
- A implementação da classe Turma (da figura 1) foi mapeada para uma interface (Turma) e uma classe (TurmaImpl).
Figura 2 Modelo de dados efetivamente implementado em Java com Ozone para o exemplo
Nas seções a seguir, será descrita a implementação limitada do modelo apresentado na figura 2 referente às classes “Turma” e “Aluno”. Na implementação fornecida é possível incluir turmas, consultar e apagar as existentes e incluir alunos em uma determinada turma, através de um cadastro de aluno. No final, será mostrado exemplo de uma consulta simples.
3.2 Turma
O estilo de implementação adotado pelo Ozone exige que, para objetos Ozone, uma interface Java seja definida para descrever previamente os métodos usados na manipulação de objetos persistentes (isto é, armazenados no banco de dados). Assim, a listagem 2 apresenta o código da interface Turma.java enquanto que a listagem 3 descreve a classe TurmaImpl.java, as quais são descritas a seguir.
3.2.1 Interface Turma
O código para a interface Turma.java descreve nas duas primeiras linhas os pacotes necessários (fornecidos pela biblioteca Ozone). O cabeçalho da interface aparece na linha 3 fornecendo o nome (Turma) e a indicação de que a interface sendo criada é uma extensão da interface OzoneRemote. Nas linhas numeradas de 4 a 10 são fornecidas as assinaturas para os métodos de atualização e consulta de informações dos objetos.
Deve-se chamar a atenção para os comentários /*update*/ incluídos para as funções setNumero, setSemestre, addPessoa e removePessoa. Este comentário é usado por um pré-processador do Ozone para identificar os métodos que modificam o estado de um objeto armazenado.
import java.util.Vector;
import org.ozoneDB.OzoneRemote;
public interface Turma extends OzoneRemote {
public void setNumero(String num);/*update*/
public String getNumero();
public void setSemestre(String semestre);/*update*/
public String getSemestre();
public void addPessoa(Pessoa pessoa) throws Exception;/*update*/
public void removePessoa(Pessoa pessoa);/*update*/
public Vector getPessoas();
}
Listagem 2 O código-fonte para a interface Turma.java
3.2.2 Classe TurmaImpl
A implementação da funcionalidade original requerida para classe Turma (da figura 1) é completada pelo código da classe TurmaImpl.java fornecido na listagem 3. A linha 4 do código de TurmaImpl.java descreve o cabeçalho da classe que é herdeira de OzoneObject e implementa a interface Turma (descrita anteriormente).
Na listagem 3, as linhas numeradas de 5 a 9 são responsáveis por descrever os atributos necessários para a classe. Além dos atributos numero e semestre (linhas 7 e 8, respectivamente), são descritos o banco de dados (variável db - linha 6) e um tabela hash para manter a associação com os objetos de Pessoa (linha 9).
O método construtor da classe (linhas 10 a 21) é o responsável por abrir a conexão com o servidor Ozone. Por sua vez, os métodos especificados na interface Turma (setNumero, getNumero, setSemestre, getSemestre, addPessoa e removePessoa) possuem sua implementação descrita nas linhas numeradas de 23 a 48.
1. import org.ozoneDB.*;
2. import java.util.*;
3.
4. public class TurmaImpl extends OzoneObject implements Turma {
5. final static long serialVersionUID = 1L;
6. private static ExternalDatabase db;
7. private String numero;
8. private String semestre;
9. private Hashtable pessoas;
10. public TurmaImpl() {
11. try{
12. db = ExternalDatabase.openDatabase( "ozonedb:remote://localhost:3333");
13. System.out.println( "Connected ..." );
14. db.reloadClasses();
15. }
16. catch(Exception e) {
17. e.printStackTrace();
18. System.out.println("Not connected");
19. }
20. pessoas=new Hashtable();
21. }
22.
23. public void setNumero(String _numero){
24. numero=_numero; }
25.
26. public String getNumero(){
27. return numero; }
28.
29. public void setSemestre(String _semestre){
30. semestre=_semestre; }
31.
32. public String getSemestre(){
33. return semestre; }
34.
35. public void addPessoa(Pessoa pes)throws Exception {
36. pessoas.put( pes.getCPF(), pes ); }
37.
38. public Vector getPessoas(){
39. Vector result = new Vector();
40. Enumeration e=pessoas.elements();
41. while (e.hasMoreElements()) {
42. Pessoa pessoa = (Pessoa)e.nextElement();
43. result.add( pessoa );
44. }
45. return result;
46. }
47. public void removePessoa(Pessoa pessoa){
48. pessoas.remove(pessoa); }
49.}
Listagem 3 Código-fonte para TurmaImpl.java
3.2.3 Classe UITurma (interface com usuário)
A camada de apresentação para os objetos da classe Turma é descrita aqui de forma bastante simplificada. Assim, o objetivo prioritário é descrever como é feita a conexão entre a camada de apresentação e o banco de dados Ozone, sem se preocupar com os detalhes de como os componentes de interface são usados para compor o formulário.
Figura 3 Formulário para cadastro de objetos de Turma
Vale a pena destacar alguns pontos específicos da listagem 4. O construtor da classe (linhas 13 a 22) é responsável por chamar os métodos initBanco() e initExtent(), além de compor graficamente o formulário para interface com usuário (linha 21). O método initBanco() (linhas 24 a 34) é responsável por se conectar com o banco de dados (linha 26) e carregar as classes armazenadas (linha 28). O método actionPerformed() é, de acordo com o estilo de programação de eventos adotado por Java, invocado sempre que ocorrer um evento registrado no sistema. Se o evento corresponder ao clique no botão Salvar (evento jBtnSalvar - linha 38), será chamado o método createTurma().
1. import java.util.Date;
2. import java.awt.*;
3. import javax.swing.*;
4. import java.awt.event.*;
5. import org.ozoneDB.*;
6.
7. public class UITurma extends JFrame implements ActionListener {
8. private static ExternalDatabase db;
9. private Extent turmaExt; //extent para turma
10. private Turma turma;
11. …
12.
13. public UITurma() {
14. try {
15. initBanco();
16. initExtent();
17. }
18. catch(Exception e) {
19. e.printStackTrace();
20. }
21. // construção do formulário - não apresentado aqui
22. }
23.
24. public void initBanco(){
25. try{
26. db = ExternalDatabase.openDatabase( "ozonedb:remote://localhost:3333");
27. System.out.println( "Connected ..." );
28. db.reloadClasses();
29. }
30. catch(Exception e) {
31. e.printStackTrace();
32. System.out.println("Not connected");
33. }
34. }
35.
36. public void actionPerformed(java.awt.event.ActionEvent e){
37. Object source=e.getSource();
38. if (source==jBtnSalvar){
39. createTurma();
40. JOptionPane.showMessageDialog(null,"Turma cadastrada com sucesso","",1,null);
41. }
42. else // o código corresponde para buscar e remover objeto de Turma foi omitido
43.
44. public void initExtent() {
45. //Criar ou retornar o extent para turma
46. try{
47. turmaExt = (Extent)(db.createObject(ExtentImpl.class.getName(), 0,
48. "turmasExt"));
49. System.out.print("Criando extent para turmas...");
50. }
51. catch(Exception e) {
52. try{
53. turmaExt=(Extent)(db.objectForName( "turmasExt"));
54. System.out.print("Retornando extent de turmas...");
55. }
56. catch(Exception exc) {
57. exc.printStackTrace();
58. };
59. }
60. }
61.
62. public void createTurma() {
63. //criar uma nova turma
64. String numero=jNumero.getText();
65. try{
66. turmaExt.addTurma(numero,jSemestre.getText());
67. }
68. catch(Exception e) {
69. e.printStackTrace();
70. }
71. }
72. }
Listagem 4 Código-fonte parcial de IUTurma.java
3.3 Pessoa
A classe Pessoa - mostrada na listagem 5 - é responsável por criar objetos Java que serão usados para armazenar as informações necessárias para as instâncias. Deve-se observar que a classe explicitamente indica que implementa a interface Serializable (linha 5), atendendo a necessidade indicada na seção 2.2.
1. import java.io.*;
2. import java.util.Date;
3. import java.util.Vector;
4.
5. public class Pessoa implements Serializable{
6. private String nome;
7. private String cpf;
8. private Date nascimento;
9. private Vector papeis;
10.
11. public Pessoa(String _nome, String _cpf, Date _nascimento) {
12. nome=_nome;
13. cpf=_cpf;
14. nascimento=_nascimento;
15. papeis=new Vector();
16.. }
17.
18. public void setCPF(String _cpf){
19. cpf=_cpf;
20. }
21. public String getCPF(){
22. return cpf;
23. }
24.
25. public void setNome(String _nome){
26. nome=_nome;
27. }
28.
29. public String getNome(){
30. return nome;
31. }
32.
33. public void setNascimento(Date nasc){
34. nascimento=nasc;
35. }
36. public Date getNascimento(){
37. return nascimento;
38. }
39.
40. public void add(PapelPessoaUniversidade p){
41. papeis.addElement(p);
42. }
43.}
Listagem 5 Classe Pessoa.java
3.4 PapelPessoaUniversidade
No modelo original (figura 1), a classe abstrata PapelPessoaUniversidade foi criada para evitar transmutação de objetos. Além disso, esta classe permite ainda que uma pessoa possa exercer simultaneamente vários papéis (especializações da classe PapelPessoaUniversidade).
public abstract class PapelPessoaUniversidade {
private int matricula;
private Pessoa pessoa;
public Pessoa getPessoa(){
return pessoa;
}
public void setPessoa(Pessoa _pessoa){
pessoa=_pessoa;
}
public void setMatricula(int _matricula){
matricula=_matricula;
}
}
Listagem 6 Classe PapelPessoaUniversidade
3.5 Aluno
No problema apresentado através do diagrama da figura 1, existem dois tipos de papéis exercidos por pessoas em uma Universidade: Aluno e Professor. Do ponto de vista de implementação, esta característica é mapeada aqui para duas subclasses de PapelPessoaUniversidade. Por simplificação, este texto apresenta somente a subclasse Aluno, a qual é detalhada nas sub-seções a seguir.
3.5.1 Classe Aluno
Aluno é uma subclasse concreta de PapelPessoaUniversidade. Assim, no exemplo apresentado, a classe Pessoa é bastante simples, conforme apresentado pela listagem 7.
1. import java.io.*;
2. public class Aluno extends PapelPessoaUniversidade implements Serializable{
3. public Aluno() {
4. }
5. }
Listagem 7 Classe Aluno
3.5.2 Classe UIAluno (interface com usuário)
A figura 4 apresenta a representação gráfica implementada em uma classe denominada UIAluno. A interface permite o cadastro de alunos e alocação destes a turmas específicas (previamente cadastradas).
Figura 4 Formulário para cadastro de objetos de Aluno
Embora não seja objetivo deste texto descrever detalhadamente o código de UIAluno, deve-se destacar o trecho mostrado na listagem 8. A linha 1 é responsável por recuperar um objeto da classe Turma a partir do seu código. A partir da recuperação da turma correspondente, é enviada uma mensagem addPessoa para o objeto turma para adicionar o objeto de Pessoa passado como argumento.
1. Turma turma=(Turma)(db.objectForName( codTurma));
2. if (turma!=null){
3. turma.addPessoa(pes);
4. ...
5. }
Listagem 8 Código para inserir um Aluno em uma Turma existente
4. Considerações finais
Um BDOO possui, em geral, o objetivo de armazenar dados segundo um modelo muito parecido com o adotado pelas linguagens de programação OO, mantendo uma correspondência entre os conceitos usados no programa e sua representação em banco de dados. No caso do Ozone, o modelo de dados é exatamente o modelo adotado por Java.
Enquanto que esta característica é excelente para programadores Java, é óbvio que cria obstáculos para utilização por parte de programadores de outras linguagens de programação.
Deve-se observar que ainda existem diferenças entre o modelo de dados conceitual apresentado na figura 1 e o modelo efetivamente implementado em Java com Ozone (sintetizado na figura 2 e descrito em maior detalhe nas seções numeradas de 3.2 a 3.5). Algumas classes e interfaces adicionais são necessárias, mas muito da essência do modelo ainda permanece, e não há necessidade de classes específicas escritas pelo programador para lidar com banco de dados. Além disso, destaca-se que decisões de projeto podem influenciar na arquitetura do sistema resultante: no estudo de caso aqui apresentado optou-se por definir que somente a classe Turma constitui um objeto Ozone, isto é, recuperável a partir de consultas ao banco de dados. Isto influenciou no projeto da aplicação em virtude da necessidade em se criar uma interface (Turma) para indicar ao banco de dados as operações de manipulação que são efetivamente implementadas em TurmaImpl.
Do ponto de vista de utilização, o Ozone é bastante simples e esconde uma complexidade sofisticada que atua nos bastidores, o que o aproxima dos sistemas comerciais existentes. Por exemplo, o mecanismo de coleta automática de lixo (garbage collection) fornecido pelo Ozone atua de forma análoga à coleta de lixo implementada por Java, isto é, todo o trabalho é realizado sem interferência do usuário da aplicação ou o programador.
Deve-se ressaltar que, atualmente, Ozone é um banco de dados com uma base de usuários bastante pequena. Assim, o interesse pelo Ozone por parte dos autores deste texto surgiu a partir da experiência de uso deste banco de dados em um trabalho prático na disciplina de Programação Orientada a Objetos [2]. É possível que esta base de usuários aumente em virtude da recente direção tomada pelo projeto para tornar o Ozone um repositório de dados XML, isto é, qualquer ferramenta XML pode ser usada para manipular os dados inseridos no banco de dados.
A principal limitação da versão atual do Ozone é a falta de uma linguagem de consulta. Atualmente, todas as rotinas de consulta e manipulação com banco de dados devem ser escritas em Java, o que torna a tarefa cansativa. O ideal seria que também estivesse disponível alguma linguagem declarativa (tipo OQL [5]) que fosse uma alternativa ao paradigma imperativo adotado pelos algoritmos escritos na linguagem Java.
Referências
[1] Braeutigam, F.; Mueller, Gerd; Nyfelt, Per. Ozone Users Guide. Em http://www.ozone-db.org
[2] Acácio Junior, L. InTour: gerência de agência de turismo em Java. Trabalho prático. Disciplina Programação Orientada a Objetos. Curso de Bacharelado em Ciência da Computação. Universidade Federal do Pará (UFPA), 2003.
[4] Sun Microsystems. JavaTM Remote Method Invocation (RMI). Em http://java.sun.com/products/jdk/rmi/
[5] ODMG. Em http://www.odmg.org
[6] Computer Associates. Jasmine Object Database. Em http://www.ca.com/