DSI - SIM - Universidade do Minho

Transcrição

DSI - SIM - Universidade do Minho
Universidade do Minho
Escola de Engenharia
Departamento de Informática
DSI
LESI/LMCC
2002/03
António Ramires Fernandes
Índice
1
Introdução..........................................................................................................3
1.1
Descrição Sumária da API JDBC 2.0 ............................................................................. 3
1.2
Drivers JDBC.................................................................................................................. 6
Tipos de Drivers ..................................................................................................................... 7
1.3
Introdução ao SQL.......................................................................................................... 8
Operações Sobre Tabelas........................................................................................................ 9
Operações Sobre Dados - Selecção...................................................................................... 10
Operações Sobre Dados - Manutenção ................................................................................. 17
2
Utilização Básica da API JDBC .....................................................................21
2.1
Conectividade com Bases de Dados ............................................................................. 21
Estabelecer uma Ligação ...................................................................................................... 21
Terminar a ligação ................................................................................................................ 28
Exemplo ................................................................................................................................ 29
2.2
Definição e Envio de comandos SQL........................................................................... 30
Método específico para Envio de comandos de selecção ..................................................... 31
Método Específico para Envio de Comandos de Actualização ............................................ 32
Método Genérico .................................................................................................................. 33
2.3
Análise de Resultados de Comandos SELECT ............................................................ 35
Exemplo Completo ............................................................................................................... 39
2.4
Meta Dados dos Resultados .......................................................................................... 40
2.5
Excel via JDBC............................................................................................................. 43
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
3
Definição Avançada de Comandos SQL .......................................................46
3.1
Pré-processamento de Comandos SQL......................................................................... 46
3.2
Comandos SQL Parametrizados ................................................................................... 50
3.3
Invocação de Stored Procedures .................................................................................. 54
Stored Procedures em Microsoft Access .............................................................................. 60
3.4
4
Envio de Comandos em Batch...................................................................................... 62
Manipulação Avançada de Resultados de Comandos SELECT ................67
4.1
Resultados Navegáveis Aleatoriamente........................................................................ 70
Navegação Relativa .............................................................................................................. 70
Navegação Absoluta ............................................................................................................. 71
Exemplos............................................................................................................................... 72
Posição Actual do Cursor...................................................................................................... 74
5
Transacções......................................................................................................76
6
Referências.......................................................................................................81
2
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
1 IN TRO D U Ç Ã O
Na sua primeira versão, a linguagem JAVA não incluía nenhuma forma de acesso a bases de
dados. O JDBC, Java DataBase Connectivity, surgiu, inicialmente, como uma API adicional. No
entanto cedo se tornou claro que esta API era essencial. O acesso a bases de dados abria um
leque extenso de novas aplicações para a linguagem JAVA. O desenvolvimento da Internet, com
a proliferação de sites com conteúdos dinâmicos, também veio reforçar a necessidade desta API.
O JDBC tornou-se parte integrante do Java SDK, Software Development Kit, na versão 1.2. A
sua evolução não foi tão dramática como a do próprio JAVA, em parte por se tratar desde o
início de uma API bastante completa. No entanto as alterações implementadas justificam
plenamente esta nova edição. Algumas das novidades mais relevantes da versão 2.0 são as
seguintes:
•
Maior flexibilidade ao lidar com resultados de comandos SQL;
•
Possibilidade de realizar tarefas em modo Batch;
•
Capacidade de trabalhar com os tipos de dados introduzidos em SQL3.
1.1 D ESC R IÇ Ã O SU M Á R IA D A A PIJD BC 2.0
Em termos de divisão lógica, a API JDBC 2.0 encontra-se dividida em dois pacotes:
java.sql
javax.sql
O primeiro pacote, java.sql, encontra-se incluído na edição Standard da linguagem JAVA
2.0. O segundo pacote, javax.sql, encontra-se incluído na edição Enterprise, podendo no
entanto ser obtido separadamente. Ambas as versões estão disponíveis na secção JDBC do site
da SUN (http://java.sun.com/products/jdbc).
3
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Em termos de funcionalidade pode-se considerar que o segundo pacote é uma extensão ao
primeiro por forma a aumentar a robustez e escalabilidade das aplicações. Neste livro só será
tratado o pacote java.sql.
O pacote java.sql contêm um conjunto de classes e interfaces que permitem definir ligações a
bases de dados e realizar operações sobre a mesma.
Para estabelecer ligações a uma base de dados, e obter informação sobre as ligações activas, são
disponibilizadas as seguintes classes e interfaces:
•
classe DriverManager
•
interface Driver
•
classe DriverPropertyInfo
•
interface Connection
Uma vez estabelecida a ligação a uma base de dados é possível o envio de comandos SQL à base
de dados. A API JDBC 2.0 permite a definição de comandos SQL de diversas formas, incluindo
a pré-compilação de comandos, e o envio em modo batch de um conjunto de comandos. As
seguintes classes e interfaces são disponibilizadas para este efeito:
•
interface Statement
•
interface PreparedStatement
A invocação de stored procedures (rotinas definidas na própria base de dados pelos utilizadores,
gestores de base de dados ou até pelos próprios fabricantes), também está prevista na API JDBC
2.0. Para este efeito a API disponibiliza a seguinte interface:
•
interface CallableStatement
4
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Uma vez enviado um comando à base de dados é necessário processar o seu resultado. Os
métodos para consulta e manipulação de resultados dos comandos SQL encontram-se definidos
na seguinte interface:
•
interface ResultSet
Esta interface serve de uma forma geral para todos os métodos que retornem resultados não
atómicos.
Para análise dos meta dados são fornecidas duas interfaces. Uma para os meta dados da base de
dados propriamente dita, e uma outra para resultados não atómicos, i.e. para objectos que
implementem a interface ResultSet.
•
interface DataBaseMetaData
•
interface ResultSetMetaData
A interface DataBaseMetaData também define um conjunto de métodos para inquirir o driver
sobre o suporte a certas funcionalidades previstas pela API JDBC 2.0
Devido à especificidade desta API, quatro excepções foram definidas. As seguintes excepções
são de tratamento obrigatório:
•
classe SQLException
•
classe BatchUpdateException
As restantes excepções são meramente avisos e podem ser ignoradas pelo programador. Note-se
no entanto que esta liberdade oferecida pela API deve ser usada conscientemente uma vez que o
lançamento destas excepções implica sempre a presença de algum problema, embora não fatal
para a aplicação. Os avisos definidos pela API são:
5
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
•
class SQLWarning
•
class DataTruncation
Existem ainda um conjunto de interfaces e classes que definem novos tipos de dados em JAVA
de forma a facilitar a utilização de certos tipos de dados em SQL como por exemplo BLOB
(Binary Large Objects), Date e Array. A API prevê também a definição de tipos de dados criados
pelo utilizador da base de dados.
1.2 D R IVER S JD BC
A API JDBC 2.0 não implementa a ligação à base de dados. Tal como se pode observar na
listagem de conteúdos da API apresentada anteriormente, a maior parte das suas componentes
são interfaces, e não classes. Quer isto dizer que a API define a assinatura dos métodos, e por
consequência define implicitamente o modo de interacção programática com a API, mas deixa a
parte da implementação propriamente dita dos métodos definidos nas interfaces a cargo dos
drivers JDBC.
A distribuição da API inclui somente um driver para bases de dados acessíveis via ODBC, Open
DataBase Conectivity. A inclusão deste driver é estratégica, uma vez que isso garantia à priori, e
sem depender de terceiros para desenvolver drivers específicos, que a comunidade Windows
podia trabalhar com um grande número de bases de dados só com a distribuição da API JDBC.
Actualmente, encontra-se disponível um grande número de drivers para as mais variadas bases
de dados. No entanto, convém referir que a implementação dos drivers não tem necessariamente
de ser completa, por consequência nem todos os drivers implementam todas as funcionalidades
descritas neste capítulo. Pode também dar-se o caso de a própria base de dados não suportar toda
a funcionalidade presente na API JDBC 2.0.
6
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Os requisitos a que um driver deve obedecer são os seguintes:
1. As seguintes interfaces devem ser completamente implementadas;
•
•
•
Driver
DatabaseMetaData
ResultSetMetaData
2. As seguintes interfaces devem ser implementadas:
•
•
•
•
•
CallableStatement
Connection
PreparedStatement
ResultSet
Statement
No entanto, a implementação destas interfaces, por ser dependente da funcionalidade da própria
base de dados, não tem forçosamente de ser completa. Os métodos relativos a funcionalidades
não disponíveis na base de dados podem ser implementados de forma a lançarem uma excepção,
aquando da sua invocação. Através dos métodos definidos nas interfaces relativas aos meta
dados é possível verificar se uma determinada funcionalidade é disponibilizada pelo driver.
Todas as restantes interfaces definidas na API JDBC são opcionais, podendo inclusive ser
totalmente omitidas.
T IPO S D E D R IVER S
Os drivers para efectuar uma ligação a uma base de dados através do JDBC podem-se dividir em
quatro grandes categorias:
•
JDBC-ODBC: Este tipo de drivers permite a ligação a base de dados cujo driver
OBDC esteja instalado. Permite aceder a todas as bases de dados registadas em
ODBC. Este driver está incluído no Java 2.0.
7
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
•
API nativa da Base de Dados: Drivers que permitem a ligação directa a uma base de
dados local. Estes drivers são fornecidos pelas entidades responsáveis pelas próprias
bases de dados ou por terceiros.
•
JDBC-Net: Este tipo de drivers converte as instruções JDBC para um protocolo
independente do sistema de base de dados. Para completar o circuito de ligação é
necessário instalar um segundo driver que traduza este protocolo para o protocolo
especifico da base de dados desejada.
•
Protocolo de Rede Nativo: Através deste tipo de drivers consegue-se uma ligação
directa à base de dados utilizando o protocolo de rede proprietário da base de dados.
Desta forma não é necessário instalar nenhum software adicional para bases de dados
locais ou remotas.
1.3 IN TR O D U Ç Ã O A O SQ L
O SQL, Structured Query Language, é utilizado na maior parte dos sistemas de bases de dados
para realizar operações de consulta, manutenção e actualização. A API JDBC2.0 também recorre
ao SQL como forma de comunicação com as bases de dados. Nesta secção será apresentada uma
curta introdução ao SQL. O pequeno tutorial que se segue não cobre de forma alguma todas as
potencialidades do SQL. A sua inclusão neste capítulo deve-se à forte dependência do JDBC em
relação ao SQL.
A curta introdução aqui apresentada inclui:
•
criação, alteração e remoção de tabelas;
•
selecção de dados presentes nas tabelas.
•
inserção, remoção e alteração de dados nas tabelas;
8
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O PER A Ç Õ ES SO BR E T A BELA S
A sintaxe SQL para definir uma tabela é a seguinte:
CREATE TABLE nomeDaTabela (
listaDeEspecificaçãoDeAtributos
);
Nota: O simbolo ";" não é necessário quando se utiliza SQL em JDBC.
Na sua forma mais simples, a especificação de um atributo consiste no nome e tipo de dados. Por
exemplo, para definir um atributo modelo, com tipo de dados texto, de comprimento 50
escrevemos:
modelo
CHAR(50)
A palavra reservada CHAR define o tipo de dados como sendo texto. O comprimento do texto é
definido entre parêntesis. Para definir uma tabela para armazenar informação sobre produtos
informáticos contendo para cada produto o código, modelo, e preço podemos então escrever:
CREATE TABLE Produtos (
código
CHAR(9),
modelo
VARCHAR(50),
preço
FLOAT
);
O tipo de dados VARCHAR introduzido acima, representa um texto de comprimento variável,
tendo como comprimento máximo o valor indicado entre parêntesis.
A chave primária de uma tabela é definida através das palavras chave PRIMARY KEY. No
exemplo acima, caso pretendêssemos definir o atributo código como uma chave primária,
poderíamos reescrever o comando da seguinte forma :
CREATE TABLE Produtos (
código
CHAR(9) PRIMARY KEY,
modelo
VARCHAR(50),
preço
FLOAT
);
9
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O SQL permite ainda definir valores a aplicar por omissão aos através da palavra-chave
DEFAULT. Por exemplo, se considerarmos um atributo com uma só letra para indicar se o
produto está em promoção, cujo valor por omissão é 'N'. A nova definição da tabela Produtos é
agora:
CREATE TABLE Produtos (
código
CHAR(9) PRIMARY KEY,
modelo
VARCHAR(50),
preço
FLOAT,
promoção
CHAR(1) DEFAULT 'N'
);
Devido à estrutura dinâmica dos problemas que nos rodeiam, é por vezes necessário alterar a
estrutura de tabelas já existentes, quer adicionando, ou removendo atributos.
A sintaxe para estas operações envolve as palavras-chave ALTER TABLE. Para adicionarmos um
atributo com a marca do produto poderíamos escrever o seguinte comando SQL:
ALTER TABLE Produtos ADD marca CHAR(20);
A remoção de um atributo tem uma sintaxe semelhante. Por exemplo para remover o atributo
promoção, previamente definido na tabela Produtos definiríamos o seguinte comando SQL:
ALTER TABLE Produtos DROP promoção;
Por vezes o número de alterações a uma tabela é de tal forma grande que mais vale reescrever a
tabela do principio. Sendo assim, torna-se por vezes necessário remover tabelas previamente
criadas. O SQL fornece o comando DROP para este fim. Por exemplo, o comando SQL que se
segue remove a tabela Produtos da base de dados:
DROP TABLE Produtos;
O PER A Ç Õ ES SO BR E D A D O S -SELEC Ç Ã O
Uma das operações mais utilizadas em SQL, senão a mais utilizada, é a selecção de dados das
tabelas. O comando SELECT, utilizado para este efeito, tem uma sintaxe extremamente rica,
sendo aqui apresentada apenas uma pequena parte desta.
10
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
No seu formato mais simples o comando SELECT permite seleccionar colunas de uma tabela. Por
exemplo, o seguinte comando SELECT permite seleccionar o modelo e preço dos itens existentes
na tabela Produtos:
SELECT modelo, preço
FROM Produtos;
Neste caso a selecção actua como uma projecção sobre as colunas da tabela, devolvendo somente
para cada linha da tabela, ou registo, as colunas indicadas na clausula SELECT.
Considerando que a tabela Produtos contêm os seguintes dados:
código
marca
modelo
categoria
preço
promoção
01
ASUS
V8100 De Luxe
Placa Gráfica
547
N
02
ASUS
NoteBook L8400C
Portátil
1995
S
03
Toshiba
Satelite 3100
Portátil
1950
N
04
Creative
Audigy 5.1 OEM
Placa Audio
103
S
05
Compaq
IPaq Pocket PC
PDA
745
N
06
IBM
DeskStar 60GB
Disco IDE
283
N
O resultado do comando SELECT apresentado seria:
modelo
preço
V8100 De Luxe
547
L8400C
1995
Satelite 3100
1950
Audigy 5.1 OEM
103
IPaq Pocket PC
2343
DeskStar 60GB
283
A sintaxe do comando SELECT na sua versão mais simples é
SELECT listaDeAtributos
FROM listaDeTabelas
A lista de atributos pode ser definida pelo simbolo '*' que representa todos os atributos.
11
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Consideremos o seguinte comando SQL:
SELECT marca
FROM Produtos;
Neste caso o resultado seria apenas coluna marca da tabela Produtos:
marca
A SU S
ASUS
Toshiba
Creative
Compaq
IBM
Como podemos verificar existem linhas duplicadas no resultado do comando SQL anterior.
Através da utilização da palavra-chave DISTINCT, podemos eliminar as repetições dos
resultados. Ao especificar DISTINCT na clausula SELECT são eliminadas da resposta todas as
linhas repetidas. Por exemplo, o seguinte comando SQL já não apresenta repetições:
SQL:
SELECT DISTINCT marca
FROM Produtos;
Resultado:
marca
ASUS
Toshiba
Creative
Compaq
IBM
12
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Realizar projecções sobre as colunas de uma tabela é apenas uma parte do que podemos obter
utilizando a comando SELECT. Através da clausula WHERE é possível especificar condições
que as linhas da tabela tem de obedecer para constarem do resultado. A titulo de exemplo
consideremos que só pretendíamos as marcas que contêm produtos que se encontram em
promoção. Podemos então adicionar a seguinte condição ao comando SQL anterior
promoção='S'
O comando completo será:
SELECT DISTINCT marca
FROM Produtos
WHERE promoção='S';
Resultado:
marca
ASUS
Creative
A clausula WHERE permite-nos especificar um conjunto de condições que podem ser ligadas
através das palavras-chave AND (conjunção lógica) e OR (disjunção lógica). Por exemplo, o
resultado do seguinte comando devolve todas as marcas e modelos de artigos em promoção e
cuja categoria seja 'Portátil'.
SQL:
SELECT marca, modelo
FROM Produtos
WHERE categoria = 'Portátil' AND
promoção = 'S';
13
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Resultado:
marca
modelo
ASUS
L8400C
Os operadores AND e OR podem ser usados em conjunto, sendo no entanto necessário observar
que o operador AND tem prioridade sobre o operador OR. Por exemplo, no seguinte comando
iremos obter todos os produtos em promoção cuja categoria é 'Placa Gráfica' ou 'Placa Audio':
SQL:
SELECT marca,modelo
FROM Produtos
WHERE (
categoria = 'Placa Gráfica' OR
categoria = 'Placa Audio'
) AND
promoção= 'S';
Resultado:
marca
Creative
Note-se que a existência dos parêntesis no comando anterior é fundamental. Caso os parêntesis
não existissem o significado seria:
- Todas os produtos em promoção da categoria 'Placa Audio' e todos os produtos da
categoria 'Placa Gráfica', independentemente destes últimos se encontrarem ou não em
promoção.
14
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Ou seja, devido à prioridade anteriormente mencionada relativamente aos operadores AND e
OR, escrever o comando sem parêntesis é equivalente a escrever o seguinte:
SELECT marca,modelo
FROM Produtos
WHERE categoria = 'Placa Gráfica' OR
(
categoria = 'Placa Audio' AND
promoção= 'S'
);
Resultado:
marca
ASUS
Creative
O resultado deste comando SELECT é como podemos ver diferente da versão em que os
parêntesis estão correctamente colocados.
A complexidade inerente mesmo ao mais simples dos problemas, leva a que uma base de dados
seja normalmente composta por muitas tabelas. A combinação de informação das tabelas
contidas numa base de dados é fundamental para a análise da informação sob um perspectiva
global do sistema de informação. Iremos agora adicionar uma nova tabela e apresentar a forma
de realizar consultas de informação que envolvam mais que uma tabela.
Consideremos, então, uma tabela em que se especifica quem são os fornecedores de cada marca
de produtos.
15
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Esta nova tabela, baptizada de Fornecedores, terá dois campos: marca do produto e nome do
fornecedor:
marca
nome
ASUS
BomBit
Creative
ChipChip
IBM
BitChip
Toshiba
MegaChip
Compaq
AGPBit
ASUS
ChipChip
Creative
BomBit
IBM
BomBit
Iremos agora analisar o processo de construção de um comando SELECT que nos diga quais as
categorias de produtos fornecidas pelo fornecedor 'BomBit'. A primeira condição que teremos de
especificar resulta do próprio problema:
Fornecedores.nome = 'BomBit'
Note-se que na especificação do nome do atributo foi acrescentado, como prefixo, o nome da
tabela. Esta prática é recomendável para auxiliar a leitura de comandos SELECT com mais de
uma tabela, sendo imperativa no caso em que possa existir ambiguidade em relação à origem de
um atributo. Por exemplo o atributo marca existe em ambas as tabelas, logo é necessário
especificar o prefixo para remover ambiguidades.
A segunda condição necessária para a resolução do nosso problema está relacionada com a
forma como a informação entre as tabelas deve ser relacionada. Neste caso, a semântica do
problema diz-nos que os dados na coluna marca da tabela Produtos estão relacionadas com os
dados na coluna homónima da tabela Fornecedores. Sendo assim a condição que relaciona as
duas tabelas é:
Produtos.marca = Fornecedores.marca
Podemos agora escrever o comando para a obter a informação desejada:
16
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
SELECT Produtos.categoria
FROM Produtos, Fornecedores
WHERE Produtos.marca = Fornecedores.marca AND
Fornecedores.nome = “BomBit”;
O resultado do comando aplicado as tabelas anteriormente definidas é:
categoria
Placa Gráfica
Portátil
Placa Audio
Disco IDE
O PER A Ç Õ ES SO BR E D A D O S -M
A N U TEN Ç Ã O
Caso se pretenda realizar a manutenção de uma base de dados a partir de uma aplicação JAVA, é
necessário conhecer também os comandos para inserir, remover e alterar dados.
A operação de inserção de dados numa tabela em SQL é realizada através do comando INSERT,
e tem duas variantes possíveis. A primeira insere um registo especificando os valores de cada
atributo. A segunda insere o resultado de um comando SELECT numa tabela.
Na primeira versão pode-se utilizar a seguinte sintaxe:
INSERT INTO nomeDaTabela VALUES (listaDeValores);
A título de exemplo vamos criar um comando SQL para inserir um novo registo na tabela
Fornecedores.
INSERT INTO Fornecedores VALUES ('IBM', 'ChipChip');
Note-se que a lista de valores tem de respeitar a ordem em que os atributos são definidos na
tabela, e também o seu número. Ou seja tem de existir tantos valores como atributos. Caso se
pretenda inserir registos incompletos, ou não especificar os campos que tem valores por omissão
é necessário utilizar uma sintaxe mais completa, onde é definido de forma explicita quais os
atributos a inserir, e qual a sua ordem:
17
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
INSERT INTO nomeDaTabela(listaDeAtributos)
VALUES (listaDeValores);
Por exemplo, o comando INSERT definido anteriormente para inserir um registo na tabela
Fornecedores, utilizando a nova sintaxe, podia ser escrito das seguintes formas:
INSERT INTO Fornecedores(marca,nome)
VALUES ('IBM', 'ChipChip');
ou
INSERT INTO Fornecedores(nome,marca)
VALUES ('ChipChip', 'IBM');
Utilizando esta sintaxe, a ordem da especificação dos valores dos atributos é independente das
posições dos respectivos atributos na base de dados.
Uma questão que surge naturalmente é a seguinte: "Caso não se especifique os atributos todos,
qual o valor registado na tabela para os atributos não especificados?". Como foi mencionado
anteriormente, pode-se especificar um valor por omissão para um determinado atributo. Por
exemplo, recordando a definição da tabela Produtos, caso não se especificasse um valor para o
atributo promoção, o valor registado na tabela seria 'N', ou seja o valor especificado a ser
utilizado por omissão. Para os atributos que não se definiu valores por omissão, o valor registado
é NULL.
Tal como mencionado anteriormente, o comando INSERT possibilita a inserção de múltiplos
registos de uma só vez. No entanto, uma vez que se utiliza o comando SELECT para definir quais
os dados a inserir, estes têm de ser derivados de dados já existentes na base de dados.
A sintaxe para esta versão do comando INSERT é:
INSERT INTO nomeDaTabela(listaDeAtributos)
comandoSELECT;
18
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Por exemplo, assumindo que a tabela Fornecedores ainda não continha nenhum registo, caso se
pretendesse criar um registo para cada marca presente na tabela Produtos poderiamos escrever:
INSERT INTO Fornecedores(marca)
SELECT marca
FROM Produtos;
Os atributos presentes na clausula SELECT têm de coincidir com a lista de atributos definida
após o nome da tabela. Neste caso, para cada registo inserido, o valor do atributo nome seria
NULL, a não ser que a definição da tabela Fornecedores especificasse um valor por omissão para
este atributo.
A remoção de registos de uma tabela em SQL é realizada através do comando DELETE. A
especificação deste comando implica a definição do nome da tabela e de um conjunto de
condições a que os registos a remover têm de obedecer.
A sintaxe para o comando DELETE é:
DELETE FROM nomeDaTabela WHERE condições;
As condições a especificar no comando DELETE seguem as regras definidas para o comando
SELECT.
Por exemplo, para remover todos os produtos cuja marca seja 'ASUS' da tabela Fornecedores,
podemos escrever:
DELETE FROM Fornecedores WHERE marca='ASUS';
Para terminar esta curta introdução ao SQL vamos apresentar o comando UPDATE. Através deste
comando é possível alterar valores presentes numa tabela. A sintaxe é a seguinte:
UPDATE nomeDaTabela
WHERE condições;
SET listaDeAtribuições
Mais uma vez, a definição das condições é semelhante à definida para o comando SELECT. Por
exemplo para alterar o atributo promoção em todos os produtos da marca IBM, para 'S'
poderíamos escrever:
19
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
UPDATE Produtos SET promoção = 'S' WHERE marca = 'IBM';
20
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
2 U TILIZA Ç Ã O B Á SIC A D A A PIJD BC
Neste capitulo será explorada a forma de utilização mais simples da API JDBC para
comunicação com bases de dados. Em primeiro lugar o processo de ligação à base de dados será
analisado, incluindo a forma de carregar o driver necessário e a definição da localização da base
de dados utilizando exemplos para bases de dados locais e remotas. A ligação efectiva a uma
base de dados é exemplificada com diversos tipos de drivers.
Uma vez estabelecida a ligação a uma base de dados torna-se possível o envio de comandos SQL
para realizar operações de alteração ou consulta. O JDBC permite vários métodos de acesso à
base de dados de forma a prever um leque abrangente de situações. Neste capitulo será abordado
o método mais simples que consiste em criar uma string com o texto de um comando SQL e
enviá-lo à base de dados.
Dependendo do tipo de comando SQL, o JDBC fornece os métodos apropriados para o envio
destes à base de dados. Os métodos a invocar para os diversos tipos de comandos serão
apresentados. Ainda dependente do tipo de comandos enviados, é o tipo de resposta obtida.
Para comandos SQL do tipo SELECT é necessário processar o resultado devolvido pela base de
dados. Neste capítulo é apresentada a forma mais simples de extrair os resultados deste tipo de
comandos.
2.1 C O N EC TIVID A D E C O M B A SES D E D A D O S
E STA BELEC ER U M A L IG A Ç Ã O
O primeiro passo consiste em especificar o driver a utilizar. Como foi mencionado
anteriormente, o JDBC inclui o driver necessário para ligações via o protocolo ODBC. Este
driver será utilizado nos exemplos que se seguem, juntamente com um driver tipo 4 (protocolo
de rede nativo) para a base de dados MySQL.
21
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O JDBC está definido de forma modular no sentido em que o driver a utilizar só influencia a
definição da ligação. Posteriormente, após a ligação estar estabelecida, é indiferente qual o driver
utilizado, excepto, claro está, no grau de implementação de cada um dos drivers.
O primeiro passo consiste em especificar qual o driver a utilizar e torná-lo disponível para
posterior utilização. Para tal é necessário carregar o driver, por exemplo, através da invocação do
seguinte método da classe java.lang.Class:
void forName(String nomeClasse)
throws ClassNotFoundException;
O parâmetro deste método especifica a classe que implementa o driver. A documentação do
driver especifico para a base de dados deve fornecer o nome da classe. A titulo de exemplo, caso
se
pretenda
utilizar
o
driver
JDBC-ODBC,
a
classe
respectiva
é
sun.jdbc.odbc.JdbcOdbcDriver, sendo o código para a sua invocação:
try {
Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);
}
catch (ClassNotFoundException e) {
}
// tratamento do erro
Para aceder a uma base dados MySQL, pode utilizar-se o driver tipo 4 (gratuito) MM.MySQL. A
denominação do driver resulta das iniciais do seu autor, Mark Matthews. A classe a especificar
no método forName para este driver é: org.gjt.mm.mysql.Driver.
try {
Class.forName(“org.gjt.mm.mysql.Driver”);
}
catch (ClassNotFoundException e) {
}
// tratamento do erro
Como se pode inferir do código acima apresentado, caso o driver não esteja disponível o método
forName lança a excepção ClassNotFoundException. Esta excepção é subclasse de
RunTimeException, logo não é de tratamento obrigatório. No entanto, deve prever-se o seu
tratamento, uma vez que existe a possibilidade do driver não existir no sistema.
22
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Um processo alternativo para disponibilizar um driver JDBC numa aplicação consiste em
adicionar às propriedades do sistema, o texto com o nome da classe associado à propriedade
jdbc.drivers. Tal pode ser conseguido de duas formas: através da linha de comando, ao
invocar a aplicação; ou utilizando o método setProperty da classe java.lang.System. É
possível adicionar vários drivers a esta propriedade utilizando o simbolo ":" para separar os
nomes dos drivers.
Por exemplo para definir que uma aplicação xpto pode usar o driver JDBC-ODBC pode-se
especificar a seguinte linha de comando:
> java -Djdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver xpto
Como foi mencionado anteriormente, também se pode utilizar o método setProperty para
definir a propriedade jdbc.driver. A assinatura deste método é em seguida apresentada:
public static String setProperty(
String chave,
String valor);
Por
exemplo,
para
atribuir
o
valor
sun.jdbc.odbc.JdbcOdbcDriver
à
chave
jdbc.drivers, pode-se escrever:
System.setProperty("jdbc.drivers",
"sun.jdbc.odbc.JdbcOdbcDriver");
Note-se que a especificação da propriedade jdbc.drivers é uma alternativa à utilização do
método forName. Logo, caso se opte pela definição de jdbc.drivers, não é necessário
utilizar o método forName para carregar o driver.
Ao utilizar a propriedade jdbc.drivers, a aplicação não carrega o driver tal como acontece
quando se utiliza o método forName. No entanto, ao executar pela primeira vez um método da
classe DriverManager, esta classe irá verificar se está definida a propriedade jdbc.drivers.
Em caso afirmativo, os drivers especificados na propriedade serão disponibilizados para a
aplicação.
23
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Uma vez que o driver esteja disponível, é possível criar a ligação à base de dados. A classe
DriverManager é a responsável pelo estabelecimento da ligação. Todos os métodos desta
classe são declarados como static, sendo portanto métodos de classe. Esta classe não permite a
criação de instâncias pelo programador, sendo portanto um serviço fornecido aos utilizadores da
API.
A classe DriverManager disponibiliza os seguintes métodos para estabelecer a ligação:
Connection getConnection(String url)
throws SQLException;
Connection getConnection(String url,
String login,
String password)
throws SQLException;
Connection getConnection(String url,
java.util.Properties info)
throws SQLException;
O resultado da invocação do método getConnection é um objecto cuja classe implementa a
interface Connection. A interface Connection define as assinaturas dos métodos necessários
para criar objectos que contêm comandos SQL.
A diferença entre a primeira e as restantes versões do método getConnection reside na forma
de especificar um login e password para acesso à base de dados. Caso a base de dados tenha
associado um login, pode-se utilizar a segunda ou terceira versões. Em certos drivers, os
parâmetros de ligação, nomeadamente login e password, podem ser definidos no parâmetro url
como iremos ver mais adiante.
Nos casos em que não é necessário especificar um par (login, password), pode-se também
utilizar a segunda ou terceira versões, em que, por exemplo, na segunda versão os parâmetros
login e password são definidos com a string vazia "".
A última versão do método getConnection, em que o segundo parâmetro é uma instância de
java.util.Properties, permite que se especifiquem propriedades da ligação através de
pares (chave,valor). Por exemplo, este parâmetro poderia conter o login e password do
24
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
utilizador. Outras propriedades da ligação são dependentes do driver em causa, podendo, por
exemplo, definir o tempo máximo de espera para obter a ligação.
O parâmetro url necessita de uma explicação mais detalhada. Este parâmetro está dividido
logicamente em três componentes separadas pelo símbolo ":". As componentes do parâmetro
url são:
•
protocolo: O protocolo em ligações a bases de dados através de JDBC é constante, sendo
representado pela string “jdbc”.
•
sub protocolo: Quanto ao sub protocolo, este é dependente do driver em causa. Por
exemplo, caso exista um driver apropriado uma base de dados pode ser acedida
directamente; caso a base de dados em questão seja suportada por ODBC, esta também
pode ser acedida via ODBC. A documentação do driver deve mencionar qual o sub
protocolo a utilizar. A título de exemplo para um driver JDBC-ODBC o sub protocolo é
“odbc”, e para o driver MM.MySQL é "mysql".
•
identificador da base de dados: A identificação da base de dados têm como objectivo
permitir a localização da base de dados. A sua sintaxe é dependente do sub protocolo
utilizado. Para ligações através do driver JDBC-ODBC o identificador é o definido em
Data Source Name aquando do registo da base de dados no administrador de ODBC.
Para bases de dados remotas o identificador pode indicar o endereço de rede, utilizando a
notação standard de descrição de URL1, e o nome pelo qual a base de dados é conhecida.
A especificação do identificador pode também incorporar os parâmetros da ligação em
certos drivers.
Adicionalmente, em alguns drivers, é possível especificar propriedades da ligação no parâmetro
url. A especificação destas propriedades é no entanto dependente do driver em causa. Para o
1
Uniform Resource Location
25
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
driver MM.MySQL serão apresentados exemplos de definições do parâmetro url que tiram
partido desta funcionalidade.
O excerto do código JAVA para realizar a ligação a uma base de dados com o identificador
"bitShop" utilizando o sub-protocolo "odbc" é em seguida apresentado a titulo de exemplo:
try {
con =
DriverManager.getConnection(“jdbc:odbc:bitShop”);
}
catch (SQLException e) {
//tratamento do erro
}
Caso seja necessário especificar um login e password, pode-se substituir a linha onde se realiza a
invocação ao método getConnection, por uma das outras versões do mesmo:
con = DriverManager.getConnection(
"jdbc:odbc:bitShop",
"gestor","bitbit");
ou ainda,
java.util.Properties info =
new java.util.Properties();
info.add("login","gestor");
info.add("password", "bitbit");
con =DriverManager.getConnection(
"jdbc:odbc:bitShop",
info);
Considerando o driver MM-MySQL, e uma vez que este driver é do tipo 4, protocolo de rede
nativo, o parâmetro url é naturalmente diferente, já que é necessário especificar qual a
localização do servidor MySQL. Por exemplo, assumindo que o servidor está alojado na
máquina bitShop.com, a responder a pedidos na porta 3306, e que a base de dados tem por
identificador "bitShop", o código para efectuar a ligação seria:
26
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
String id;
id = "jdbc:mysql://bitShop.com:3306/bitShop"
try {
con = DriverManager.getConnection(id);
}
catch (SQLException e) {
//tratamento do erro
}
A especificação de parâmetros como login e password pode, neste caso, ser feita também através
do parâmetro url. Por exemplo,
id = " jdbc:mysql://bitShop.com:3306/bitShop?" +
"user=gestor&password=bitbit";
Como se pode verificar neste exemplo, a construção do parâmetro url é em tudo semelhante à
construção de um URL parametrizado na WWW.
TEMPO MÁXIMO DE ESPERA
Através da classe DriverManager é possível estabelecer o tempo máximo de espera para
efectuar a ligação a uma base de dados. Note-se que, à partida, existe um tempo máximo prédefinido pelo driver. No entanto, por vezes pode ser necessário alterar a temporização máxima
desta operação. Uma situação em que esta necessidade pode surgir resulta da utilização de bases
de dados remotas com tempos de acesso prolongados devido a ligações de rede lentas ou
congestionadas. A classe DriverManager fornece um método para interrogar qual o valor
definido, e um outro método para alterar este valor.
O método para interrogar a classe DriverManager sobre o valor definido para o tempo máximo
de espera tem a seguinte assinatura:
public static int getLoginTimeout()
throws SQLException;
27
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O valor devolvido por este método indica o número máximo de segundos para a obtenção da
ligação. Este método pode ser utilizado para determinar qual o valor pré-definido desde que o
valor ainda não tenha sido alterado.
Caso seja necessário, é também possível alterar o valor pré-definido pelo driver. O método para
definir o tempo máximo de espera para efectuar a ligação tem a seguinte assinatura:
public static void setLoginTimeout(int segundos)
throws SQLException;
O parâmetro deste método é o número máximo de segundos permitido para estabelecimento da
ligação.
Certos drivers, como por exemplo MM.MySQL, permitem que o tempo máximo para efectuar
uma ligação seja também especificado no parâmetro url. Por exemplo:
url = "jdbc:mysql://bitShop.com:3306/bitShop?" +
"user=gestor&password=bitbit" +
"&initialTimeout=30";
T ER M IN A R A
LIG A Ç Ã O
Uma ligação a uma base de dados consome recursos tanto ao nível do JAVA como da própria
base de dados. Para libertar esses recursos deve-se terminar a ligação logo que esta já não seja
necessária. Para tal efeito a interface Connection define um método com a seguinte assinatura:
void close() throws SQLException;
Para verificar se uma ligação foi efectivamente terminada, ou para averiguar se uma determinada
ligação está activa, a interface Connection define o seguinte método:
boolean isClosed() throws SQLException;
28
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
E X EM PLO
Nesta secção é apresentado um exemplo para definir uma ligação a uma base de dados, e
posteriormente terminá-la. O exemplo contem código para apanhar possíveis excepções geradas
durante a invocação dos métodos.
Assume-se que a seguinte variável foi previamente definida:
Connection con;
O exemplo apresenta um excerto de código para realizar uma ligação a uma base de dados
"bitShop" utilizando o driver JDBC-ODBC.
// Carregar o driver JDBC
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch (ClassNotFoundException e) {
System.out.println("Driver não disponível");
}
// Abrir a ligação com a base de dados "bitShop"
// Não esquecer de registar a base de dados em odbc
try {
con =
DriverManager.getConnection("jdbc:odbc:bitShop");
}
catch (SQLException e) {
System.out.println("Ligação não efectuada");
}
// Efectuar dialogo com a base de dados aqui
// ...
// Terminar a ligação
try {
con.close();
}
catch (SQLException e) {
System.out.println("Erro ao terminar a ligação");
}
29
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
2.2 D EFIN IÇ Ã O E E N VIO D E C O M A N D O S SQ L
Nesta secção será apresentada a forma mais simples de dialogar com a base de dados: o envio de
um texto com um comando SQL à base de dados. O primeiro passo consiste em criar um
Statement, ou seja um objecto do tipo Statement. É este objecto que será utilizada para
enviar o comando SQL à base de dados.
A criação de um Statement implica a ligação prévia à base de dados. Para criar um
Statement deve-se invocar o método createStatement, definido na interface Connection,
cuja assinatura se apresenta a seguir:
Statement createStatement()
throws SQLException;
Como foi mencionado anteriormente, ao efectuar a ligação à base de dados através do método
getConnection, é criado um objecto cuja classe implementa a interface Connection. É este
objecto que irá permitir a criação de Statement. Assumindo que a variável con é o resultado
do método getConnection, o código para criar um Statement é o seguinte:
Statement s;
try {
s = con.createStatement();
}
catch (SQLException e) {
// tratamento do erro
}
Após a execução do código apresentado é possível enviar comandos SQL à base de dados. O
método a invocar para este efeito depende do tipo de comando a enviar.
30
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O JDBC fornece três métodos para o envio de comandos SQL:
•
executeQuery: para comandos de selecção;
•
executeUpdate: para todos os outros comandos;
•
execute: método genérico para todos os tipos de comando. Esta alternativa embora
mais simples à partida, implica um esforço adicional para a análise de resultados.
A invocação destes métodos para comandos que não sejam os especificados acima provoca o
lançamento de uma excepção. Por exemplo, a invocação de executeQuery com um comando
INSERT, ou a invocação do método executeUpdate com um comando SELECT. Seguidamente
estes três métodos serão apresentados em detalhe.
M
ÉTO D O ESPEC ÍFIC O PA R A
E N VIO
D E C O M A N D O S D E SELEC Ç Ã O
O método executeQuery permite realizar consultas aos dados armazenados em tabelas na base
de dados. Este método tem como argumento uma String com o texto correspondente a um
comando SQL SELECT. Este método encontra-se definido na interface Statement, e apresenta
a seguinte sintaxe:
ResultSet executeQuery(String sql)
throws SQLException;
O resultado deste método é um ResultSet. Este objecto contém os dados que correspondem à
execução do comando SQL enviado à base de dados. Posteriormente serão analisados os
métodos para extrair essa informação.
A título de exemplo considere-se o seguinte comando SQL:
SELECT modelo FROM produtos WHERE preço > 100
Assumindo que a variável con representa uma ligação activa à base de dados, o excerto de
código que se segue exemplifica a invocação do método executeQuery:
31
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
ResultSet res;
Statement st;
String sql;
sql = "SELECT modelo FROM Produtos WHERE preço > 100";
try {
st = con.createStatement();
res = st.executeQuery(sql);
}
catch (SQLException e) {
System.err.println(e.getMessage());
}
M
ÉTO D O
E SPEC ÍFIC O
PA R A
E N VIO
DE
C O M A N D O S D E A C TU A LIZA Ç Ã O
A interface Statement fornece um método especifico para comandos SQL que envolvam
alterações da estrutura da base de dados ou dos valores contidos nas tabelas propriamente ditas.
O método indicado para este tipo de operações é o seguinte:
int executeUpdate(String sql)
throws SQLException;
O parâmetro deste método é uma String com o texto do comando SQL pretendido. O valor
devolvido depende do tipo do comando SQL. Para actualizações ao conteúdo das tabelas da base
de dados, ou seja para comandos SQL UPDATE, INSERT e DELETE, o resultado do método é
o número de linhas afectadas. Por exemplo, considerando um comando DELETE que remova três
linhas de uma tabela, o resultado seria três.
No caso de comandos que alterem a estrutura da base de dados, como por exemplo a criação de
uma nova tabela, o resultado é sempre zero. Se a alteração especificada no comando não for
permitida é lançada uma excepção do tipo SQLException.
A título de exemplo considere-se o seguinte comando SQL:
UPDATE Produtos
SET promoção ='S'
WHERE marca = 'ASUS';
32
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O seguinte excerto de código envia o comando à base de dados e apresenta o número de registos
alterados.
int cont;
String s;
Statement st;
s = "UPDATE Produtos " +
"SET promoção = 'S' " +
"WHERE marca = 'ASUS'";
try {
st = con.createStatement();
cont = st.executeUpdate(s);
System.out.println("Registos Alterados: " + cont);
}
catch (SQLException e) {
System.err.println(e.getMessage());
}
M
ÉTO D O
G EN ÉR IC O
A API JDBC fornece um terceiro método para envio de comandos SQL à base de dados. O
método execute, definido na interface Statement permite o envio de qualquer comando SQL,
sem as restrições definidas previamente. Uma utilização típica deste método pode surgir numa
aplicação que contenha uma caixa de texto para o utilizador definir um comando SQL. Embora
seja possível escrever o código que analise o comando introduzido pelo utilizador para
determinar qual o método a invocar, tal não é necessário com a utilização deste comando.
O método para envio de comandos SQL genéricos tem a seguinte assinatura:
boolean execute(String sql)
throws SQLException;
O resultado devolvido por este comando é função do tipo de comando enviado. Caso o comando
seja SELECT o resultado será TRUE. Para todos os outros comandos o resultado é FALSE. Deste
forma é possível saber qual o tipo de comando à posteriori sem ser necessário implementar
qualquer código para análise do comando.
Resta agora saber como aceder ao resultado real da operação. Dois métodos são fornecidos pela
API JDBC consoante o tipo de comando SQL. Para comandos SELECT o método
33
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
getResultSet, definido na interface Statement, permite obter o conjunto de linhas
devolvidas pelo comando. A assinatura deste método é a seguinte:
ResultSet getResultSet()
throws SQLException;
Caso o resultado do método execute seja FALSE, ou seja não estamos perante um comando de
selecção, este método não deve ser invocado. A invocação indevida deste método produz como
resultado null não sendo lançada nenhuma excepção.
Para comandos SQL do tipo INSERT, UPDATE e DELETE, o método getUpdateCount, da
interface Statement, produz informação sobre o número de registos alterados. Relativamente
aos comandos SQL para alteração da estrutura da base de dados, o resultado do método
getUpdateCount será sempre zero. A assinatura deste método é em seguida apresentada.
int getUpdateCount()
throws SQLException;
Caso se invoque este método indevidamente este método, ou seja o comando SQL previamente
enviado é um comando de selecção, o resultado será –1 não sendo lançada nenhuma excepção.
Ambos os métodos apresentados lançam a excepção SQLException em caso de erro no acesso
à base de dados. O exemplo que se segue ilustra a utilização destes métodos.
Assume-se que a variável con representa uma ligação activa à base de dados.
34
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
int cont;
boolean res;
String s;
ResultSet sel;
Statement st;
s = "UPDATE Produtos " +
"SET promoção = 'S' " +
"WHERE marca = 'ASUS'";
try {
st = con.createStatement();
res = st.execute(s);
if (res)
sel = st.getResultSet()
else
cont = st.getUpdateCount();
}
catch (SQLException e) {
System.err.println(e.getMessage());
}
Como é ilustrado pelo exemplo acima, a utilização deste método genérico exige um esforço
adicional para a obtenção do resultado, em comparação com os métodos específicos
apresentados nas secções anteriores. A sua utilização só se justifica quando a natureza do
comando SQL a enviar à base de dados não é conhecida à priori.
2.3 A N Á LISE D E R ESU LTA D O S D E C O M A N D O S SELEC T
Nas secções anteriores foi apresentada a forma de efectuar uma ligação a uma base de dados, e
enviar individualmente comandos SQL. Ao enviar um comando SELECT, utilizando os métodos
apresentados nas secções anteriores, é criado um ResultSet.
Neste contexto, um ResultSet contem os dados resultantes da resposta da base de dados a um
comando SELECT. Um ResultSet tem uma estrutura semelhante a uma tabela, sendo composto
por uma sequência de linhas, correspondentes às linhas devolvidas pelo comando SELECT. Cada
linha por sua vez contem uma sequência de valores relativos aos campos indicados na clausula
SELECT.
A forma de navegação na tabela de resultados de um ResultSet depende da forma como o
comando SQL é criado. Na sua forma mais simples, e por omissão, a navegação é sequencial, e
35
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
unidirecional, i.e. só permite percorrer o ResultSet numa direcção. Mais à frente veremos
como construir um ResultSet com possibilidade de acesso aleatório.
Devido à unidirecionalidade implícita do ResultSet, os seus dados devem ser obtidos
iterativamente de uma forma semelhante à da interface Enumeration. Tal como na interface
Enumeration, um ResultSet contem um cursor para as linhas cuja posição inicial se
encontra antes da primeira linha. O acesso às linhas é realizado sequencialmente, avançando o
cursor para a linha seguinte, através do método next cuja assinatura se apresenta a seguir:
boolean next()
throws SQLException;
O método next, caso existam mais linhas, avança o cursor para a linha seguinte e devolve true.
Se o cursor estiver posicionado na última linha, a invocação deste método produz false como
resultado. Note-se que neste caso não é lançada nenhuma excepção. A excepção só é lançada
quando há erros relacionados com o acesso à base de dados.
Uma vez que o método next tem como resultado um valor booleano, a iteração dos dados de um
ResultSet pode ser implementada utilizando um ciclo while. Assumindo que rs representa
um ResultSet criado como resultado do envio de um comando SELECT à base de dados, podese então escrever:
try {
while( rs.next() ) {
}
// processar linha
}
catch (SQLException e) {
// processar erro
}
Uma vez o cursor posicionado numa linha válida torna-se necessário aceder aos dados relativos
às colunas seleccionadas pelo comando SELECT. Os dados das colunas podem ser acedidos
utilizando a família de métodos getXXX, sendo XXX correspondente ao tipo de dados da coluna
em JAVA. O parâmetro destes métodos pode ser a posição relativa da coluna, ou o seu nome, tal
como definido na base de dados.
36
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Por exemplo, considere-se o seguinte comando SELECT:
SELECT modelo, marca
FROM Produtos
WHERE preço < 1000;
Neste caso ambas as colunas são do tipo VARCHAR, o que corresponde ao tipo String em
JAVA. Dado um objecto rs, que obedeça à interface ResultSet, para obter o valor da coluna
"marca", qualquer uma das opções a seguir apresentadas é válida:
rs.getString(2);
rs.getString("marca");
Note-se que o índice das colunas começa em 1 e não em 0, contrariamente ao que acontece com
os dados indexados em praticamente todas as classes JAVA.
Para colunas cujo tipo de dados corresponda a uma String JAVA, estão definidos os métodos:
String getString(int índiceColuna)
throws SQLException;
String getString(String nomeColuna)
throws SQLException;
Estes métodos também podem ser usados para quase todos os tipos de dados, sendo a conversão
para String realizada automaticamente.
Outros métodos da família getXXX são, por exemplo:
Float getFloat
Date getDate
Int getInt
O parâmetro pode ser sempre, ou o nome da coluna, ou o índice. A excepção SQLException
será lançada em caso de invocação inapropriada do método, i.e. o tipo de dados do atributo não é
passível de conversão no tipo de dados respectivo do método, ou em caso de erro no acesso à
base de dados. Consideremos então o seguinte comando SQL aplicado à tabela Produtos:
Tabela Produtos
37
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
código
marca
modelo
categoria
preço
promoção
01
ASUS
V8100 De Luxe
Placa Gráfica
547
N
02
ASUS
NoteBook L8400C
Portátil
1995
S
03
Toshiba
Satelite 3100
Portátil
1950
N
04
Creative
Audigy 5.1 OEM
Placa Audio
103
S
05
Compaq
IPaq Pocket PC
PDA
745
N
06
IBM
DeskStar 60GB
Disco IDE
283
N
SELECT marca,modelo,preço
FROM Produtos
WHERE preço < 1000.0
O seguinte excerto de código ilustra a invocação destes métodos. Assume-se que a variável rs
contem uma instancia de ResultSet com o resultado do comando SQL acima apresentado.
System.out.println("Marca
\tModelo
\tPreço");
System.out.println("----\t-----\t-----");
try {
while (rs.next()) {
System.out.println(rs.getString(1) +
"
\t" + rs.getString(2) +
"\t"
+ rs.getFloat(3));
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
O resultado do código acima apresentado seria:
Marca
----ASUS
Creative
IBM
Modelo
---V8100 De Luxe
Audigy 5.1 OEM
DeskStar 60GB
Preço
----547.0
103.0
283.0
Como foi mencionado anteriormente, para cada tipo de dados, existem dois métodos getXXX. A
diferença entre estes dois métodos reside unicamente no seu argumento. O argumento tanto pode
ser, um int representativo do índice da coluna, como vimos no exemplo acima, ou então uma
String com o nome da coluna. Desta forma, poderíamos alternativamente ter escrito, para a
linha que imprime os dados, o seguinte código:
38
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
System.out.println(rs.getString("marca") +
"
\t" + rs.getString("modelo") +
"\t"
+ rs.getFloat("preço"));
Alternativamente, também poderíamos ter utilizado o método getString para obter os valores
do atributo preço. Como foi mencionado anteriormente o método getString é um método
genérico que pode ser utilizado para a maioria dos tipos de dados.
E X EM PLO C O M PLETO
import java.sql.*;
public class SimpleResultSet {
static Connection con;
static ResultSet rs;
static Statement st;
public static void main(String[] args) {
// Carregar o driver JDBC
try {
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch (ClassNotFoundException e){
System.out.println("Driver não disponível");
System.exit(-1);
}
// Abrir a ligação com a base de dados "bitShop"
// Não esquecer de registar a base de dados no odbc
try {
con = DriverManager.getConnection(
"jdbc:odbc:bitShop",
"gestor",
"bitbit");
}
catch (SQLException e) {
System.out.println("Ligação não Efectuada");
System.exit(-1);
}
try {
// Definição e envio do comando SQL
String sql = "SELECT marca,nome,preço " +
"FROM Produtos " +
39
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
"WHERE preço < 1000.0";
st = con.createStatement();
rs = st.executeQuery(sql);
// Mostrar resultados
System.out.println("Marca
\tNome
System.out.println("----\t----
\tPreço");
\t-----");
while (rs.next()) {
System.out.println(rs.getString(1) +
"
\t" + rs.getString(2) +
"\t"
+ rs.getFloat(3));
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
try {
con.close();
}
catch (SQLException e) {
System.out.println e.getMessage());
}
} // fim da função main
} // fim da classe
2.4 M ETA D A D O S D O S R ESU LTA D O S
Na secção anterior foi apresentada uma forma simples de extrair resultados de um ResultSet.
O processo apresentado no entanto pressupõe à priori um conhecimento do comando SELECT,
nomeadamente qual o número de colunas, qual o seu tipo e nome, etc.
Como lidar então com casos em que este conhecimento não existe? Por exemplo, uma aplicação
interactiva em que o utilizador introduz comandos SQL para enviar à base de dados. Nestes
casos ao receber um ResultSet como resultado do envio de um comando SELECT torna-se
necessário determinar em primeiro lugar qual a estrutura do resultado propriamente dito de
forma a se poder apresentar convenientemente os resultados.
A API JDBC contem uma interface que define um conjunto de métodos para obter informações a
estrutura sobre um ResultSet: a interface ResultSetMetaData. Através dos métodos
definidos nesta interface é possível determinar o número de colunas presentes num ResultSet,
40
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
o nome das colunas, o seu tipo de dados, e outras informações relativas à estrutura de um
ResultSet.
Dado um ResultSet rs, para se obter a sua meta data, deve-se invocar o seguinte método:
ResultSetMetaData getMetaData() throws SQLException;
A partir deste ponto podem-se invocar os métodos definidos na interface ResultSetMetaData
utilizando o resultado deste método. Esta interface define um espectro largo de funcionalidades
das quais apenas algumas são aqui apresentadas.
Ao receber o resultado de um comando SELECT cujo conteúdo é desconhecido é necessário
determinar o número de colunas que este contem. Para tal pode-se invocar o seguinte método
definido na interface ResultSetMetaData:
int getColumnCount() throws SQLException;
Dado um índice de uma coluna, o seu nome pode ser obtido através do seguinte método:
String getColumnName(int índice) throws SQLException;
O nome da classe JAVA que corresponde ao tipo de dados de uma coluna também se pode obter
através da interface ResultSetMetaData, invocando o método:
String getColumnClassName(int índice)
throws SQLException;
O nome da tabela de onde foram retirados os valores da coluna pode-se obter através do método:
String getTableName(int índice) throws SQLException;
Consideremos agora o seguinte comando SQL:
SELECT marca,modelo,preço
FROM Produtos
WHERE preço < 1000.0
41
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Em seguida apresenta-se um excerto de código que ilustra a invocação destes métodos e
respectivo resultado. Seja rs um ResultSet que contem o resultado da invocação do comando
apresentado.
try {
ResultSetMetaData rsmd = rs.getMetaData();
System.out.println("Total de colunas : " +
rsmd.getColumnCount());
System.out.println("Nome da coluna 1 : " +
rsmd.getColumnName(1));
System.out.println("Classe Java da coluna 1 : " +
rsmd.getColumnClassName(1));
System.out.println("Nome da tabela da coluna 1 : " +
rsmd.getTableName(1) );
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Resultado:
Total de colunas : 3
Nome da coluna 1 : marca
Classe Java da coluna 1 : java.lang.String
Nome da tabela da coluna 1 : Produtos
Note-se que muitos destes métodos não se encontram implementados em alguns drivers,
podendo lançar excepções na altura da sua invocação.
Através da análise dos meta dados de um ResultSet é possível definir um método genérico
para apresentação de resultados. De seguida, apresenta-se um exemplo simples de um método
que recebe como parâmetro um ResultSet, que ilustra a potencial da interface
ResultSetMetaData.
static void display(ResultSet rs) throws SQLException {
ResultSetMetaData rsmd;
int cont,i;
// acesso à meta data
42
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
rsmd = rs.getMetaData();
// número de colunas
cont = rsmd.getColumnCount();
// para cada coluna imprimir o nome
for (i = 1; i <= cont; i++)
System.out.print(rsmd.getColumnName(i) + "\t");
System.out.println();
}
// imprimir linhas do resultado
while (rs.next()) {
for (i = 1; i <= cont; i++)
System.out.print(rs.getString(i) + "\t");
System.out.println();
}
2.5 E X C EL VIA JD BC
Uma consulta rápida ao administrador de ODBC revela-nos que este protocolo suporta a folha de
cálculo Excel. O processo de registo de uma folha de cálculo em ODBC é semelhante ao registo
de uma base de dados.
Uma vez que o JDBC inclui um driver JDBC-ODBC, então é possível trabalhar com uma folha
de cálculo de forma semelhante à apresentada para bases de dados convencionais.
Do ponto de vista do ODBC, cada folha de trabalho (Worksheet) corresponde a uma tabela. A
primeira linha da tabela especifica os atributos da tabela, sendo as restantes linhas utilizadas para
os dados. O nome da folha de trabalho especifica o nome da tabela.
Via ODBC, é possível realizar consultas, inserções, e alterações sobre os dados de uma folha de
cálculo. No entanto, a ligação via ODBC não permite a remoção de linhas de uma folha de
cálculo. Operações sobre de tabelas em folhas de cálculo Excel, via ODBC, também são
permitidas embora com uma semântica particular. A inserção de folhas de trabalho não é
possível, mas a criação de uma tabela numa folha existente é permitida. Ao realizar uma
operação DROP TABLE a folha de trabalho não é eliminada mas os seus conteúdos são apagados.
43
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
A Figura 1apresenta duas folhas de trabalho de uma folha de cálculo gravada em ficheiro com o
nome produtos.xls. Como podemos observar pela barra inferior das folhas de trabalho
apresentadas na Figura 1, estão definidas duas tabelas: Produtos e Fornecedores. Os nomes dos
atributos aparecem na primeira linha de cada uma das tabelas. O sombreado visível na primeira
linha de cada folha de trabalho tem somente um fim estético para separar os nomes dos atributos
dos seus valores.
44
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Figura 1 Bases de dados em folhas de trabalho Excel
Em relação ao código JAVA, o procedimento para efectuar uma ligação à folha de cálculo é
exactamente idêntico ao apresentado anteriormente para base de dados, sendo utilizado o driver
JDBC-ODBC.
A única diferença reside na definição do comando SQL, que apresenta um pouco de açúcar
sintáctico. A referência às tabelas é realizada utilizando o nome da tabela com o sufixo '$'. Uma
vez que este símbolo é reservado em SQL, torna-se ainda necessário adicionar parêntesis rectos
ao nome da tabela.
Consideremos, por exemplo, o seguinte comando SELECT
SELECT Produtos.marca, modelo, preço
FROM Produtos, Fornecedores
WHERE nome = 'BomBit' AND
Produtos.marca = Fornecedores.marca;
A versão do comando acima apresentado, para utilização numa ligação via JDBC a uma folha de
cálculo Excel, é a seguinte:
SELECT [Produtos$].marca, modelo, preço
FROM [Produtos$], [Fornecedores$]
WHERE nome = 'BomBit' AND
[Produtos$].marca = [Fornecedores$].marca;
45
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
3 D EFIN IÇ Ã O A VA N Ç A D A D E C O M A N D O S SQ L
No capítulo anterior foi apresentada a forma mais simples de definição e envio de comandos
SQL a uma base de dados. Tendo em vista optimizar a performance de aplicações que utilizem
esta API, estão previstas outras formas quer para a definição quer para o envio de comandos
SQL. Neste capítulo iremos explorar exactamente estas funcionalidades avançadas da API JDBC
2.0.
3.1 PR É -PR O C ESSA M EN TO D E C O M A N D O S SQ L
De acordo com o processo definido para envio de comandos SQL apresentado anteriormente, o
comando SQL só é passado ao JDBC na altura da sua execução. Só nessa altura o comando é
processado, e enviado. A ideia por detrás do pré-processamento de comandos SQL reside
exactamente na separação destes dois passos. O objectivo é permitir reutilizar o processamento
de comandos SQL que sejam utilizados frequentemente.
Ao utilizar esta funcionalidade o comando SQL é pré-processado uma só vez e executado
potencialmente múltiplas vezes, sendo o processo de execução teoricamente mais rápido. Para
efectuar o pré-processamento, o comando SQL é enviado à base de dados antes da sua execução.
Note-se que nem todos os drivers permitem realizar este pré-processamento, apesar de não
lançarem excepções, ou avisos ao utilizar esta funcionalidade. No entanto, como veremos mais à
frente, as situações criadas em termos de excepções potenciais são diferentes.
O pedido de pré-processamento de um comando SQL é realizado através do método
prepareStatement da interface Connection:
PreparedStatement prepareStatement(String sql)
throws SQLException;
46
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Note-se a diferença em relação ao método createStatement apresentado anteriormente. O
método createStatement não tem argumentos, enquanto que o método prepareStatement
tem como argumento a String correspondente ao comando SQL.
Como se pode observar da assinatura do método, o resultado é um objecto que obedece à
interface PreparedStatement. Esta interface é uma especialização da interface Statement,
sendo sub-interface desta. No entanto os métodos da super-interface Statement, relativos à
execução dos comandos SQL, não devem ser invocados para objectos do tipo
PreparedStatemnt. A interface PreparedStatement define um conjunto próprio de
métodos para execução de comandos. A execução de um comando pré-compilado é realizada
através de um dos três métodos seguintes:
ResultSet executeQuery()
throws SQLException;
int executeUpdate()
throws SQLException;
boolean execute()
throws SQLException;
A escolha do método para a execução do comando SQL é dependente do seu tipo. Tal como
anteriormente temos o método executeQuery para comandos SELECT, o método
executeUpdate para todos os outros comandos, e finalmente o método execute que pode ser
utilizado com qualquer tipo de comando.
Note-se mais uma vez a diferença em relação aos métodos homónimos apresentados no capítulo
anterior. Agora todas as variante para execução de comandos SQL não tem argumentos,
contrariamente ao que sucedia anteriormente, onde o comando SQL era um argumento.
As diferenças são mais facilmente perceptíveis ao observar o código para as duas versões. O
código que se segue assume que a variável con, do tipo Connection, representa uma ligação
activa a uma base de dados, e que a variável sql, do tipo String, contem o texto de um
comando SQL. Segundo o capítulo anterior tínhamos:
Statement st;
boolean res;
47
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
st = con.createStatement();
res = st.execute(sql);
Com a introdução da funcionalidade para pré-compilação de comandos podemos escrever:
PreparedStatement preparedSt;
boolean res;
preparedSt = con.prepareStatement(sql);
res = preparedSt.execute();
Na primeira versão, o método createStatement, limita-se a criar um container vazio para um
comando SQL. O objecto resultante de createStatement só é preenchido na ocasião da
execução do comando. Nesta altura o comando é compilado e executado.
A segunda versão utiliza o método prepareStatement, que já leva como argumento o
comando SQL a utilizar posteriormente. A pré-compilação, caso seja permitida, é efectuada
nesta altura enviando o comando à base de dados para este fim. Posteriormente ao executar o
comando não é necessária mais nenhuma informação, logo o método execute não necessita de
argumentos.
Note-se que os ganhos potenciais ao nível de performance só serão obtidos caso exista précompilação e o comando seja utilizado repetidamente. O número exacto a partir do qual existem
benefícios ao nível de performance é obviamente dependente do driver e da base de dados.
Devido à sobrecarga requerida pela pré-compilação, não se justifica a utilização de comandos
pré-compilados para uma só execução.
Como foi mencionado anteriormente nem todos os drivers e/ou bases de dados permitem a précompilação. Embora não sejam lançadas excepções pela utilização desta funcionalidade quando
o pré-processamento não se realiza, existem implicações em relação a qual dos métodos lança
excepções caso haja problemas como comando SQL a enviar.
48
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Caso o driver e a base de dados suportem pré-compilação, e por consequência envie o comando
à base de dados para este efeito, as excepções relativas à construção sintáctica do comando SQL
serão imediatamente lançadas na invocação do método prepareStatement. Se a précompilação não for suportada, então mesmo com comandos SQL incorrectos não é lançada
nenhuma excepção na invocação de prepareStatement. Neste último caso a excepção, será
lançada por ocasião da invocação do método execute.
Podemos aproveitar este facto para facilmente determinar se existe suporte para préprocessamento de comandos SQL. Consideremos o seguinte excerto de código, onde con, do
tipo Connection representa uma ligação activa a uma base de dados:
String sql = "Isto não é um comando SQL";
try {
st = con.prepareStatement(sql);
}
catch (SQLException e) {
System.out.println("Pré-compilação suportada");
}
O código acima apresentado lançará uma excepção caso exista pré-compilação. Neste caso a
excepção é lançada porque a base de dados informou o driver que o comando em causa continha
erros sintácticos.
Note-se no entanto que de um ponto de vista semântico nem todas as restrições são aplicadas na
fase da pré-compilação do comando. Por exemplo, inserir uma linha na base de dados com um
valor duplicado para um campo que seja chave primária pode não lançar uma excepção na fase
compilação. No entanto operações de selecção sobre uma tabela inexistente na fase de précompilação podem não ser permitidas e provocar o lançamento de uma excepção.
É importante lembrar que, dada a existência de uma fase prévia de pré-compilação, o contexto na
pré-compilação não é necessariamente o mesmo que na fase de execução.
Sendo assim, o facto de um determinado comando SQL não lançar uma excepção na fase de précompilação, não implica que estamos livres de perigo ao mandar executar o respectivo comando
SQL. Mesmo que consideremos comandos que são válidos sintáctica e semanticamente na altura
49
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
de compilação, podemos obter excepções na altura da execução. Por exemplo um comando que
insira uma linha numa tabela pode conter uma chave válida, i.e. não existente na tabela, na fase
de compilação. No entanto nada nos garante que na fase de execução essa chave não tenha sido
entretanto criada, resultando portanto uma excepção ao executar o comando.
A título de exemplo, utilizando o driver JDBC-ODBC, as bases de dados Microsoft SQL Server
permitem a pré-compilação de um comando que insira uma valor duplicado para uma chave
primária, mas não permite uma operação de inserção sobre uma tabela inexistente. O mesmo
driver JDBC-ODBC, com bases de dados Access já não suporta pré-compilação de comandos,
logo não lança nenhuma excepção caso o comando esteja incorrecto na fase de pré-compilação.
3.2 C O M A N D O S SQ L PA R A M ETR IZA D O S
Com foi mencionado anteriormente, a pré-compilação de comandos SQL permite optimizar, caso
o driver o implemente esta funcionalidade, a execução posterior dos respectivos comandos. No
entanto, para obter ganhos reais de performance é necessário que o comando SQL seja executado
um determinado número de vezes para compensar o esforço adicional da pré-compilação.
A possibilidade de pré-compilação de comandos SQL parametrizados alarga claramente este
espectro de utilização. Por exemplo, quais os livros requisitados pelo utente X? Qual os produtos
em promoção da categoria Y? Quais os produtos em stock da marca Z? Quais os alunos
matriculados na disciplina W? Os comandos apresentados são claramente executados inúmeras
vezes
Vejamos agora um exemplo concreto. Quais os produtos distribuídos pelo fornecedor X?
SELECT marca
FROM Fornecedores
WHERE nome = ?;
ou, quais os modelos da categoria Y, cujo preço seja menor que Z, que se encontram em
promoção?
50
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
SELECT marca, modelo
FROM Produtos
WHERE categoria = ? AND preço
< ? AND promoção = 'S';
O símbolo '?' é a notação usada pelo JDBC para especificar um parâmetro num comando SQL.
Uma vez pré-compilado, o comando pode ser instanciado múltiplas vezes atribuindo um valor ao
parâmetro antes de cada execução.
A criação de comandos pré-compilados com parâmetros é obtida através do método
prepareStatement introduzido na secção anterior. A atribuição de valores aos parâmetros é
realizada através da família de métodos setXXX da interface PreparedStatement.
Consideremos o primeiro comando apresentado nesta secção. O seguinte excerto de código cria
uma versão pré-compilada deste comando, e em seguida cria duas instâncias do comando e
envia-as à base de dados. Assume-se que a variável con representa uma ligação activa à base de
dados.
String sql;
PreparedStatement preparedSt;
ResultSet rs1,rs2;
try {
sql = "SELECT marca FROM Formecedores WHERE nome = ?";
preparedSt = con.prepareStatement(sql);
preparedSt.setString(1,"Bombit");
rs1 = preparedSt.executeQuery();
preparedSt.setString(1,"BitChip");
rs2 = preparedSt.executeQuery();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
As variáveis rs1 e rs2, após a execução do código acima apresentado, contêm os resultados dos
respectivos comandos SQL. O código acima apresentado é equivalente à seguinte versão em que
não são utilizados comandos SQL parametrizados.
51
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
String sql;
PreparedStatement preparedSt;
ResultSet rs1,rs2;
try {
sql = "SELECT marca FROM Fornecedores " +
"WHERE nome = Bombit";
preparedSt = con.prepareStatement(sql);
rs1 = preparedSt.executeQuery();
sql = "SELECT marca FROM Fornecedores " +
"WHERE nome = BitChip";
preparedSt = con.prepareStatement(sql);
rs2 = preparedSt.executeQuery();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Com podemos ver no código apresentado na primeira versão, com comandos SQL
parametrizados, o método setString tem dois argumentos. A sintaxe deste método é de
seguida apresentada:
void setString(int índice, String valor)
throws SQLException;
O primeiro argumento indica qual o índice do parâmetro no comando SQL, o segundo a valor a
atribuir ao parâmetro. Note-se que o primeiro índice é 1 e não 0.
O segundo comando SQL apresentado nesta secção têm dois parâmetros, o primeiro relativo à
categoria, e o segundo ao preço.
SELECT marca, modelo
FROM Produtos
WHERE categoria = ? AND preço
< ? AND promoção = 'S';
52
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Um exemplo do código para a criação da sua versão pré-compilada, e instanciação, é em seguida
apresentado:
String sql;
PreparedStatement preparedSt;
ResultSet rs1;
try {
sql = "SELECT marca, modelo" +
"FROM Produtos" +
"WHERE categoria = ? AND preço
"AND promoção = 'S'";
< ? " +
preparedSt = con.prepareStatement(sql);
preparedSt.setString(1,"Portátil");
preparedSt.setFloat(2,2000.0);
rs1 = preparedSt.executeQuery();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Relativamente a este exemplo, vamos rever as instruções para instanciar os parâmetros do
comando SQL:
preparedSt.setString(1,"Portátil");
preparedSt.setFloat(2,2000.0);
Como podemos observar foi utilizado o método correspondente para cada tipo de dados, ou seja
o método setString para o atributo categoria, e o método setFloat para o atributo
preço. Alternativamente, poderíamos ter escrito, o seguinte código:
preparedSt.setString(1,"Portátil");
preparedSt.setString(2,”2000.0”);
O método setString pode ser utilizado como um método genérico.
53
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
3.3 IN VO C A Ç Ã O D E STO R ED PR O C ED U R ES
Stored Procedures é o nome dado a métodos armazenados na própria base de dados. Estes
métodos são semelhantes a métodos convencionais das linguagens de programação no sentido
em que podem ter argumentos de entrada e produzir um resultado.
Teoricamente, a execução de um stored procedure será uma das formas mais eficientes para
obter resultados de comandos SQL, uma vez que estes já se encontram pré-compilados. Um
stored procedure pode incluir conjuntos de comandos SQL, reduzindo assim o tráfego entre a
aplicação com JDBC e a base de dados, uma vez que só é necessário enviar o nome do stored
procedure para executar múltiplos comandos.
De um ponto de vista de organização da aplicação, a utilização de stored procedures também é
uma forma de garantir a separação total entre a camada de dados e a camada computacional. Ao
embeber comandos SQL numa aplicação, estamos a estabelecer uma ligação explicita entre a
aplicação e a estrutura da base de dados. Caso a estrutura da base de dados seja alterada torna-se
necessário editar o código da aplicação, de forma a alterar todos os comandos SQL afectados.
Com a utilização de stored procedures só é necessário realizar alterações na base de dados, sem
ser necessário alterar e recompilar a aplicação.
Na sua forma mais simples, os stored procedures representam um comando SQL. Por exemplo,
vamos supor que o gestor da bitShop pretendia um método para reduzir o preço de todos os seus
produtos por uma determinada percentagem, devido aos saldos da época Natalícia. Neste caso o
gestor pode optar por criar um comando pré-compilado no código da sua aplicação, ou por
definir um stored procedure na própria base de dados.
A título de exemplo, é agora apresentada um stored procedure, criado na base de dados MS SQL
Server 7.0, para este efeito.
CREATE PROCEDURE spSaldos
AS UPDATE Produtos
SET preço = preço * 0.95
54
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O stored procedure apresentado reduz os preços de todos os produtos, aplicando um desconto de
5% ao preço. A terminar a época dos saldos, o gestor pretende retornar os preços ao seu valor
original, tendo para este efeito definido o seguinte stored procedure:
CREATE PROCEDURE spResetSaldos
AS UPDATE Produtos
SET
preço = preço * (1 / 0.95)
Como foi mencionado anteriormente, um stored procedure também pode ter argumentos. Por
exemplo podemos escrever um stored procedure para colocar os produtos de uma determinada
marca em promoção.
CREATE PROCEDURE spSetPromo
(@marca_1
varchar)
AS UPDATE Produtos
SET promoção = 'Y'
WHERE marca = @marca_1
A utilização de stored procedures permite a execução de uma sequência de comandos SQL. Por
exemplo, o gestor da bitShop pretende regularmente obter informação sobre marcas,
nomeadamente quais são os fornecedores e quais as categorias disponibilizadas pela marca.
Neste caso, podemos construir um stored procedure parametrizado e com múltiplos comandos
SQL de forma a realizar todas as tarefas pretendidas.
CREATE PROCEDURE spInfoMarca
(@marca1 VARCHAR(15))
AS
SELECT nome
FROM Fornecedores
WHERE marca = @marca1
SELECT categoria
FROM Produtos
WHERE marca = @marca1
De facto, na maior parte dos sistemas de bases de dados, a sintaxe para escrita de stored
procedures é muito mais rica, não se limitando a permitir a invocação sequências de comandos
SQL. No entanto, a descrição detalhada das potencialidades de stored procedures sai fora do
âmbito deste livro.
55
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Como podemos inferir dos exemplos acima, a sintaxe para estes casos mais simples é:
CREATE PROCEDURE nome (listaDeParâmetros)
AS sequênciaDeComandosSQL
Para podermos executar um stored procedure armazenado numa base de dados é necessário
primeiro invocar o método prepareCall. Este método está definido na interface Connection,
a semelhança do que ocorre com os métodos para comandos SQL. O método cria um objecto,
que implementa a interface CallableStatement, com uma referência ao stored procedure
para
posterior
invocação.
A
interface
CallableStatement
é
sub-interface
de
PreparedStatement.
A sua sintaxe é:
CallableStatement prepareCall(String nomeSP)
throws SQLException;
Caso o stored procedure tenha parâmetros estes podem ser instanciados da mesma forma que os
comandos SQL parametrizados. Após a criação da referência, e instanciação de possíveis
parâmetros, podemos então executar o stored procedure respectivo. Os métodos para este efeito
são herdados da interface PreparedStatement, a saber:
boolean execute()
throws SQLException;
ResultSet executeQuery()
throws SQLException;
int executeUpdate()
throws SQLException;
O método executeQuery deve ser utilizado nos casos em que o stored procedure contem um
único comando SELECT. De igual modo o método executeUpdate também deve ser executado
quando o stored procedure contem um único comando SQL de actualização, i.e. um comando
que não seja uma selecção. O método execute deve ser invocado quando o stored procedure
contem múltiplos comandos SQL.
Consideremos então o stored procedure spSaldos, apresentado anteriormente. O seguinte excerto
de código ilustra a sua execução. Assume-se que a variável con, do tipo Connection,
56
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
representa uma ligação activa à base de dados. Uma vez que o stored procedure spSaldos
contem um único comando SQL do tipo UPDATE, iremos utilizar o método executeUpdate
para a sua execução.
CallableStatement callSt;
try {
callSt = con.prepareCall("{ call spSaldos }");
n = callSt.executeUpdate();
System.out.println("actualizações = " + n);
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
O próximo exemplo é baseado no stored procedure spInfoMarca definido acima. Este stored
procedure é parametrizado e contem dois comandos SELECT. Uma vez que estamos perante
múltiplos comandos SQL, iremos utilizar o método execute para a execução do stored
procedure. O resultado deste método é um booleano que nos indica qual o tipo de comando que
foi executado em primeiro lugar: true para comandos SELECT; false para todos os outros
comandos.
Os métodos para aceder aos resultados dos comandos SQL são os mesmos que foram
apresentados quando se discutiu a forma de aceder a resultados utilizando comandos o método
execute para comandos SQL simples, a saber:
ResultSet getResultSet()
throws SQLException;
int getUpdateCount()
throws SQLException;
O método getResultSet deve ser utilizado para comandos SELECT, enquanto que o método
getUpdateCount é indicado para todos os outros comandos.
57
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O excerto de código que se segue ilustra a execução do stored procedure spInfoMarca e a
obtenção do resultado relativo ao primeiro comando. Assume-se que a variável con representa
uma ligação activa à base de dados.
CallableStatement callSt;
ResultSet rs;
try {
callSt = con.prepareCall("{call spInfoMarca(?)}");
callSt.setString(1,"ASUS");
boolean b = callSt.execute();
rs = callSt.getResultSet();
// apresentação de resultados
displayRS(rs);
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Uma vez que sabemos que o stored procedure contem dois comandos SELECT não é necessário
verificar o valor devolvido pelo método execute. Note-se a presença do símbolo '?' na
invocação ao stored procedure. Tal como nos comandos parametrizados, a presença de um '?'
indica um parâmetro.
O acesso aos resultados seguintes é obtido iterativamente através do método getMoreResults
definido na interface Statement. A sintaxe deste método é agora apresentada:
boolean getMoreResults()
throws SQLException;
O resultado deste método indica-nos o tipo de comando SQL: true para comandos SELECT;
false para todos os outros comandos, ou caso não existam mais comandos. Note-se que quando
o resultado é false a situação é ambígua. No entanto no nosso exemplo, sabemos que temos
dois comandos SELECT e portanto sabemos à priori qual o número de resultados esperados.
Iremos ver mais à frente como desambiguar a situação num caso genérico.
Por enquanto vamos continuar com o nosso exemplo anterior e apresentar a forma de obter o
segundo ResultSet.
58
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
CallableStatement callSt;
ResultSet rs;
try {
callSt = con.prepareCall("{call spInfoMarca(?)}");
callSt.setString(1,"ASUS");
boolean b = callSt.execute();
rs = callSt.getResultSet();
displayRS(rs);
b = callSt.getMoreResults();
rs = callSt.getResultSet();
displayRS(rs);
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Vamos agora analisar um caso genérico, queremos executar um stored procedure de nome
spXpto, em relação ao qual desconhecemos o tipo e número de comandos SQL. Como foi
mencionado anteriormente, caso o resultado de getMoreResults seja false, ou estamos
perante um comando de actualização, ou terminou a lista de resultados do stored procedure.
Para desambiguar esta situação é necessário recordar qual o resultado do método
getUpdateCount. Este método devolve um número maior ou igual a zero caso o comando SQL
seja um comando de actualização. Em todas as outras situações o resultado é -1. A forma de
desambiguar esta situação pode agora ser resumida na seguinte expressão. A lista de resultados
terminou se:
b = callSt.getMoreResults();
(!b && (callSt.getUpdateCount() == -1)
O seguinte excerto de código ilustra uma forma de iterar nos resultados obtidos para stored
procedures com múltiplos comandos SQL.
CallableStatement callSt;
59
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
ResultSet rs;
int cont;
try {
callSt = con.prepareCall("{call spXpto }");
boolean b = callSt.execute();
do {
if (b) { // comandos SELECT
rs = callSt.getResultSet();
displayRS(rs);
}
else { // outros comandos
cont = callSt.getUpdateCount();
System.out.println("actualizações : " + cont);
}
b = callSt.getMoreResults();
} while ( b || callSt.getUpdateCount() != -1);
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
STO R ED PR O C ED U R ES EM M
IC R O SO FT
A C C ESS
Na base de dados Microsoft Access a invocação de um stored procedure equivale à execução de
uma consulta (query) definida no ambiente da aplicação Access.
60
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Figura 2 - Consultas definidas no ambiente Access.
Como se pode observar, na base de dados encontra-se já definida uma consulta. O código SQL
relativo à consulta porMarca é o seguinte:
SELECT modelo, preço
FROM Produtos
WHERE marca=m;
Note-se que no código SQL aparece um parâmetro m. No ambiente Access, todas as referências
que não correspondam a colunas das tabelas definidas na base de dados são automaticamente
consideradas como parâmetros.
O código para invocar as consultas definidas no ambiente Access é equivalente ao apresentado
para o caso geral de um stored procedure numa base de dados. O código que se segue poderia
ser utilizado para invocar, e apresentar a consulta porMarca.
Considera-se que a variável con representa uma ligação activa à base de dados.
static ResultSet rs;
static CallableStatement callSt;
try {
61
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
callSt = con.prepareCall("{call porMarca(?)}");
callSt.setString(1,"ASUS");
rs = callSt.executeQuery();
displayRS(rs);
}
catch (SQLException e) {
System.out.println(e.getMessage());
3.4 E N VIO D E C O M A N D O S EM B A TC H
Até agora foram analisadas formas de enviar à base de dados comandos SQL, ou executar stored
procedures. O processo descrito até agora implica a submissão individual de cada um destes
pedidos. Tendo em vista a melhor performance possível, a API JDBC 2.0 permite-nos enviar
uma sequência de comandos em simultâneo. Desta forma a comunicação entre o driver e a base
de dados é reduzida e, teoricamente, é obtido um ganho de performance.
Ao falar de performance, é sempre preciso lembrar que a implementação dos drivers é um dos
factores a ter em conta. Para algumas bases de dados existem literalmente dezenas de drivers,
cujo nível de eficiência e totalidade de implementação varia significativamente. A base de dados
também é um factor importante. Por muito eficiente que um driver seja, está sempre limitado
pela performance da própria base de dados.
As seguintes interfaces suportam o envio de comandos em batch:
•
Statement
62
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
•
PreparedStatement
•
CallableStatement
A funcionalidade no modo batch está restrita a comandos de actualização, ou seja, todos os
comandos SQL, excepto comandos SELECT. Em termos de stored procedures a restrição aplicase ao tipo de comandos, e também ao seu número. Um stored procedure só pode ter um único
comando SQL, e este não pode ser um comando SELECT.
As interfaces Statement, PreparedStatement e CallableStatement definem os métodos
necessários para adicionar comandos SQL, ou referências a stored procedures a uma sequência.
Para comandos totalmente especificados, i.e. sem parâmetros, e não pré-compilados utiliza-se o
seguinte método definido na interface Statement:
void addBatch(String sql)
throws SQLException;
Para comandos SQL, ou stored procedures parametrizados utiliza-se o seguinte método definido
na interface PreparedStatement:
void addBatch() throws SQLException;
Para executar a sequência de comandos invoca-se o método executeBatch definido na
interface Statement. A sintaxe para este método é:
int[] executeBatch() throws SQLException;
Conforme especificado na assinatura este método lança uma excepção do tipo SQLException
caso ocorra algum problema na ligação à base de dados, ou o driver não suporte . Este método
pode ainda lançar a excepção BatchUpdateException, subclasse de SQLException, caso a
execução de algum dos comandos SQL falhe. Neste caso cabe ao driver decidir se continua ou
não a executar os restantes comandos da sequência.
O resultado devolvido por este método é um array de inteiros, em que o valor de cada posição do
array indica o número de linhas afectadas pelo comando SQL respectivo. Note-se que a ordem
63
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
dos comandos é a ordem pela qual são adicionados à sequência. Os valores possíveis para cada
posição do array são:
•
um número maior ou igual a zero para comandos executados sem problemas e cujo
número de linhas afectadas é conhecido
•
-2 caso o comando seja executado sem problemas mas o driver não consiga descobrir
qual o número de linhas afectadas
•
-3 para os comandos cuja execução falhou.
O seguinte excerto de código apresenta a construção de uma sequência de comandos simples, ou
seja sem parâmetros e sem pré-compilação:
try {
Statement st = con.createStatement();
String sql = "UPDATE Produtos " +
"SET promoção='S' WHERE marca='ASUS'";
st.addBatch(sql);
sql= "UPDATE Produtos " +
"SET preço=preço*0.7 WHERE marca='Toshiba'";
st.addBatch(sql);
sql= "INSERT INTO Fornecedores " +
"VALUES ('Toshiba','Qwerty')";
st.addBatch(sql);
int res[] = st.executeBatch();
// Mostrar resultados
for (int i=0; i < res.length; i++) {
System.out.println("O comando " + i +
" afectou " + res[i] + " linhas");
}
}
catch (BatchUpdateException e) {
System.out.println(e.getMessage());
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
64
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Recordemos o conteúdo da tabelas Produtos.
Tabela Produtos
código
marca
modelo
categoria
preço
promoção
01
ASUS
V8100 De Luxe
Placa Gráfica
547
N
02
ASUS
NoteBook L8400C
Portátil
1995
S
03
Toshiba
Satelite 3100
Portátil
1950
N
04
Creative
Audigy 5.1 OEM
Placa Audio
103
S
05
Compaq
IPaq Pocket PC
PDA
745
N
06
IBM
DeskStar 60GB
Disco IDE
283
N
O resultado do código acima apresentado seria:
O comando 0 afectou 2 linhas
O comando 1 afectou 1 linhas
O comando 2 afectou 1 linhas
O resultado do último comando será sempre 1 caso a execução seja bem sucedida, uma vez que
consiste na inserção de uma linha. Caso a linha a inserir introduzisse uma chave repetida seria
lançada uma excepção do tipo BatchUpdateException e o resultado seria -3.
No caso de comandos SQL ou stored procedures parametrizados, as sequências tem de ser
constituídas pelo mesmo comando, instanciado múltiplas vezes. Caso se pretenda utilizar
comandos variados a única opção é utilizar a interface Statement para criar os comandos, ou
seja comandos não parametrizados e sem pré-compilação.
O excerto de código que se segue apresenta um exemplo utilizando comandos parametrizados.
try {
String sql = "INSERT INTO Fornecedores " +
"VALUES (?,?) ";
PreparedStatement preparedSt;
preparedSt = con.prepareStatement(sql);
65
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
preparedSt.setString(1,"ASUS");
preparedSt.setString(2,"BomBit");
preparedSt.addBatch();
preparedSt.setString(1,"Creative");
preparedSt.setString(2,"ChipChip");
preparedSt.addBatch();
preparedSt.setString(1,"IBM");
preparedSt.setString(2,"BitChip");
preparedSt.addBatch();
preparedSt.setString(1,"Toshiba");
preparedSt.setString(2,"MegaChip");
preparedSt.addBatch();
preparedSt.setString(1,"Compaq");
preparedSt.setString(2,"AGPBit");
preparedSt.addBatch();
preparedSt.executeBatch();
}
catch (BatchUpdateException e) {
System.out.println(e.getMessage());
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Como podemos observar no código apresentado, o comando é instanciado múltiplas vezes e cada
instância é adicionada à sequência.
Para finalizar esta secção apresenta-se de seguida o método clearBatch, definido na interface
Statement. O seu propósito é eliminar os comandos previamente inseridos numa sequência
com o método addBatch. A sintaxe para este comando é a seguinte:
void clearBatch() throws SQLException;
66
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
4 M A N IPU LA Ç Ã O A VA N Ç A D A D E R ESU LTA D O S D E C O M A N D O S
SELEC T
A execução de um comando SQL SELECT retorna como resultado um ResultSet. Como vimos
anteriormente, por omissão, um ResultSet só permite a navegação sequencial e unidirecional,
ou seja a única operação de navegação permitida é avançar o cursor para a linha seguinte.
Para se obter um ResultSet, resultado da execução de um comando SELECT, é necessário
primeiro criar um objecto do tipo Statement. O mesmo se aplica a objectos do tipo
PreparedStatement e CallableStatemet. Como estas interfaces são sub-interfaces de Statement,
um objecto de qualquer um destes últimos tipos também é um Statement. Sendo assim, ao
mencionar que um objecto é do tipo Statement, está-se implicitamente a considerar também que
o objecto pode ser um PreparedStatement ou um CallableStatement.
Nos capítulos anteriores foram exploradas diversas alternativas para executar um comando
SELECT numa base de dados. Os métodos utilizados para criar um Statement foram os
seguintes:
Statement createStatement()
throws SQLException;
PreparedStatement prepareStatement()
throws SQLException;
CallableStatement prepareCall()
throws SQLException;
Como foi mencionado anteriormente, ao criar objectos do tipo Statement com estes métodos, o
ResultSet resultante da execução do respectivo comando só permite a navegação sequencial e
unidirecional.
No entanto, a API JDBC permite criar um ResultSet de acesso aleatório, i.e. com acesso
directo a qualquer linha, e por consequência com navegação bidirecional. Neste caso, o cursor do
67
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
ResultSet pode-se mover livremente em qualquer direcção.De facto, até é possível utilizar um
ResultSet para alterar os dados presentes na base de dados, eliminando parcialmente a
necessidade de executar comandos SQL de actualização de dados.
Para se obter um ResultSet com este nível de versatilidade utilizam-se os métodos para
criação de Statements da interface Connection, homónimos aos anteriormente apresentados,
desta vez com parâmetros. Apresentam-se em seguida os métodos na sua versão com parâmetros:
Statement createStatement(int tipo, int concorrência)
throws SQLException;
PreparedStatement prepareStatement(String sql, int tipo,
int concorrência)
throws SQLException;
CallableStatement prepareCall(String sp, int tipo,
int concorrência)
throws SQLException;
O parâmetro tipo pode ter os seguintes valores:
•
TYPE_FORWARD_ONLY
•
TYPE_SCROLL_INSENSITIVE
•
TYPE_SCROLL_SENSITIVE
As constantes apresentadas encontram-se definidas na interface ResultSet. O valor
TYPE_FORWARD_ONLY implica que o ResultSet resultante da execução do comando SQL
é acessível somente de forma sequencial e unidirecional. Quando um ResultSet é criado com o
valor
TYPE_SCROLL_INSENSITIVE
ou
TYPE_SCROLL_SENSITIVE
obtemos
um
ResultSet com acesso aleatório, e por consequência bidirecional.
A diferença entre estes dois últimos valores reside na forma como é encarado o problema do
acesso concorrente à base de dados. No primeiro caso o ResultSet não é sensível à alterações
na base de dados sobre dados que se encontram replicados no ResultSet. Ou seja, se um
68
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
ResultSet for resultado de um comando SELECT, e posteriormente os dados seleccionados
forem alterados na base de dados, o ResultSet não irá reflectir estas alterações. No segundo
caso o ResultSet pode ser "refrescado" de forma a reflectir alterações realizadas por terceiros.
O segundo parâmetro dos métodos apresentados, define se o ResultSet pode ser utilizado para
actualizar directamente os valores na base de dados. Os valores possíveis para este parâmetro
são:
•
CONCUR_READ_ONLY
•
CONCUR_UPDATABLE
As constantes acima apresentadas encontram-se definidas na interface ResultSet. Caso um
Statement seja criado com o parâmetro CONCUR_UPDATABLE, torna-se possível utilizar o
ResultSet resultante para actualizar os valores na base de dados directamente sem ser
necessário recorrer a comandos SQL INSERT, DELETE, ou UPDATE. Desta forma, as
actualizações registadas num ResultSet são automaticamente replicadas na base de dados. Se
um Statement for criado com o parâmetro CONCUR_READ_ONLY, só é possível realizar
operações de leitura no ResultSet.
A utilização dos métodos apresentados sem parâmetros, tal como apresentados nos capítulos
anteriores é equivalente a utilizar os seguintes valores para os parâmetros:
•
tipo = TYPE_FORWARD_ONLY
•
concorrência = CONCUR_READ_ONLY
Por exemplo, a criação de um Statement utilizando o método sem parâmetros,
st = con.createStatement();
69
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
é equivalente a
st = con.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
sendo con uma Connection e st um Statement.
Nas secções seguintes será apresentada a forma de navegação aleatória e actualização de um
ResultSet e por consequência a actualização dos valores presentes na base de dados.
4.1 R ESU LTA D O S N A VEG Á VEIS A LEA TO R IA M EN TE
Como foi mencionado anteriormente, a forma de navegação de um ResultSet é definida na altura
da criação do objecto tipo Statement. Caso o valor especificado para o parâmetro tipo seja
ResultSet.TYPE_SCROLL_INSENSITIVE
ou
ResultSet.TYPE_SCROLL_SENSITIVE
o ResultSet resultante da execução de um comando SELECT é navegável de forma aleatória.
Um ResultSet nestas condições contem um cursor, apontador para a linha actual do resultado,
que pode ser movido livremente em ambas as direcções. A API JDBC permite que a navegação
seja realizada de forma absoluta ou relativa. A excepção SQLException será lançada se o tipo do
ResultSet for TYPE_FORWARD_ONLY.
N A VEG A Ç Ã O R ELA TIVA
Dentro de um ResultSet é possível avançar ou recuar o cursor uma linha através dos seguintes
métodos:
boolean next() throws SQLException;
boolean previous() throws SQLException;
70
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O resultado destes métodos será true se o cursor não ficar posicionado para alem da última linha,
ou antes da primeira linha, respectivamente.
A API JDBC fornece ainda um método de deslocamento relativo em relação à linha actual.
boolean relative(int deslocamento) throws SQLException;
Um valor negativo desloca o cursor para trás, enquanto que um valor positivo avança o cursor.
Se o deslocamento exceder os limites do ResultSet, o cursor ficará posicionado antes da
primeira linha, para valores negativos, ou depois da última linha, para valores positivos. Neste
caso o valor devolvido por este método será false. A invocação deste método com o valor zero
não surte qualquer efeito.
O método relative lança a excepção SQLException caso o cursor não se encontre numa
linha do ResultSet, ou seja caso se encontre antes da primeira linha ou depois da última.
N A VEG A Ç Ã O A BSO LU TA
O posicionamento do cursor de um ResultSet pode ser feito através de instruções de
posicionamento absoluto. A API JDBC fornece um método para posicionar o cursor na primeira
ou ultima linha:
boolean first() throws SQLException;
boolean last() throws SQLException;
O resultado destes método é true, excepto no caso particular em que o ResultSet seja vazio,
i.e. não contenha nenhuma linha, tendo neste caso o valor false.
Também é possível posicionar o cursor directamente numa linha através do seu índice:
boolean absolute(int linha) throws SQLException;
Este método aceita valores negativos como parâmetro, sendo o deslocamento efectuado a partir
da última linha do ResultSet. Por exemplo absolute(-1) é equivalente a last(). O valor
zero não é considerado um parâmetro válido para este método, sendo neste caso lançada uma
71
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
excepção do tipo SQLException. Caso o valor exceda o número de linhas do ResultSet, o
cursor ficará posicionado a seguir à última linha.
Para o método absolute o valor devolvido será true se o índice corresponder a uma linha do
ResultSet.
Os métodos que se seguem permitem colocar o cursor antes da primeira linha, ou depois da
última, respectivamente.
void afterLast() throws SQLException;
void beforeFirst() throws SQLException;
Estes métodos são úteis no caso em que se pretende iterar sobre todos as linhas do ResultSet.
E X EM PLO S
O seguinte excerto de código itera sobre todas as linhas de um ResultSet desde a primeira até
à última. A variável con representa uma ligação activa à base de dados.
try {
sql = "SELECT marca, modelo" +
"FROM Produtos" +
"WHERE categoria = 'Portátil' " +
"AND promoção = 'S'";
st = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery(sql);
rs.beforeFirst();
while (rs.next()) {
// apresentar linha actual
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
72
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O mesmo efeito pode ser conseguido através do seguinte excerto de código que utiliza a método
relative:
try {
sql = "SELECT marca, modelo" +
"FROM Produtos" +
"WHERE categoria = 'Portátil' " +
"AND promoção = 'S'";
st = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery(sql);
if (rs.first())
while (rs.relative(1)) {
// apresentar linha actual
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
Embora mais complicado, também é possível iterar sobre as linhas de um ResultSet utilizando
o método absolute, como é ilustrado no seguinte excerto de código:
try {
sql = "SELECT marca, modelo" +
"FROM Produtos" +
"WHERE categoria = 'Portátil' " +
"AND promoção = 'S'";
st = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery(sql);
i = 1;
while (rs.absolute(i)) {
// apresentar linha actual
i++;
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
73
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
Nota: Utilizando o driver JDBC-ODBC em ligações às bases de dados SQLServer e Access, o
método absolute não consegue chegar à ultima linha. Neste caso o excerto de código acima
apresentado imprime desde a primeira até à penúltima linha.
Caso se pretenda, pode-se ainda apresentar as linhas pela ordem inversa à presente no
ResultSet:
try {
sql = "SELECT marca, modelo" +
"FROM Produtos" +
"WHERE categoria = 'Portátil' " +
"AND promoção = 'S'";
st = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
rs = st.executeQuery(sql);
rs.afterLast();
while (rs.previous()) {
// apresentar linha actual
}
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
PO SIÇ Ã O A C TU A L D O C U R SO R
A API JDBC fornece ainda um conjunto de métodos para recolher informação sobre a posição
actual do cursor. Entre estes encontram-se os métodos para indagar se o cursor se encontra antes
da primeira linha, ou depois da última, respectivamente.
boolean isBeforeFirst() throws SQLException;
boolean isAfterLast() throws SQLException;
74
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
O par de métodos que se segue fornece indicação sobre se o cursor se encontra na primeira ou
última linha, respectivamente:
boolean isFirst() throws SQLException;
boolean isLast() throws SQLException;
Finalmente, o método getRow indica em que linha o cursor se encontra.
int getRow() throws SQLException;
Caso o cursor esteja posicionado antes da primeira linha, ou depois da última, o resultado é zero.
75
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
5 T R A N SA C Ç Õ ES
Por omissão, quando um comando que altere a estrutura da base de dados ou os dados contidos
em tabelas é enviado à base de dados, esta é actualizada automaticamente e as alterações
realizadas são definitivas. Neste caso a única forma de retroceder a base de dados ao estado
anterior ao envio do comando consiste em enviar um ou mais comandos que realizem a operação
inversa.
Este tipo de procedimento nem sempre é conveniente. Por vezes é necessário garantir que as
alterações realizadas só são registadas definitivamente caso todo um grupo de comandos SQL
seja executado com sucesso. Caso ocorra um erro num dos comandos SQL, os efeitos dos
comandos do mesmo grupo anteriormente executados devem então ser anulados, i.e. o estado da
base de dados retorna ao estado anterior à execução do primeiro comando do grupo.
As transacções são uma solução elegante para este problema, permitindo agrupar uma sequência
de alterações. As alterações provocadas por comandos SQL dentro de uma transacção só são
tornados definitivos quando a transacção é encerrada. Enquanto a transacção se encontrar aberta
todas as alterações são provisórias. Ao terminar a transacção a sequência de alterações realizadas
é registada na base de dados como definitiva.
Por omissão, em JDBC uma transacção é constituída por um único comando SQL. Diz-se que a
ligação à base de dados está em modo auto-commit, ou seja cada alteração individual é
imediatamente registada na base de dados definitivamente.
A interface Connection define um método para desactivar este modo, permitindo assim a
especificação de transacções com sequências de comandos SQL. A sua assinatura é agora
apresentada:
void setAutoCommit(boolean commit)
throws SQLException;
76
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
A invocação deste método com o parâmetro false, para além de desactivar o modo autocommit, inicia também uma transacção. A interface Connection define ainda um método para
inquirir sobre o modo actual. A sua assinatura é:
boolean getAutoCommit() throws SQLException;
Quando a ligação não se encontra no modo auto-commit, as alterações realizadas são provisórias
podendo a qualquer momento a aplicação pedir à base de dados que retorne ao estado anterior ao
inicio da transacção. Sendo assim será necessário indicar à base de dados quando se pretende
terminar a transacção, e, por consequência, registar definitivamente as alterações realizadas. A
interface Connection define um método para este fim, cuja assinatura se apresenta em seguida:
void commit() throws SQLException;
A invocação deste método tem um efeito duplo: termina a transacção actual, e inicia uma nova
transacção.
Como foi mencionado anteriormente as alterações provocadas durante uma transacção são
provisórias até ao término da transacção, i.e. antes de se terminar uma transacção é sempre
possível pedir à base de dados que retorne ao estado imediatamente anterior ao inicio da
transacção. Esta operação denomina-se por rollback. A interface Connection define o seguinte
método para este fim:
void rollback();
Apresenta-se em seguida um exemplo em que se pretende realizar uma transacção com uma
sequência de comandos SQL parametrizados. O objectivo é registar as alterações na base de
dados como definitivas só se todas as alterações forem realizadas com sucesso, i.e. se nenhum
dos comandos SQL envolvidos lançar uma excepção.
77
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
try{
// desactivação do modo auto-commit
con.setAutoCommit(false);
// criação de um comando précompilado parametrizável
PreparedStatement s =
con.prepareStatement(“UPDADE Produtos "+
"SET preço = ? WHERE código = ?”);
// execução da primeira instância do comando
s.setFloat(1,1000.0);
s.setString(2,"01");
s.executeUpdate();
// execução da segunda instância do comando
s.setFloat(1,2000.0);
s.setString(2,"02");
s.executeUpdate();
// se tudo correr bem tornar as alterações definitivas
con.commit();
}
// em caso de erro
// anular quaisquer alterações já efectuadas
catch (SQLException e) {
try {
con.rollback();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
}
78
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
As transacções também podem ser utilizadas ao enviar sequências de comandos SQL em modo
batch.
try{
// desactivação do modo auto-commit
con.setAutoCommit(false);
// criação de um comando précompilado parametrizável
PreparedStatement s =
con.prepareStatement(“UPDADE Produtos "+
"SET preço = ? WHERE código = ?”);
// adicionar à sequência a primeira instância do comando
s.setFloat(1,1000.0);
s.setString(2,"01");
s.addBatch();
// adicionar à sequência a segunda instância do comando
s.setFloat(1,2000.0);
s.setString(2,"02");
s.addBatch();
// envio da sequência de comandos à base de dados
s.executeBatch();
// se tudo correr bem tornar as alterações definitivas
con.commit();
}
// em caso de erro
// anular quaisquer alterações já efectuadas
catch (SQLException e) {
try {
con.rollback();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
}
É importante realçar que após a execução do método commit as alterações são tornadas
definitivas, e que o método rollback não pode anular os comandos SQL executados antes da
invocação do método commit. O exemplo seguinte assume a existência de dois grupos de
comandos SQL e ilustra uma situação em que o método commit é invocado após a execução de
cada grupo.
79
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
try{
// desactivação do modo auto-commit
con.setAutoCommit(false)
// colocar aqui a execução do primeiro grupo de comandos
.....
// se tudo correr bem tornar as alterações definitivas
con.commit();
// colocar aqui a execução do segundo grupo de comandos
.....
// se tudo correr bem tornar as alterações definitivas
con.commit();
}
// em caso de erro anular
// as alterações desde o último commit
catch (SQLException e) {
try {
con.rollback();
}
catch (SQLException e) {
System.out.println(e.getMessage());
}
}
No exemplo acima, caso ocorra uma excepção no segundo grupo de comandos o método
rollback é executado. Uma vez que as alterações realizadas pelo primeiro grupo de comandos já
foram tornadas definitivas pelo método commit, o método rollback só anula as alterações
realizadas pelos métodos executados por comandos do segundo grupo anteriores ao comando em
que ocorreu o erro.
80
JDBC – APONTAMENTOS PEDAGÓGICOS PARA DSI
6 R EFER ÊN C IA S
Seth White, Maydene Fisher, Rick Cattell, Graham Hamilton, Mark Hapner, JDBC(TM) API
Tutorial and Reference: Universal Data Access for the Java(TM) 2 Platform (2nd Edition),
Addison-Wesley, 1999.
Maydene
Fisher,
The
JDBC
2.0
Optional
Package,
http://java.sun.com/products/jdbc/articles/package2.html.
81

Documentos relacionados

JDBC – Trabalhando com banco de dados em Java

JDBC – Trabalhando com banco de dados em Java banco de dados. A classe Driver proporciona à JDBC um ponto de partida para a conectividade de banco de dados respondendo às requisições de conexão de DriverManager e fornecendo informações sobre a...

Leia mais

Acesso a Banco de Dados com JDBC

Acesso a Banco de Dados com JDBC É mais utilizado para banco de dados antigos como estatais de governos. Aplicação JDBC Driver Manager

Leia mais