Campos - ondas e dispositivos eletromagnéticos
Campos - ondas e dispositivos eletromagnéticos
Fundamentos da programação orientada a objetos
Fundamentos da programação orientada a objetos
Fundamentos da programação orientada a objetos

Bancos de dados
5.2 Acesso a bancos de dados
A linguagem Java vem se destacando como uma alternativa viável para a integração de aplicações novas e legadas na Internet. Essa infra-estrutura de integração não estaria completa se não contemplasse o acesso a sistemas de bancos de dados.
Um sistema de banco de dados é constituído por uma coleção organizada de dados (a base de dados) e pelo software que coordena o acesso a esses dados (o sistema gerenciador de banco de dados, ou SGBD). Utilizar um sistema de banco de dados ao invés de simples arquivos para armazenar de forma persistente os dados de uma aplicação apresenta diversas vantagens, das quais se destacam:
o desenvolvedor da aplicação não precisa se preocupar com os detalhes do armazenamento, trabalhando com especificações mais abstratas para a definição e manipulação dos dados; tipicamente, um SGBD incorpora mecanismos para controle de acesso concorrente por múltiplos usuários e controle de transações; é possível compartilhar dados comuns entre mais de uma aplicação mesmo que a visão dos dados de cada aplicação seja distinta. Existem soluções para integrar aplicações Java a bancos de dados orientados a objetos e a bancos de dados relacionais. Neste caso, a solução é padronizada através da especificação JDBC.
5.2.1 Bancos de dados relacionais
Um banco de dados relacional organiza seus dados em relações. Cada relação pode ser vista como uma tabela, onde cada coluna corresponde a atributos da relação e as linhas correspondem às tuplas ou elementos da relação. Em uma nomenclatura mais próximas àquela de sistemas de arquivos, muitas vezes as tuplas são denominadas registros e os atributos, campos. Um conceito importante em um banco de dados relacional é o conceito de atributo chave, que permite identificar e diferenciar uma tupla de outra. Através do uso de chaves é possível acelerar o acesso a elementos (usando índices) e estabelecer relacionamentos entre as múltiplas tabelas de um sistema de banco de dados relacional.
Essa visão de dados organizados em tabelas oferece um conceito simples e familiar para a estruturação dos dados, sendo um dos motivos do sucesso de sistemas relacionais de dados. Certamente, outros motivos para esse sucesso incluem o forte embasamento matemático por trás dos conceitos utilizados em bancos de dados relacionais e a uniformização na linguagem de manipulação de sistemas de bancos de dados relacionais através da linguagem SQL.
Sob o ponto de vista matemático, uma relação é o subconjunto do produto cartesiano dos domínios da relação. Sendo um conjunto, é possível realizar operações de conjuntos — tais como união, interseção e diferença — envolvendo duas relações de mesma estrutura. No entanto, um dos pontos mais fortes do modelo relacional está nos mecanismos de manipulação estabelecidos pela Álgebra Relacional. Os três principais operadores da álgebra relacional são seleção, projeção e junção.
A operação de seleção tem como argumento uma relação e uma condição (um predicado) envolvendo atributos da relação e/ou valores. O resultado é uma outra relação contemplando apenas as tuplas para as quais a condição foi verdadeira.
A operação de projeção tem como argumento uma relação e uma lista com um subconjunto dos atributos da relação. O resultado é outra relação contendo todas as tuplas da relação mas apenas com os atributos especificados. Observe que se a lista de atributos não englobar a chave da relação, o resultado dessa operação poderia gerar tuplas iguais. Sob o ponto de vista estritamente matemático, os elementos duplicados devem ser eliminados, pois não fazem sentido para um conjunto.
A operação de junção recebe como argumentos duas relações e uma condição (um predicado) envolvendo atributos das duas relações. O resultado é uma relação com os atributos das duas relações contendo as tuplas que satisfizeram o predicado especificado. A operação de junção não é uma operação primitiva, pois pode ser expressa em termos da operação de produto cartesiano e da seleção, mas é uma das operações mais poderosas da álgebra relacional.
A forma mais usual de junção é aquela na qual a condição de junção é a igualdade entre valores de dois atributos das relações argumentos. Essa forma é tão usual que recebe o nome de junção natural. Nesse caso, o atributo comum aparece apenas uma vez na relação resultado, já que ele teria para todas as tuplas o mesmo valor nas duas colunas.
5.2.2 SQL
SQL é uma linguagem padronizada para a definição e manipulação de bancos de dados relacionais. Tipicamente, um SGBD oferece um interpretador SQL que permite isolar a aplicação dos detalhes de armazenamento dos dados. Se o projetista da aplicação tiver o cuidado de usar apenas as construções padronizadas de SQL, ele poderá desenvolver a aplicação sem se preocupar com o produto SGBD que estará sendo utilizado depois.
As três componentes de SQL são:
1. uma linguagem de definição de dados (DDL) para definir e revisar a estrutura de bancos de dados relacionais;
2. uma linguagem de controle de dados (DCL) p/ especificar mecanismos de segurança/integridade dos dados;
3. uma linguagem de manipulação de dados (DML) para ler e escrever os dados.
A DDL supre as facilidades para a criação e manipulação de esquemas relacionais. Uma das necessidades de uma aplicação que irá armazenar seus dados em um sistema de banco de dados relacional é como criar uma identidade para o conjunto de tabelas de sua aplicação. Esse mecanismo não é padronizado em SQL, podendo variar de fornecedor a fornecedor de banco de dados.
Algumas possibilidades incluem
CREATE SCHEMA name [AUTHORIZATION {user | group}]
ou
CREATE DATABASE name [On {default | device}]
[Log On device [= size]]
Para criar uma tabela, o comando CREATE TABLE é utilizado:
CREATE TABLE name ( ATTRIBUTE DOMAIN [UNIQUE] [{NULL | NOT NULL}])
Alguns fabricantes adotam, ao invés da forma UNIQUE para indicação de chave da relação, um comando Primary Key após a criação da tabela para indicar qual atributo formará ou quais atributos comporão a chave primária da relação. O campo ATTRIBUTE especifica o nome para a aplicação da coluna da tabela, enquanto DOMAIN especifica o tipo de dado para a coluna. Alguns tipos de dados usuais em SQL são:
INTEGER,
DECIMAL(p,q) — dos dígitos são casas decimais,
FLOAT(p) — é a precisão,
CHARACTER(n) - string de caracteres,
BIT(n) — arranjo de valores booleanos,
DATE, TIME e TIMESTAMP.
Além desses comandos, ALTER TABLE permite modificar a estrutura de uma tabela existente e
DROP TABLE permite remover uma tabela.
O principal comando da DML de SQL é SELECT. Por exemplo, para obter todos os dados de uma relação utiliza-se a forma básica:
SELECT * FROM table
Através do mesmo comando, é possível especificar projeções e seleções de uma tabela:
SELECT columns FROM table WHERE condition
A condição pode envolver comparações entre atributos e/ou valores constantes, podendo para tanto utilizar comparadores tais como igual (=), maior que (>), menor que (<), maior ou igual que (>=), menor ou igual que (<=), diferente (!= ou <>) e comparadores de strings (LIKE ‘string’, onde ‘string’ pode usar os caracteres ‘_’ e ’%’ para indicar um caráter qualquer os uma seqüência qualquer de caracteres, respectivamente). As comparações podem ser combinadas através dos conectivos lógicos And, Or e Not.
É possível expressar as quatro operações aritméticas (+, -, *, /), tanto para a condição como para
a especificação de recuperação dos dados. É possível também especificar a ordem desejada de apresentação dos dados, usando para tal a for- ma SELECT...ORDER BY.... Uma alternativa a esse comando é SELECT...GROUP BY...,
que ordena os dados e não apresenta elementos que tenham o mesmo valor para a cláusula de agrupamento.
Uma importante categoria de funções de SQL incluem as funções de agregação, que permitem computar a média (Avg), a quantidade (Count), o maior ou menor valor (Max ou Min) e o total (Sum) das expressões especificadas.
Através do mesmo comando Select, é possível especificar consultas envolvendo múltiplas tabelas, como em
SELECT nome, data FROM Pedidos, Clientes WHERE Pedidos.NumCliente = Clientes.NumCliente
Nesse caso, a junção das duas tabelas Pedidos e Clientes é realizada tendo como Num-Cliente como atributo de junção.
É possível especificar consultas internas a uma consulta, como em
SELECT NumProduto From Produtos
WHERE PrecoUnit > (SELECT Avg(PrecoUnit) FROM Produtos)
Para qualificar os resultados de uma subconsulta, podem ser utilizadas as funções All (a condição foi verdadeira para todos os elementos resultantes da subconsulta), Any (verdade para pelo menos um elemento), Exists (verdade se resultado da consulta tem pelo menos um elemento), In (verdade se o valor especificado faz parte do resultado), Not Exists (verdade se subconsulta teve resultado vazio) e Not In (verdade se valor especificado não faz parte do resultado da subconsulta).
SQL oferece ainda mecanismos para inserir elementos em uma tabela,
INSERT INTO table (columns) VALUES (values)
para modificar valores de colunas de um elemento,
UPDATE table SET column = value [, column = value]* WHERE condition
e para remover elementos de uma tabela,
DELETE FROM table WHERE condition
SQL pode ser utilizado diretamente pelo usuário, quando o SGBD oferece um interpretador SQL interativo, ou através de comandos embutidos em uma aplicação desenvolvida em uma linguagem de programação. No caso dessa linguagem ser Java, a forma de interagir com o banco de dados é especificado por JDBC.
5.2.3 JDBC
Java permite o acesso a bancos de dados relacionais através das funcionalidades definidas no pacote java.sql, que define o “produto JDBC”. JDBC é uma API para execução e manipulação de resultados a consultas SQL através de Java. Para desenvolver uma aplicação com Java e bancos de dados relacionais, é preciso ter disponível: O pacote JDBC (padrão na distribuição da plataforma de desenvolvimento Java desde sua versão 1.1); Acesso ao servidor, o sistema gerenciador de banco de dados que entende SQL; e O driver JDBC adequado ao tipo de SGBD acessado. Uma vez que esses recursos estejam disponíveis, a aplicação Java tem acesso ao banco de dados relacional através da execução dos seguintes passos:
1. Habilitar o driver;
2. Estabelecer uma conexão com o banco de dados;
3. Executar consulta SQL; e
4. Apresentar resultados da consulta.
Driver JDBC
Do ponto de vista da aplicação Java, um driver nada mais é do que uma classe cuja funcionalidade precisa ser disponibilizada para a aplicação. A funcionalidade básica que um driver deve oferecer é especificada através da interface Driver. A classe DriverManager estabelece um conjunto básico de serviços para a manipulação de
drivers JDBC. Como parte de sua inicialização, essa classe tentará obter o valor da propriedade jdbc.drivers de um arquivo de definição de propriedades e carregar os drivers especificados pelos nomes das classes.
Alternativamente, um driver pode ser carregado explicitamente para a JVM; a forma usual para executar essa tarefa é através do método forName() da classe Class, como em
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Conexão com banco de dados
Uma vez que o driver esteja carregado, a aplicação Java pode estabelecer uma conexão com o gerenciador de banco de dados. Para especificar com qual banco de dados deseja-se estabelecer a conexão, é utilizada uma string na forma de um URL na qual o protocolo é jdbc: e o restante da string é dependente do driver. Por exemplo, o driver jdbc:odbc especifica o formato jdbc:odbc:dsn, onde dsn é o data source name que identifica o banco de dados. Identificado o banco de dados, a sessão a ser estabelecida para o acesso ao banco de dados será controlada por um objeto de uma classe que implementa a interface Connection. O DriverManager oferece o método getConnection() para executar essa tarefa.
O encerramento de uma sessão é sinalizado pelo método close() da conexão:
import java.sql.*;
...
String DB = "jdbc:...";
...
Connection c = DriverManager.getConnection(DB);
...
c.close(); // encerra a sessão
Execução da consulta
Estabelecida a conexão ao banco de dados, é possível criar uma consulta e executá-la a partir da aplicação Java. Para representar uma consulta, o JDBC utiliza um objeto de uma classe que implementa a interface Statement. Um objeto dessa classe pode ser obtido através do método createStatement() da classe Connection. Uma vez que um objeto Statement esteja disponível, é possível aplicar a ele o método executeQuery(), que recebe como argumento uma string representando uma consulta SQL. O resultado da execução da consulta é disponibilizado através de um objeto ResultSet.
import java.sql.*;
...
Connection c; c = ...;
Statement s = c.createStatement(); String query;
query = ...;
ResultSet r = s.executeQuery(query);
...
s.close();
Os métodos da interface ResultSet permitem a manipulação dos resultados individuais de uma tabela de resultados. Métodos como getDouble(), getInt(), getString() e getTime(), que recebem como argumento a especificação de uma coluna da tabela, permitem acessar o valor da coluna especificada na tupla corrente para os diversos tipos de dados suportados. Para varrer a tabela, um cursor é mantido. Inicialmente, ele está posicionado antes do início da tabela, mas pode ser manipulado pelos métodos first(), next(), previous(), last() e absolute(int row).
Por exemplo,
ResultSet r = s.executeQuery("Select * from Clientes"); System.out.println("ID NOME");
while (r.next()) System.out.println(r.getString("ClienteID")+" " + r.getString("Nome")); r.close();
Para lidar com atributos que podem assumir valores nulos, o método wasNull() é oferecido. Ele retorna verdadeiro quando o valor obtido pelo método getXXX() for nulo, onde XXX é um dos tipos SQL.
A interface ResultSetMetadata permite obter informação sobre a tabela com o resultado da consulta. Um objeto desse tipo pode ser obtido através da aplicação do método getMetaData() ao ResultSet:
ResultSetMetaData m = r.getMetaData();
Uma vez obtido esse objeto, a informação desejada pode ser obtida através de métodos tais como getColumnCount(), getColumnLabel(), getColumnTypeName() e getColumnType(). O último método retorna tipos que podem ser identificados a partir de constantes definidas para a classe java.sql.Types.
Além da forma Statement, JDBC oferece duas formas alternativas que permitem respectivamente ter acesso a comandos SQL pré-compilados (PreparedStatement) e a procedimentos armazenados no banco de dados (CallableStatement).
Exemplo completo
Este exemplo ilustra o mecanismo básico para uma aplicação Java acessar um banco de dados. O objetivo é apresentar o conteúdo da seguinte relação, a tabela “notas” do banco de dados “poojava”:
> java PoojavaDB
fname lname activ grd
Joao Silva 1 6
Joao Silva 2 7
Pedro Souza 1 8
Pedro Souza 2 5
Maria Santos 1 7
Maria Santos 2 8
O resultado acima foi obtido a partir da execução da seguinte aplicação, acessando um gerenciador de banco de dados PostgreSQL:
1 import java.sql.*;
2 public class PoojavaDB {
3 public static void main(String[] args) {
4 try {
5 // carrega driver
6 Class.forName("postgresql.Driver");
7 // estabelece conexao com banco de dados
8 Connection c =
9 DriverManager.getConnection("jdbc:postgresql:poojava",
10 "ricarte","");
11 // monta e executa consulta
12 Statement s = c.createStatement();
13 ResultSet r = s.executeQuery("Select * from notas");
14 // apresenta estrutura da tabela
15 ResultSetMetaData m = r.getMetaData();
16 int colCount = m.getColumnCount();
17 for (int i=1; i<=colCount; ++i)
18 System.out.print(m.getColumnName(i) + "\t\t");
19 System.out.println();
20 // apresenta resultados da consulta
21 while (r.next())
22 System.out.println(r.getString(1) + " " +
23 r.getString(2) + " " +
24 r.getInt(3) + "\t\t" +
25 r.getInt(4));
26 r.close();
27 // fecha conexao com banco de dados
28 c.close();
29 }
30 catch (Exception e) {
31 System.err.println(e);
32 }
33 }
34 }
5.3 Servlets
Servlets oferecem uma maneira alternativa a CGI para estender as funcionalidades de um servidor Web. Na verdade, a API de servlet de Java oferece mecanismos adequados à adaptação qualquer servidor baseado em requisições e respostas, mas é em aplicações Web que servlets têm sido mais utilizados. CGI (Common Gateway Interface) é a especificação de uma interface que permite que servidores Web tenham acesso a funcionalidades oferecidas por programas executando no ambiente da máquina servidora. Através de programas conectados a essa interface é possível por exemplo conectar uma base de dados à Web ou gerar dinamicamente o conteúdo de uma página HTML.
O servidor Web reconhece uma requisição CGI quando o URL especificado na solicitação identifica um arquivo executável (programa ou script) localizado em um diretório específico dentro do espaço Web de recursos disponibilizados aos clientes. Parâmetros podem ser repassados ao programa CGI especificando-os no URL, separados do nome do recurso pelo caráter ’?’.
Tipicamente um programa CGI pode ser desenvolvido em qualquer linguagem de programação
que tenha acesso à leitura de variáveis de ambiente e à manipulação dos streams padrões de entrada e saída de dados do sistema operacional (stdin, System.in; stdout, System.out). Com o uso de servlets, a arquitetura da Web torna-se um base atrativa para o desenvolvimento de aplicações distribuídas em Java. A utilização de browsers HTML simplifica o desenvolvimento das aplicações cliente. Servidores Web suportam os mecanismos básicos de conexão ao cliente. Assim, o desenvolvimento irá se concentrar na extensão dos serviços através dos servlets.
5.3.1 Ciclo de vida de um servlet
A execução de um servlet não difere muito de uma aplicação CGI em sua forma de interação com o servidor. As quatro principais etapas nessa interação são:
1. cliente envia solicitação ao servidor;
2. servidor invoca servlet para a execução do serviço solicitado;
3. servlet gera o conteúdo em resposta à solicitação do cliente; e
4. servidor envia resultado do servlet ao cliente.
Quando um servlet é carregado pela primeira vez para a máquina virtual Java do servidor, o seu método init() é invocado. Esse método tipicamente prepara recursos para a execução do serviço (por exemplo, abrir arquivos ou ler o valor anterior de um contador de número de acessos) ou estabelece conexão com outros serviços (por exemplo, com um servidor de banco de dados). O método destroy() permite liberar esses recursos (fechar arquivos, escrever o valor final nessa sessão do contador de acessos), sendo invocado quando o servidor estiver concluindo sua atividade.
Uma diferença fundamental entre um servlet e uma aplicação CGI é que a classe que implementa o servlet permanece carregada na máquina virtual Java após concluir sua execução. Um programa CGI, ao contrário, inicia um novo processo a cada invocação — por este motivo, CGI deve utilizar mecanismos adicionais para manter o estado entre execuções, sendo a forma mais comum a utilização de arquivos em disco. Com um servlet, tais mecanismos são necessários apenas na primeira vez que é carregado e ao fim da execução do servidor, ou eventualmente como um mecanismo de checkpoint.
Servlets também oferecem como vantagem o fato de serem programas Java. Assim, eles permitem a utilização de toda a API Java para a implementação de seus serviços e oferecem adicionalmente portabilidade de plataforma.
5.3.2 Fundamentos da API de servlets
O suporte a servlets é uma extensão padronizada ao pacote Java, não sendo parte da distribuição básica do Java SDK. Assim, quem quiser desenvolver servlets deve obter o JSDK, o Java Servlet Development Kit. Adicionalmente, o servidor Web deve suportar o acesso a servlets, o que pode ocorrer de forma nativa (como no caso do Java Web Server) ou através de módulos add-on (como no caso de apache).
O JSDK inclui as classes que implementam a API de servlets organizadas em pacotes, dos quais
os principais são javax.servlet e javax.servlet.http. Adicionalmente, uma aplicação
servletrunner permite desenvolver e testar servlets antes de integrá-los a servidores.
Os três mecanismos alternativos básicos para criar um servlet são:
1. Estender a classe javax.servlet.GenericServlet, quando o servlet não for implementar nenhum protocolo específico de comunicação;
2. Estender a classe javax.servlet.HttpServlet, para servlets que manipulam dados específicos do protocolo HTTP; ou
3. Implementar a interface javax.servlet.Servlet.
Na primeira forma básica de implementar um servlet, estendendo a classe GenericServlet, o método service() deve ser definido. Esse método descreve o serviço que o servlet estará oferecendo.
Esse exemplo ilustra o código de um servlet que responde à solicitação de serviço com uma mensagem fixa:
1 import javax.servlet.*;
2 import java.io.*;
3 public class OiServlet extends GenericServlet {
4 public void service(ServletRequest solicitacao,
5 ServletResponse resposta)
6 throws ServletException, IOException {
7 resposta.setContentType("text/plain");
8 PrintWriter saida = resposta.getWriter();
9 saida.println("Oi!");
10 }
11 }
Nesse exemplo, o método getWriter() é utilizado para estabelecer o canal de envio de dados desde o servlet - no caso, um objeto PrintWriter, permitindo o envio de textos. Alternativamente, dados binários poderiam ser enviados através de um OutputStream, obtido através do método getOutputStream().
A classe HttpServlet é uma extensão de GenericServlet especificamente projetada para a conexão de servlets a servidores HTTP. Assim, métodos dedicados a lidar com solicitações HTTP, tais como doGet(), doPost() e doPut(), são definidos. A implementação padrão do método service() reconhece qual o tipo de solicitação recebida e invoca o método correspondente.
Este exemplo ilustra a utilização de um servlet que envia uma mensagem fixa no corpo de uma página HTML em resposta a uma requisição GET ao servidor Web, usando para tal o método doGet():
1 import javax.servlet.*;
2 import javax.servlet.http.*;
3 import java.io.*;
4 public class OiHttpServlet extends HttpServlet {
5 public void doGet(HttpServletRequest solicitacao,
6 HttpServletResponse resposta)
7 throws ServletException, IOException {
8 resposta.setContentType("text/html");
9 PrintWriter saida = resposta.getWriter();
10 saida.println("<HTML>");
11 saida.print("<HEAD><TITLE>");
12 saida.print("Resposta do servlet");
13 saida.println("</TITLE></HEAD>");
14 saida.println("<BODY><P>Oi!</P></BODY>");
15 saida.println("</HTML>");
16 }
17 }
Outros métodos que suportam a interação do servlet através de solicitações HTTP incluem getLastModified(), que é invocado pelo servidor para obter a data da última modificação do “documento” (um número negativo se não houver informação ou long com o número de segundos desde 1 de janeiro de 1970 GMT); e getServletInfo(), que retorna uma string de documentação sobre o servlet, tal como nome, autor e versão.
A passagem de dados de um formulário do cliente para o servlet pode se dar através do método getParameter(), que permite obter uma string com o valor do campo especificado. Por exemplo, se um formulário HTML especificasse um campo de texto para entrada do nome do usuário, como em
<INPUT type="text" name="username" size=20>
esse valor poderia ser obtido no código do servlet do exemplo anterior através da invocação
String nome = solicitacao.getParameter("username");
Além de getParameter(), o método getParameterValues() retorna um arranjo de strings com todos os valores de um determinado parâmetro. Outros métodos são oferecidos para obter informação das aplicações que estão invocando o servlet, tais como:
getRemoteHost(),
getServerName(),
getServerPort() e,
especificamente para HTTP, getHeaderNames() e getHeader().
A forma de integrar o servlet ao servidor Web é dependente da implementação; por exemplo, alguns servidores especificam um diretório (tal como servlet) onde as classes servlets são con- centradas e a invocação dá-se pela invocação direta da URL, como em
http://site/servlet/OiHttpServlet