Você está na página 1de 99

Apostila de Java

Tópicos Avançados
Prof. Carlos Ribeiro

Setembro de 1999
_______________________________________________________________________________________

Índice
TRATAMENTO DE EXCEÇÕES E DEBUG DE PROGRAMAS ..........................................................4

Manipulando Erros ......................................................................................................................................4


Classificação das Exceções .........................................................................................................................5
Anunciando Exceções que um Método pode Gerar.....................................................................................7
Como Gerar uma Exceção...........................................................................................................................9
Criando uma Classe de Exceção................................................................................................................10
Capturando Exceções ................................................................................................................................12
Capturando Múltiplas Exceções ................................................................................................................14
para obter o tipo real do objeto exceção. ...................................................................................................14
Regerando Exceções..................................................................................................................................15
Cláusula Finally.........................................................................................................................................15
Algumas Dicas na Utilização de Exceções................................................................................................17
Técnicas de Debugging .............................................................................................................................18
Verificando Condições de Erro .................................................................................................................19
Exibindo Mensagens de Debug em um Programa Gráfico........................................................................21
Utilizando o JDB Degugger ......................................................................................................................22

STREAMS E ARQUIVOS..........................................................................................................................28

Streams ......................................................................................................................................................28
Lendo e Escrevendo Bytes ........................................................................................................................28
Tipos de Streams .......................................................................................................................................30
Layering Streams Filters............................................................................................................................32
Object Streams...........................................................................................................................................34
Armazenando Objetos de Tipos Variados .................................................................................................34
O Problema de Salvar Referências a Objetos ............................................................................................39

ACESSANDO BANCOS DE DADOS .......................................................................................................46

Arquitetura de Três Camadas ....................................................................................................................48


Aplicações de uma Camada...................................................................................................................48
Aplicações de duas Camadas.................................................................................................................49
Aplicações Multicamadas......................................................................................................................49
Acessando um Banco de Dados com Java.................................................................................................52
Tipos de Drivers Oracle JDBC ..............................................................................................................53
SQLJ......................................................................................................................................................55
Desenvolvendo uma Aplicação SQLJ ...................................................................................................56
Traduzindo e Compilando um Programa SQLJ.....................................................................................58
JDBC 1.0 ...............................................................................................................................................59
Estabelecendo uma Conexão .............................................................................................................59
Como Carregar o Driver ....................................................................................................................60
Como fazer a Conexão ......................................................................................................................60
Criando Statements JDBC .................................................................................................................63
Entrando com Dados em uma Tabela ................................................................................................64
Obtendo Dados de uma Tabela..........................................................................................................66
Utilizando o método Next..................................................................................................................66
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 2
_______________________________________________________________________________________
Utilizando os Métodos getXXX ........................................................................................................66
Utilizando o Método getString ..........................................................................................................69
Atualizando Tabelas ..........................................................................................................................69
Utilizando Prepared Statement ..........................................................................................................71
Criando um objeto PreparedStatement ..............................................................................................71
Fornecendo Valores para Parâmetros de PreparedStatement.............................................................71
Utilizando um Loop para Atribuir Valores........................................................................................74
Códigos de Retorno para o Método executeUpdate ..........................................................................74
Utilizando Joins .................................................................................................................................76
Utilizando Transações .......................................................................................................................77
Chamando uma Stored Procedure .....................................................................................................80
Capturando Exceções ........................................................................................................................81
JDBC 2.0 ..............................................................................................................................................85
Navegando em um Cursor em Ambas as Direções............................................................................85
Atualizando Result Sets.....................................................................................................................89
Atualizando um Result Set Programaticamente ................................................................................89
Inserindo e Deletando Linhas Programaticamente ............................................................................90
Deletando uma Linha.........................................................................................................................94
Fazendo Atualizações em Modo Batch .............................................................................................96
Tratamento de Exceções em Batch Update .......................................................................................98

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 3
_______________________________________________________________________________________
TRATAMENTO DE EXCEÇÕES E DEBUG DE PROGRAMAS

Quando um erro ocorre você deve, no mínimo:

• Avisar que o erro ocorreu.


• Salvar todo o trabalho.
• Permitir que o usuário feche a aplicação por conta própria.

Para situações excepcionais, tais como: digitação de dados errados com o potencial de
“detonar” o programa, java utiliza uma maneira de perceber e tratar o erro denominada
tratamento de exceções.

A primeira parte deste capítulo aborda tratamento de exceções e a segunda parte veremos
técnicas para se encontrar erros antes que eles causem exceções em tempo de execução.
Infelizmente, se você utiliza apenas o JDK, você verá que a detecção de bugs é
exatamente como era nos primórdios da computação. Para diminuir a dor explicaremos
como utilizar o debbuger de linha de comando. Desenvolvedores sérios em Java devem
utilizar produtos tais como Java WorkShop da Sun, Visual Café da Symantec e o Jbuilder
da Inprise que possuem debuggers bastante úteis que por si só já valem o preço do
produto.

Manipulando Erros

A missão do tratamento do erro é transferir o controle da execução do ponto onde o erro


ocorreu para um manipulador de erros que pode tratar a situação.

Tipos de erros que podem ocorrer:

• Erros de entrada de dados: Como um URL com erro de sintaxe.


• Erros de dispositivos: A impressora pode estar desligada, o papel pode acabar no
meio de uma impressão, ou uma página Web pode estar temporariamente não
disponível.
• Limitações físicas: Um disco pode encher, ou a memória acabou.
• Erros de código: A utilização de um índice inválido para um array.

A reação tradicional a um erro é retornar um código de erro especial que o método


chamador pode analisar. Por exemplo, métodos que lêem informações de arquivos às
vezes retornam um –1 como marca de fim de arquivo. Este pode ser um método eficiente
para tratar muitas condições de exceções, e para certas operações de I/O Java retorna null
se a operação não foi bem sucedida. Infelizmente não é sempre possível retornar um
código de erro. Pode não haver uma forma obvia de distinguir dados válidos de inválidos.
Um método que retorne um número inteiro não pode simplesmente retornar –1
significando um erro, afinal o valor –1 pode ser um resultado perfeitamente válido.
Além do mais, é difícil manter a idéia de orientação a objetos retornando um número
inteiro como uma marca de erro.
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 4
_______________________________________________________________________________________
Em vez de retornar um valor como mencionado, Java permite que todo método possua
um caminho de saída alternativo, se ele não for capaz de terminar sua tarefa de forma
normal. Nesta situação o método não retorna um valor. Em vez disso, ele gera (throws)
um objeto que encapsula a informação de erro. Note que o método termina
imediatamente. Ele não retorna seu valor normal. E mais, Java não ativa o código que
chamou o método. Em vez disso, o mecanismo de tratamento de exceção inicia uma
busca pelo “exception handler” que pode lidar com esta situação de erro particular.

Exceções possuem sua própria sintaxe e são parte de uma hierarquia de herança especial.

Classificação das Exceções

Em Java um objeto exception é sempre uma instância de uma classe descendente de


Throwable, mas a hierarquia se ramifica imediatamente em: Error e Exception

A figura abaixo contém um diagrama simplificado da hierarquia exception em Java.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 5
_______________________________________________________________________________________
A hierarquia Errors descreve erros internos e a exaustão de recursos dentro do Java run-
time system. Pouco você pode fazer se um erro deste tipo ocorre, além de notificar o
usuário e tentar terminar o programa normalmente. Estas situações são raras.

Quando você está programando em Java, você lida com a hierarquia Exception. Esta
hierarquia também se subdivide em dois ramos.: exceções que derivam de
RuntimeException e as que não derivam. A regra geral é:

• Uma RuntimeException acontece quando você comete um erro de programação.

Exceções derivadas de RuntimeException incluem problemas como:

• Um cast mal feito.


• Um acesso fora dos limites de um array.
• Um acesso a um ponteiro null.

Exceções que não descendem de RuntimeException incluem:

• Tentativa de leitura após o final de um arquivo.


• Tentativa de abrir um URL mal formado.
• Tentativa de encontrar um objeto Class para um string que não representa uma classe
existente.

A regra “Se foi um erro de run-time a culpa é sua” funciona muito bem, isto é, você
poderia ter evitado. Em relação à má formação de URLs, sabe-se que diferentes browsers
podem manipular diferentes tipos de URLs. Por exemplo, Netscape pode lidar com
mailto:URL, enquanto outros browsers não! Logo, a noção de URL mal formado depende
do ambiente e não apenas do código.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 6
_______________________________________________________________________________________

Anunciando Exceções que um Método pode Gerar

Um método Java pode lançar uma exceção se ele encontra uma situação que não pode
tratar. A idéia é simples: um método não dirá apenas ao compilador que valores ele pode
retornar. Ele também dirá ao compilador o que pode ir errado. Por exemplo, um código
que tenta ler de um arquivo sabe que o arquivo pode não existir ou que o arquivo pode
estar vazio. Logo o código que tenta processar a informação no arquivo necessitará
notificar o compilador que ele pode gerar algum tipo de IOException.

O local onde você anuncia que seu método pode gerar uma exceção é no cabeçalho do
método. O header muda para refletir as exceções que ele pode gerar. Por exemplo: aqui
está um header para um método da classe BufferReader que deve ler uma linha de texto
de um stream. Obs: um stream pode ser proveniente de um arquivo ou uma conexão de
rede.

public String readLine() throws IOException

O header indica que este método retorna um string, mas também possui a capacidade de
dar errado de uma maneira especial – gerando um IOException. Se esta triste situação
acontecer o método não retornará um string e sim uma exceção do tipo IOException. Se
esta exceção ocorrer o run-time system começará a procurar um manipulador de exceções
que saiba como manipular objetos do tipo IOException.

Quando você escreve seus próprios métodos, você não precisa anunciar todo tipo de
throwable object que pode ser gerado pelo seu método. Para entender quando e o que
você deve anunciar na cláusula throws do seu método, mantenha em mente que uma
exceção é gerada em qualquer uma das quatro situações abaixo:

• Você chama um método que gera uma exceção, por exemplo, o método readLine da
classe BufferedReader.

• Você detecta um erro e gera uma exceção com o comando throw. (Veremos este
comando na próxima seção).

• Você comete um erro de programação, como a[-1] = 0.

• Um erro Java interno ocorre.

Se qualquer dos dois primeiros cenários acontecerem, você deve dizer às pessoas que vão
utilizar o seu método que a utilização do seu método pode causar a geração de uma
exceção.

Porque? Qualquer método que lança uma exceção é uma armadilha de morte em
potencial. Se nenhum handler capturar a exceção, o programa termina. Programadores

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 7
_______________________________________________________________________________________
que utilizam o seu código geralmente não gostam de ver seus programas terminando
inesperadamente como resultado da utilização do seu código tão bem feito.

As exceções que um método pode gerar devem ser declaradas no header no método.

class Animacao
{ . . .
public Image carregaImagem (String S) throws IOException
{ . . .
}
}

Se um método deve manipular mais de uma exceção, você deve indicar todas elas no
header. Separe-as por vírgulas, como no exemplo abaixo:

class Animacao
{ . . .
public Image carregaImagem (String S)
throws EOFException, MalformedURLException
{ . . .
}
}

No entanto, você não necessita anunciar erros Java internos, isto é, exceções
descendentes de Error. Qualquer código potencialmente poderia gerar uma exceção deste
tipo, e elas estão inteiramente fora do nosso controle.

Da mesma forma, você não deveria anunciar exceções descendentes de


RuntimeException.

class Animacao
{ . . .
void drawImage (int i)
throws ArrayIndexOutOfBoundsException // Não faça isso!
{ . . .
}
}

Estes erros de run-time estão completamente sob o seu controle. Conserte o erro em vez
de anunciar que ele pode acontecer.

A especificação da linguagem Java chama de unchecked exception qualquer exceção que


derive da classe Error ou da classe RuntimeException. Todas as outras exceções são
denominadas checked exceptions. Você deve se preocupar com as exceções
denominadas checked, ou manipulando-as ou anunciando que elas podem ser propagadas.
Exceções unckecked estão fora do nosso controle ou resultam de condições que você não
deveria ter permitido. (Ex. a[-1] = 0).

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 8
_______________________________________________________________________________________
Resumindo, um método deve declarar todas as checked exceptions que ele gera. Se o seu
método não fizer isso, o compilador java emite uma mensagem de erro.

Observação: Se você faz o override de um método de uma superclasse na sua subclasse o


método da subclasse não pode gerar mais checked exceptions do que o método da
superclasse que você substituiu. (Pode gerar menos exceções, se você quiser).

Quando um método de uma classe declara que ele gera uma exceção que é uma instância
de uma classe particular, então ele pode gerar uma exceção daquela classe ou de qualquer
de suas subclasses. Por exemplo, o método readLine da classe BufferedReader declara
que gera exceções da classe IOException. Nós não sabemos que tipo de IOException.
Poderia ser um IOException puro ou um objeto de um dos seus vários filhos, como
EOFException.

Como Gerar uma Exceção

Vamos supor que você possui um método, readData, que está lendo um arquivo cujo
header informa que o tamanho do arquivo (content-length: 1024) é de 1024 bytes, mas
você recebe um EOF após ler 733 caracteres. Você decide que esta situação é tão anormal
que você decide gerar uma exceção.

Você necessita decidir que tipo de exceção gerar. Algum tipo de IOException seria uma
boa escolha. Olhando o arquivo tree.html na documentação das APIs Java, você descobre
um EOFException com a descrição: “Signals that a EOF has been reached unexpectedly
during input.” Perfeito. Aqui está como você gera esta exceção:

throw new EOFException();

ou, se você preferir,

EOFException e = new EOFException();


throw e;

E aqui está como tudo se encaixa:

String readData(BufferedReader in) throws EOFException


{ . . .
while ( . . . )
{ if (ch == -1) // EOF encontrado
{ if (n < len)
throw new EOFException();
}
. . .
}
return s;
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 9
_______________________________________________________________________________________
A exceção EOFException possui um segundo construtor que recebe um argumento string.
Você pode utilizar este string para descrever a exceção que ocorreu.

String mensagem = “Tamanho do Arquivo: “ + len + “ Recebido:“ + n;


throw new EOFException(mensagem);

Como você pode ver, é fácil gerar uma exceção se umas das classes de exceção lhe
atende. Neste caso:

1. Encontre a classe da exceção apropriada.


2. Crie um objeto desta classe.
3. Gere a exceção.

Quando um método gera uma exceção, ele não retorna para quem o chamou. Isto
significa que você não necessita atribuir um valor default de retorno ou copiar um código
de erro.

Criando uma Classe de Exceção

Pode ocorrer um problema com o seu código que não está adequadamente descrito por
nenhuma das classes de exceções definidas em Java. Neste caso você deve criar sua
própria classe de exceção. É comum se fornecer dois construtores: um default e um que
contenha uma mensagem que descreve detalhadamente o que aconteceu. O método
toString da classe base Throwable imprime a mensagem detalhada, que facilita muito o
processo de depuração.

class MinhaExcecao extends Exception


{ private String motivo;

public MinhaExcecao (String causa)


{ motivo = causa;
}
public MinhaExcecao ()
{ motivo = "";
}
public String getMotivo()
{ return motivo;
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 10
_______________________________________________________________________________________
Agora você pode gerar o seu próprio tipo de exceção.

import java.util.Calendar;
class Empregado
{ public Empregado (String n, double s, Calendar d)
throws MinhaExcecao
{ if (s > SALARIO_MAXIMO)
throw new MinhaExcecao ("Inserção Inválida. Salário de " + s
+ " > " + SALARIO_MAXIMO);
else
{ nome = n;
salario = s;
dataContratacao = d;
}
}

public void print()


{ System.out.println (nome + " " + salario + " " +
anoContratacao());
}

public void aumentaSalario (double percentualDeAumento)


throws MinhaExcecao
{ double sal = salario * (1 + percentualDeAumento / 100);
if (sal > SALARIO_MAXIMO)
throw new MinhaExcecao ("Aumento Inválido. Salário de " +
sal + " > " + SALARIO_MAXIMO);
else
salario = sal;
}

public void alteraDataContratacao (int ano, int mes, int dia)


{ dataContratacao.set(Calendar.YEAR, ano);
dataContratacao.set(Calendar.MONTH, mes);
dataContratacao.set(Calendar.DATE, dia);
}

public int anoContratacao()


{ return dataContratacao.get(Calendar.YEAR);
}

public String getNome()


{ return nome;
}

public double getSalario()


{ return salario;
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 11
_______________________________________________________________________________________
public void setSalario(double sal) throws MinhaExcecao
{ if (sal > SALARIO_MAXIMO)
throw new MinhaExcecao ("Modificação de salário Inválida. "
+ " Salário de " + sal + " > " + SALARIO_MAXIMO);
else
salario = sal;
}

private String nome;


private double salario;
private Calendar dataContratacao;
final double SALARIO_MAXIMO = 3000;

Capturando Exceções

Naturalmente algum código necessita capturar a exceção gerada por um método. Se uma
exceção é gerada e não é capturada em nenhum lugar (em uma aplicação não gráfica), o
programa termina e imprime uma mensagem uma mensagem na console fornecendo o
tipo da exceção e o stack trace. Em um programa gráfico (aplicações e applets), a mesma
mensagem de erro é impressa, mas o controle da execução volta para o loop de
processamento da interface gráfica do usuário. Quando você está debugando um
programa gráfico é uma boa idéia manter a console disponível na tela em vez de
minimizá-la.

Para capturar uma exceção escreve-se um bloco try/catch. A forma mais simples do bloco
try é:

try
{ código
código
}
catch (ExceptionType e)
{ código para manipular uma exceção deste tipo
}

Se uma instrução dentro do bloco try gera uma exceção da classe especificada na cláusula
catch, então:

1. O programa salta as demais instruções dentro do bloco try.


2. O programa executa o código dentro da cláusula catch.

Se nenhum código dentro do bloco try gerar uma exceção, então o programa salta a
cláusula catch.

Se um código do método gera uma exceção de um tipo diferente do tipo especificado na


cláusula catch, este método encerra sua execução imediatamente retornando para quem o
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 12
_______________________________________________________________________________________
chamou. (Seria bom que o método que o chamou ou algum antecessor tivesse uma
cláusula catch para aquele tipo de erro.

Para mostrar isto em funcionamento, aqui está um código da classe TestaEmpregado:

import java.util.*;
public class TestaEmpregado
{ public static void main (String[] args)
{ Empregado[] vetEmpregados = new Empregado[2];
try
{ System.out.println
("Tentando inserir Luciana Arruda - Sal: 2500");
vetEmpregados [0] =
new Empregado ("Luciana Arruda", 2500,
new GregorianCalendar (1993, 1, 12));
}
catch (MinhaExcecao e)
{ System.out.println (e.getRazao());
}

try
{ System.out.println
("Tentando inserir Lucio Arruda - Sal: 3500");
vetEmpregados [0] =
new Empregado ("Lucio Arruda", 3500,
new GregorianCalendar (1994, 11, 14));
}
catch (MinhaExcecao e)
{ System.out.println (e.getRazao());
}
try
{ System.out.println ("Tentando conceder aumento a " +
vetEmpregados[0].getNome() + " de 25%");
vetEmpregados[0].aumentaSalario(25);
}
catch (MinhaExcecao e)
{ System.out.println (e.getRazao());
}
}
}

Note que a parte do código está dentro da cláusula try. Observe que a própria classe que
gera um exceção pode tratá-la. No exemplo acima, deixamos o programa chamador tratar
a exceção. Por esta razão tivemos que anunciar no header do construtor da classe
Empregado e no header dos métodos aumentaSalario e setSalario que estes métodos
podiam gerar a exceção MinhaExcecao.

public void aumentaSalario (double percentualDeAumento)


throws MinhaExcecao

Lembre-se que o compilador verifica as exceções especificadas com throws. Se você


chama um método que gera uma exceção do tipo checked, você deve tratá-la ou passá-la
adiante.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 13
_______________________________________________________________________________________
Qual a melhor alternativa? A regra geral diz que você deve tratar as exceções que você
sabe como tratar e passar a diante as exceções que você não sabe como tratar. Há uma
exceção para esta regra: se você está escrevendo um método que faz um override em um
método da superclasse que não gera exceções então você deve capturar cada checked
exception no código do método. Seu código não pode adicionar novas exceções à lista de
exceções a um método de uma subclasse que está presente na superclasse.

Capturando Múltiplas Exceções

Você pode capturar múltiplos tipos de exceção em um bloco try e tratar cada tipo de
forma diferente. Você utiliza uma cláusula catch separada para cada tipo. Por exemplo:

try
{ código que pode gerar exceções
}
catch (MalformedURLException e1)
{ // Ação a ser tomada em relação a URL mal formadas.
}
catch (UnknownHostException e2)
{ // Ação a ser tomada em relação a Host desconhecido
}
catch (IOException e3)
{ // Ação a ser tomada em relação a problema de I/O
}

O objeto exceção (e1, e2, e3) pode conter informações sobre a natureza da exceção. Para
descobrir mais sobre o objeto utilize:

e3.getMessage()

para obter a mensagem de erro detalhada (se ela existir), e

e3.getClass().getName()

para obter o tipo real do objeto exceção.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 14
_______________________________________________________________________________________
Regerando Exceções

Ocasionalmente pode ser necessário capturar uma exceção, para efetuar algum
processamento, mas sem efetivamente resolver o problema. Quando isto ocorre, você
executa alguma ação de emergência em função da exceção que ocorreu e gera novamente
a mesma exceção chamando throw de forma que ela seja passado para o método
chamador.

Exemplo:

Graphics g = image.getGraphics();
try
{ Código que pode gerar exceções
}
catch(MalformedURLException e)
{ g.dispose();
throw e;
}

Embora tendo tomado alguma atitude com g.dispose(), a URL mal formada continua
existindo daí ser repassada ao método chamador que poderá, por exemplo, solicitar ao
usuário que a corrija.

Cláusula Finally

Quando seu código gera uma exceção, o processamento é encerrado e o código


remanescente do método não é executado. Isto é um problema se o método obteve alguns
recursos locais que só ele conhece. Pode ser preciso, então, no momento que uma
exceção é gerada, liberar este recurso. Uma solução seria capturar e regerar todas as
exceções, no entanto esta solução seria muito trabalhosa, uma vez que você teria que
liberar o tal recurso alocado em vários lugares, isto é, no tratamento de cada exceção.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 15
_______________________________________________________________________________________
Java possui uma solução melhor:

Graphics g = image.getGraphics();
try
{ Código que pode gerar exceções
}
catch(IOException e)
{ done = true;
}
finally
{ g.dispose();
}

Este código executa o código na clausula finally caso a exceção seja ou não capturada.
Isto é, em qualquer circunstância g.dispose será executado.

Vamos examinar as três possibilidades de execução da cláusula finally:

1. O código não gera exceção. O bloco try é executado e, em seguida, o bloco finally. A
seguir, é executada a primeira linha após o bloco finally.

2. O código gera uma exceção que é capturada pela cláusula catch. O código no bloco
try é executado até a linha que causou a exceção. O código remanescente no bloco try
é saltado. Então o programa executa o código na cláusula catch que captura a exceção
e, em seguida, o bloco finally. A seguir, é executada a primeira linha após o bloco
finally. Se a cláusula catch não gera uma exceção, então o programa executa a
primeira linha após o bloco try-catch-finally. Se na cláusula catch é gerada uma
exceção, o bloco finally é executado e esta exceção é retornada para o programa
chamador.

3. O código gera uma exceção que não é capturada por nenhuma cláusula catch. O
código no bloco try é executado até a linha que causou a exceção. O código
remanescente no bloco try é saltado. Então o programa executa o código na clausula
finally e a exceção é retornada para o programa chamador.

É possível utilizar a cláusula finally sem a clausula catch. Por exemplo:

Graphics g = image.getGraphics();
try
{ Código que pode gerar exceções
}
finally
{ g.dispose();
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 16
_______________________________________________________________________________________

Algumas Dicas na Utilização de Exceções

1. Tratamento de exceções não deve substituir um simples teste.

Tratar uma exceção geralmente demora muito mais do que efetuar um simples teste.

2. Não ponha cada instrução dentro de um bloco try.

3. Não cale as exceções.

Você escreve um método que chama um método que gera uma exceção uma vez a
cada 100 anos. O compilador reclama porque você não declarou a exceção na lista
throws do seu método. Você não deseja declará-lo na lista throws porque senão o
compilador vai criar caso com todos os métodos que chamam o seu método. Logo,
você simplesmente cala a exceção.

Image loadImage (String s)


{ try
{ Muito código
}
catch (Exception e)
{} // Não faz nada
}

4. Propagar uma exceção não é errado.

Muitos programadores se sentem obrigados a capturar todas as exceções que eles


conhecem.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 17
_______________________________________________________________________________________

Técnicas de Debugging

1. Inserir no código comandos como:

System.out.println (“x = “ + x);

Se x for um número, será convertido para string, e se for um objeto, seu método
toString será executado.

2. Obtenha o estado do objeto corrente com o comando:

System.out.println (“Objeto corrente = “ + this);

3. Utilize o código genérico para o método toString que imprime para cada campo o seu
valor.

4. Você pode obter uma stack trace de qualquer exceção através do método
printStackTrace da classe Throwable:

try
{ . . .
}
catch (Throwable t)
{ t.printStackTrace();
throw t;
}

5. Não é necessário capturar uma exceção para gerar um stack trace. Apenas insira o
comando abaixo em qualquer lugar do seu código:

new Throwable().printStackTrace();

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 18
_______________________________________________________________________________________
6. Se você projeta seus próprios componentes swing e eles não parecem estar
funcionando corretamente, utilize o debugger do swing. Para ligar o debugger para
um componente swing, utilize o método setDebugGraphicsOptions da classe
JComponent. As seguintes opções estão disponíveis:

Nós (autor do livro CoreJava) descobrimos que para a opção flash funcionar é preciso
desabilitar o “double buffering” , a estratégia utilizada pelo Swing para reduzir o
flicker quando atualiza uma janela. Para ligar a opção flash faça:

RepaintManager.currentManager(getRootPane())
.setDoubleBufferingEnabled(false);
((JComponent)getContentPane())
.setDebugGraphicsOptions(DebugGraphics.FLASH_OPTION);

Simplesmente ponha estas linhas no final do construtor do seu frame e você verá o
content pane sendo preenchido vagarosamente. Ou para um debug mais localizado,
coloque o setDebugGraphicsOptions para um componente específico.

7. Um truque aparentemente pouco conhecido é que você pode colocar um método main
em cada classe pública. Assim você poderá testar cada classe separadamente. Crie
alguns objetos, chame cada método e verifique se eles funcionam corretamente.

Verificando Condições de Erro

Você pode, ao longo da execução de seu programa verificar se determinadas condição


são verdadeiras e caso contrário você deve gerar uma exceção. Exemplo: Você poderia
gerar uma exceção se descobrir que o índice de um array está fora dos limites permitidos.

public void f(int[] a, int i)


{ if (!(a != null && i >= 0 && i < a.length))
throw new IllegalArgumentError (“Condição falhou”);
. . .
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 19
_______________________________________________________________________________________
Estas verificações são denominadas assertivas, e para fazer com que o código fique mais
fácil de ler podemos utilizar um método estático check.

public void f(int[] a, int i)


{ Assertion.check( a != null && i >= 0 && i < a.length);
. . .
}

public class Assertion


{ public static void check(boolean condition)
{ if (!condition)
throw new IllegalArgumentError (“Condição falhou”);
}
}

Quando o programa estiver testado teremos que remover este código. Uma maneira
simples de fazer isto é acrescentando uma variável boleana que determina se este teste
deve ou não ser executado:

public void f(int[] a, int i)


{ if (debug) // Uma variável static final
Assertion.check( a != null && i >= 0 && i < a.length);
. . .
}

Basta atribuir false a debug quando o sistema entrar em produção.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 20
_______________________________________________________________________________________

Exibindo Mensagens de Debug em um Programa Gráfico

Se você executa uma applet dentro de um browser, você pode não conseguir ver
mensagens enviada para System.out. Esperamos que, no futuro, a maioria dos browsers
possuam algum tipo de java console window. O Netscape, assim como o Internet
Explorer, possuem uma. Se você utiliza o Java Plug-In, marque, no painel de
configuração, o check box denominado Show Java Console.

Em alguns browsers a Java Console Window possui barras de rolamento de tal forma que
é possível ver mensagens que rolaram para fora da tela. No ambiente DOS estas
mensagens não podem ser vistas.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 21
_______________________________________________________________________________________

Utilizando o JDB Degugger

O JDK inclui um debugger de linha de comando extremamente rudimentar denominado


JDB.

Vamos examinar o código deliberadamente corrompido do programa ButtonTest abaixo:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class BuggyButtonTest extends JFrame implements ActionListener


{ public BuggyButtonTest()
{ pane = new JPanel();

JButton yellowButton = new JButton("Yellow");


pane.add(yellowButton);
yellowButton.addActionListener(this);

JButton blueButton = new JButton("Blue");


pane.add(blueButton);
blueButton.addActionListener(this);

JButton redButton = new JButton("Red");


pane.add(redButton);
redButton.addActionListener(this);

Container contentPane = getContentPane();


contentPane.add(pane);
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 22
_______________________________________________________________________________________
public void actionPerformed(ActionEvent evt)
{ String arg = evt.getActionCommand();
Color color = Color.black;
if (arg.equals("yellow")) color = Color.yellow;
else if (arg.equals("blue")) color = Color.blue;
else if (arg.equals("red")) color = Color.red;
pane.setBackground(color);
repaint();
}

public static void main(String[] args)


{ JFrame f = new BuggyButtonTest();
f.addWindowListener(new WindowAdapter()
{ public void windowClosing(WindowEvent e)
{ System.exit(0); }
} );
f.setSize(400, 400);
f.show();
}

private JPanel pane;


}

Para utilizar o JDB você deve compilar seu programa com a opção –g:

javac –g BuggyButtonTest.java

Quando você compila com esta opção, o compilador adiciona os nomes das variáveis
locais e outras informações de debug nos arquivos .class. Então, você executa o
debugger:

jdb BuggyButtonTest

Uma vez executando-se o debugger, você verá uma mensagem como a que vem abaixo:

Initializing jdb . . .
0x139f3c8:class(BuggyButtonTest)
>

O prompt > indica que o debugger está esperando por um comando. A tabela abaixo
mostra todos os comandos de debug que você pode fornecer. Itens entre colchetes são
opcionais, e o sufixo significam que você pode fornecer um ou mais argumentos
separados por espaços.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 23
_______________________________________________________________________________________

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 24
_______________________________________________________________________________________
Vamos examinar apenas os comandos JDB mais úteis. A idéia básica é simples: você
marca um ou mais breakpoints e então executa o programa. Quando o programa chega no
breakpoint, ele pára. Então você pode inspecionar os valores de variáveis locais.

Para estabelecer um breakpoint:

stop in class.method

Ou

stop at class:line

Por exemplo, vamos por um breakpoint no método actionPerformed de


BuggyButtonTest:

stop in BuggyButtonTest.actionPerformed

Agora desejamos que o programa seja executado:

run

O programa será executado, mas o breakpoint só será alcançado quando java começar a
executar o código do método actionPerformed. Para que isto aconteça clique no botão
Amarelo. O debugger pára no início do método actionPerformed. Você verá:

Breakpoint hit: BuggyButtonTest.actionPerformed


(BuggyButtonTest:32)

Como o debugger não nos fornece uma janela mostrando a linha corrente que vai ser
executada, utilize o comando list para ver qual é a linha corrente. Por exemplo:

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 25
_______________________________________________________________________________________
Digite locals para ver todas as variáveis locais. Por exemplo:

Para maiores detalhes digite:

dump variable

Por exemplo,

dump evt

mostrará todos os campos de instância da variável evt:

Existem dois comandos básicos para executar um programa passo a passo. O comando
step que faz com que a execução passe por todos os métodos do programa, passo a passo.
Infelizmente, fica muito confuso entre threads. Achamos que é mais seguro utilizar o
comando next, que executa a próxima linha sem entrar em nenhum método que seja
chamado. Digite next 3 vezes para ver onde você está:

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 26
_______________________________________________________________________________________
Isto não é o que deveria ter acontecido. A variável color deveria ter sido designada para
yellow para depois executar o comando setBackground.

Agora podemos entender o que aconteceu. O valor de arg era “Yellow”, com um y
maiúsculo, mas o teste compara arg com “yellow’:

if (arg.equals(“yellow”))

Para encerrar o debugger digite:

quit

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 27
_______________________________________________________________________________________
STREAMS E ARQUIVOS

Applets normalmente não podem trabalhar com arquivos no sistema do usuário. Mas
applications normalmente lidam com arquivos. Nesta seção veremos os métodos para
lidar com arquivos bem como os métodos para escrever e ler informações de arquivos.
Este capítulo também apresenta o mecanismo de serialização que nos permite armazenar
objetos tão facilmente quanto armazenamos texto ou dados numéricos.

Streams

Esta seção trata de como obter dados de qualquer fonte de dados que possa enviar uma
seqüência de bytes e como enviar dados para qualquer destino que possa receber uma
seqüência de bytes. Estas fontes e destinos de seqüência de bytes podem ser arquivos,
mas também podem ser conexões de rede e até blocos de memória. Informações
armazenadas em arquivos e informações recuperadas de conexões de rede são
manipuladas essencialmente da mesma forma. Enquanto os dados são armazenados como
uma série de bytes, é geralmente mais conveniente pensar neles como possuindo uma
estrutura de mais alto nível como sendo uma seqüência de caracteres ou objetos. Também
veremos as facilidades que Java provê para entrada e saída de dados em mais alto nível.

Em java, um objeto a partir do qual podemos ler uma seqüência de bytes é denominado
um input stream. E um objeto no qual podemos escrever uma seqüência de bytes é
denominado um output stream. Eles encontram-se implementados nas classes abstratas
InputStream e OutputStream.

Lendo e Escrevendo Bytes

A classe InputStream possui um método abstrato:

public abstract int read() throws IOException

Este método lê um byte e retorna o byte que foi lido ou –1 se ele encontra o final da fonte
de dados. O projetista de uma classe input stream concreta faz o override deste método
para prover funcionalidade util. Por exemplo, na classe FileInputStream, este método lê
um byte de um arquivo. System.in é um objeto predefinido de uma subclasse de
InputStream que nos permite ler informações do teclado.

A classe InputStream também possui métodos não abstratos para ler um array de bytes ou
para pular um certo número de bytes. Estes métodos chamam o método abstrato read,
para que as subclasses necessitem fazer o override de apenas um método.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 28
_______________________________________________________________________________________
Da mesma forma, a classe OutputStream define o método abstrato:

public abstract void write (int b) throws IOException

que escreve um byte em um local de saída.

Ambos os métodos read e write podem bloquear uma thread até que o byte seja realmente
lido ou escrito. Isto significa que se o byte não pode ser imediatamente lido ou escrito
(geralmente em função de uma conexão de rede ocupada), Java suspende a thread que
contém esta chamada. Isto dá a oportunidade a outras threads de fazer algo útil enquanto
o método está esperando pelo stream para novamente se tornar disponível.

O método available permite que você verifique a quantidade de bytes que está
disponível para leitura.

Para ler um array de bytes:

int bytesDisponiveis = System.in.available();


if (bytesDisponiveis > 0)
{ byte [] data = new byte[bytesDisponiveis];
System.in.read(data);
}

Quando você termina de ler ou de escrever em um stream, feche-o, utilizando o método


close, porque streams utilizam recursos limitados do sistema operacional. Se uma
aplicação abre muitos streams e não os fecha, os recursos do sistema pode se exaurir.
Fechar um output stream também envia o buffer utilizado pelo output stream: quaisquer
caracteres que foram temporariamente colocados para que eles pudessem ser entregues
como um pacote maior são imediatamente enviados. Em particular, se você não fecha um
arquivo, o último pacote de bytes pode nunca ser entregue. Você também pode
manualmente remeter a saída com o método flush. Programadores Java raramente
utilizam estes métodos porque programas Java raramente necessitam ler e escrever
streams de bytes. Os dados nos quais você está interessado provavelmente contém
números, strings e objetos.

Java nos fornece muitas classes stream derivadas das classes básicas InputStream e
OutputStream que nos permite trabalhar com dados da forma como geralmente utilizamos
em vez de trabalhar a nível de byte.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 29
_______________________________________________________________________________________

Tipos de Streams

Java possui mais de 60 tipos de streams. Segundo os projetistas da linguagem o fato de


existirem muitos tipos de streams faz com que o compilador seja capaz de detectar erros
que só poderiam ser detectados em tempo de execução (como em C++).

Em Java, por exemplo, não há como escrever em um stream aberto apenas para leitura
uma vez que InputStream não possui métodos para saída de dados.

Existem 4 classes abstratas que formam a base dos Streams: InputStream, OutputStream,
Reader e Writer. Você não cria objetos destes tipos mas outros métodos podem retorná-
los. Por exemplo, a classe URL possui o método OpenStream que retorna um
InputStream. Você então utiliza este objeto InputStream para ler do URL. Como
mencionamos anteriormente, as classes InputStream e OutputStream nos permite ler e
escrever apenas bytes individuais e arrays de bytes. Elas não possuem métodos para ler e
escrever strings e números. Você necessita de subclasses mais capazes para este fim. Por
exemplo, as classes DataInputStream e DatOutputStream nos permitem ler e escrever
todos os tipos de dados básicos da linguagem.

Para código Unicode (dois bytes para representar um) você necessita utilizar as classes
que descendem de Reader e Writer. Os métodos básicos das classes Reader e Writer são
similares aos das classes InputStream e OutputStream:

public abstract int read() throws IOException


public abstract void write (int b) throws IOException

Eles funcionam como os métodos das classes InputStream e OutputStream exceto que o
método read retorna um caracter Unicode (um número inteiro entre 0 e 65535) ou –1
quando você encontra o final do arquivo.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 30
_______________________________________________________________________________________

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 31
_______________________________________________________________________________________
E existem streams que são úteis, como por exemplo as classes ZipInputStream e
ZipOutputStream que nos permitem ler e escrever arquivos no formato de compressão
ZIP.

Layering Streams Filters

As classes FileInputStream e FileOutputStream nos fornece Streams de entrada e de saída


ligados a um arquivo em disco. Você deve fornecer o nome do arquivo ou o caminho
completo do arquivo no construtor:

Por exemplo, o comando:

FileInputStream fin = new FileInputStream (“empregados.dat”);

Procura no diretório corrente por um arquivo denominado “empregados.dat”.

Cuidado: Como “\” é um caracter especial em Strings Java, utilize \\ para caminhos de
arquivos estilo windows. Por exemplo: C:\\WINDOWS\\WIN.INI.

Você também pode utilizar um objeto File:

File f = new File(“empregados.dat”);


FileInputStream fin = new FileInputStream(f);

Como as classes abstratas InputStream e OutputStream, estas classes apenas suportam a


leitura e escrita a nível de byte. Isto é, podemos apenas ler bytes e array de bytes com o
objeto fin.

Observação: Como todas as classes java.io interpretam caminhos de arquivos relativos


começando no diretório de trabalho corrente do usuário, para se saber que diretório é este
utilize o método System.getProperty(“user.dir”). O diretório corrente do usuário é o
diretório onde se encontra o arquivo .class que está sendo executado.

Como você verá mais adiante, se nós apenas tivéssemos DataInputStream, então
poderíamos ler tipos numéricos:

DataInputStream din = . . .;
double s = din.readDouble();

Mas assim como FileInputStream não possui métodos para ler tipos numéricos,
DataInputStream não possui nenhum método para obter dados de um arquivo.

Java utiliza um mecanismo inteligente para separar dois tipos de responsabilidades.


Alguns streams (tais como o FileInputStream e o input stream retornado pelo método
openStream da classe URL) podem recuperar bytes de arquivos e de outros locais mais
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 32
_______________________________________________________________________________________
exóticos. Outros streams (tais como DataInputStream e PrinterWriter) podem juntar bytes
em tipos de dados mais úteis. O programador Java tem de combinar os dois no que são
geralmente denominados filtered streams fornecendo um stream existente ao construtor
de outro stream. Por exemplo, para se pode ler números de um arquivo, primeiramente
crie um FileInputStream e então passe ele para o construtor de DataInputStream.

FileInputStream fin = new FileInputStream(“empregados.dat”);


DataInputStream din = new DataInputStream(fin);
Double s = din.readDouble();

É importante entender que o data input stream que criamos com o código acima não
corresponde a um novo arquivo em disco. O mais novo stream criado ainda acessa os
dados do arquivo atachado ao file input stream, mas a questão é que agora este novo
stream possui uma interface mais poderosa.

Se você olhar a figura da pág 31 você verá as classes FilterInputStream e


FilterOutputStream. Para construir os streams que você deseja você deve combinar as
classes filho em um novo “filtered stream”. Por exemplo, por default, streams não são
movidos para um buffer. Isto é, toda chamada a read entra em contato com o sistema
operacional para pedir uma nova informação.

Se você desejar agilizar a busca a dados em um arquivo denominado empregados.dat no


diretório corrente, você necessita utilizar a seguinte seqüência de construtores:

DataInputStream din = new DataInputStream


(new BufferedInputStream
(new FileInputStream (“empregados.dat”)));

Note que colocamos o DataInputStream por último na cadeia de construtores porque


queremos utilizar os métodos de DataInputStream, e queremos que eles utilizem o
método buffered read. Embora este código seja feio você deve estar preparado para
utilizar construtores de streams em camadas até que você tenha acesso à funcionalidade
que você deseja.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 33
_______________________________________________________________________________________

Object Streams

Quando desejamos salvar em um arquivo informações sobre objetos, devemos


primeiramente salvar o tipo de cada objeto e então os dados que definem o estado
corrente do objeto. Quando lemos esta informação (previamente armazenada em um
arquivo) devemos:

• Ler o tipo do objeto.


• Criar um objeto vazio daquele tipo.
• Preenchê-lo com os dados que armazenamos no arquivo.

É inteiramente possível, embora tedioso fazer isto manualmente, no entanto a Sun


Microsystems desenvolveu um poderoso mecanismo que permite que isto seja feito com
muito menos esforço. Este mecanismo, denominado serialização, quase que
completamente automatiza este processo.

Armazenando Objetos de Tipos Variados

Para salvar dados de objetos, você primeiramente necessita abrir um objeto do tipo
ObjectOutputStream:

ObjectOutputStream out =
new ObjectOutputStream(new FileOutputStream(“empregados.dat”);

Agora, para salvar um objeto, você simplesmente utiliza o método writeObject da classe
ObjectOutputStream como no fragmento de código abaixo:

Empregado luis = new Empregado ("Luis Claudio", 3500,


new GregorianCalendar (1989, 10, 1));
Gerente vini = new Gerente ("Vinicius Aguiar", 4500,
new GregorianCalendar (1992, 11, 6));
out.writeObject(luis);
out.writeObject(vini);

Para ler os objetos de volta, primeiramente obtenha o objeto ObjectInputStream:

ObjectInputStream in =
new ObjectInputStream(new FileInputStream(“empregados.dat”));

E então, recupere os objetos na mesma ordem em que foram escritos, utilizando o método
readObject.

Empregado e1 = (Empregado)in.readObject();
Empregado e2 = (Empregado)in.readObject();

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 34
_______________________________________________________________________________________
Quando você está recuperando objetos que foram escritos em disco, você deve ter
cuidado com o número de objetos que foram salvos, sua ordem e seus tipos. Cada
chamada a readObject lê um objeto do tipo Object, logo você necessita efetuar o cast para
o tipo correto. Se você não necessita do tipo exato ou você não se lembra dele, então você
pode efetuar o cast para qualquer superclasse, ou pode até mesmo deixá-lo como sendo
do tipo Object. Por exemplo, e2 é uma variável objeto do tipo Empregado embora ela
possa se referir na realidade a um objeto do tipo Gerente. Se você necessita recuperar
dinamicamente o tipo do objeto, você pode utilizar o método getClass, descrito no
capítulo sobre Herança na apostila de Introdução.

Você pode escrever e ler apenas objetos com os métodos writeObject e readObject, isto é,
você não pode ler e escrever números com estes métodos. Para escrever e ler números
você utiliza métodos tais como writeInt/readInt ou writeDouble/readDouble. As classes
Object Stream implementam as interfaces DataInput e DataOutput. Naturalmente
números dentro de objetos (como o campo salário do objeto empregado) são
armazenados e recuperados automaticamente. Lembre-se que, em Java, Strings e arrays
são objetos e podem, conseqüentemente ser restaurados com os métodos
writeObject/readObject.

Existe, no entanto, uma modificação que você necessita fazer com qualquer classe que
você queira salvar e restaurar em um object stream. A classe deve implementar a
interface Serializable:

class Empregado implements Serializable { . . . }

A interface Serializable não possui métodos, logo você não necessita modificar as suas
classes. Neste aspecto, é similar à interface Clonable. No entanto, para fazer uma classe
cloneable, você ainda tinha que efetuar o override do método clone da classe Object. Para
fazer com que uma classe seja Serializable, você não necessita fazer nada mais. As
classes não são serializable por default por questões de segurança.

O programa abaixo escreve em disco um array contendo dois empregados e um gerente e


depois os recupera. Escrever o array é feito com uma única operação:

Empregado [] vetEmpregados = new Empregado[3];


. . .
out.writeObject(vetEmpregados);

De forma similar, ler o resultado também é feito com uma única operação. No entanto,
devemos aplicar um cast ao valor de retorno do método readObject:

Empregado [] novoVetEmpregados = (Empregado[])in.readObject();

Uma vez a informação armazenada , damos a cada empregado um aumento de 100%, não
porque estamos sendo generosos mas porque podemos facilmente distinguir objetos
empregados de objetos gerentes através de seus métodos aumentarSalario. Isto lhe
deveria convencer de que nós restauramos os tipos corretos.
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 35
_______________________________________________________________________________________
import java.io.*;
import java.util.*;
public class ObjectFileTest
{ public static void main (String[] args)
{ try
{ Empregado[] vetEmpregados = new Empregado[3];
vetEmpregados [0] = new Empregado ("Luis Claudio", 3500,
new GregorianCalendar (1989, 10, 1));
vetEmpregados [1] = new Gerente ("Vinicius Aguiar", 4500,
new GregorianCalendar (1992, 12, 6));
vetEmpregados [2] = new Empregado ("Luciana Arruda", 2500,
new GregorianCalendar (1993, 1, 12));

// O arquivo empregados.dat será escrito no diretório onde


// se encontra o arquivo ObjectFileTest.class.
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("empregados.dat"));
out.writeObject(vetEmpregados);
out.close();

ObjectInputStream in = new
ObjectInputStream(new FileInputStream("empregados.dat"));
Empregado[] novoVetEmpregados = (Empregado[])in.readObject();

int i;
for (i = 0; i < novoVetEmpregados.length; i++)
novoVetEmpregados[i].aumentaSalario(100);
for (i = 0; i < novoVetEmpregados.length; i++)
novoVetEmpregados[i].print();
}
catch(Exception e)
{ System.out.print("Erro: " + e);
System.exit(1);
}
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 36
_______________________________________________________________________________________
class Empregado implements Serializable
{ public Empregado(String n, double s, GregorianCalendar d)
{ nome = n;
salario = s;
dataContratacao = d;
}

public Empregado() {}

public void print()


{ System.out.println(nome + " " + salario
+ " " + anoContratacao());
}

public void aumentaSalario(double percentual)


{ salario *= 1 + percentual / 100;
}

public int anoContratacao()


{ return dataContratacao.getYear();
}

private String nome;


private double salario;
private GregorianCalendar dataContratacao;
}

class Gerente extends Empregado


{ public Gerente(String n, double s, GregorianCalendar d)
{ super(n, s, d);
nomeDaSecretaria = "";
}

public Gerente() {}

public void aumentaSalario(double percentual)


{ // adiciona 0.5% de bonus para cada ano de serviço
GregorianCalendar hoje = new GregorianCalendar();
double bonus = 0.5 * (hoje.getYear() - anoContratacao());
super.aumentaSalario(percentual + bonus);
}

public void setNomeDaSecretaria(String n)


{ nomeDaSecretaria = n;
}

public String getNomeDaSecretaria()


{ return nomeDaSecretaria;
}

private String nomeDaSecretaria;


}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 37
_______________________________________________________________________________________
Observação: O arquivo empregados.dat é salvo em um formato particular. Você pode
utilizar os métodos writeObject e readObject sem ter que conhecer qual é a seqüência de
bytes que representa os objetos em um arquivo.

java.io.ObjectOutputStream

java.io.ObjectInputStream

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 38
_______________________________________________________________________________________
O Problema de Salvar Referências a Objetos

Agora sabemos como salvar objetos que contém números, strings ou outros objetos
simples (como o objeto GregorianCalendar na classe Empregado). No entanto existe uma
importante situação que necessitamos considerar. O que acontece quando um objeto é
compartilhado por vários objetos como parte do seu estado?

Para ilustrar o problema, vamos fazer uma pequena modificação na classe Gerente. Em
vez de armazenar o nome da secretária, vamos salvar uma referência para o objeto
secretária, que é um objeto do tipo Empregado.

class Gerente extends Empregado


{ public Gerente(String n, double s, GregorianCalendar d, Empregado e)
{ super(n, s, d);
secretaria = e;
}

public Gerente() {}

public void aumentaSalario(double percentual)


{ // adiciona 0.5% de bonus para cada ano de serviço
GregorianCalendar hoje = new GregorianCalendar();
double bonus = 0.5 * (hoje.getYear() - anoContratacao());
super.aumentaSalario(percentual + bonus);
}

public String getNomeDaSecretaria()


{ return secretaria.getNome();
}

private Empregado seretaria; // A única modificação


}

Esta é uma abordagem melhor para projetar uma classe Gerente real do que simplesmente
utilizar o nome da secretária – o objeto Empregado referente à secretária pode agora ser
localizado sem a necessidade de fazer uma busca no array vetEmpregados.

Uma vez feito isto, você deve ter em mente que o objeto Gerente agora contém uma
referência para o objeto Empregado, que descreve a secretária, não uma cópia separada
do objeto.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 39
_______________________________________________________________________________________
Em particular, dois gerentes podem compartilhar a mesma secretária, como ocorre na
figura abaixo e no seguinte código:

luiza = new Empregado ("Luiza Thomé", 3500,


new GregorianCalendar (1989, 10, 1));
Gerente vini = new Gerente ("Vinicius Aguiar", 4500,
new GregorianCalendar (1992, 12, 6));
vini.setSecretaria(luiza);
Gerente carlos = new Gerente ("Carlos Ribeiro", 9500,
new GregorianCalendar (1990, 17, 8));
carlos.setSecretaria(luiza);

Agora suponha que o vetor vetEmpregados seja escrito em disco. O que nós não
queremos é que para cada objeto Gerente seja utilizada a seguinte lógica para armazenar
esta informação:

• Salvar dado de empregado


• Salvar dado da secretária

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 40
_______________________________________________________________________________________
Porque com esta lógica os dados de luiza seriam salvos três vezes. Quando recarregados
os objetos teriam a configuração da figura abaixo:

E isto não é o que queremos. Suponha que a secretária receba um aumento. Teríamos que
procurar por todas as outras cópias deste objeto para conceder o mesmo aumento. Na
verdade desejamos salvar e recuperar apenas uma cópia deste objeto secretária. Para fazer
isto devemos copiar e restaurar as referências originais para os objetos. Em outras
palavras, desejamos que o layout dos objetos no disco seja exatamente como o layout dos
objetos na memória. No linguajar da orientação a objetos isto se chama persistência.

Naturalmente não podemos salvar e restaurar os endereços de memória para os objetos


secretária. Quando um objeto é recarregado, ele provavelmente ocupará um endereço de
memória completamente diferente do endereço ocupado pelo objeto original.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 41
_______________________________________________________________________________________
O que java faz nestes casos é utilizar uma abordagem baseada na serialização. Daí o
nome “object serialization” para este mecanismo. Aqui está o algoritmo:

1. Todos os objetos salvos em disco recebem um número de série (1, 2, 3, ...)

2. Quando um objeto vai ser salvo em disco é verificado se este objeto já foi salvo
anteriormente.

3. Se ele já tiver sido salvo em seu lugar é escrito apenas o seu número de série, caso
contrário, todos os seus dados são salvos.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 42
_______________________________________________________________________________________
Ao ler de volta os objetos, este procedimento é revertido. Para cada objeto que é
carregado na memória é armazenado seu número de seqüência e a posição na memória
onde ele foi colocado. Quando um objeto com um número de seqüência já carregado na
memória é procurado, a referência ao endereço de memória para este objeto é
estabelecida.

Observe que os objetos não necessitam ser salvos em nenhuma ordem particular. A figura
acima mostra o que ocorre quando um gerente ocorre primeiro no array vetEmpregados.
Todo este processo é automático.

Um outro tipo de aplicação muito importante que utiliza serialização é a transmissão de


uma coleção de objetos através de uma conexão de rede para um outro computador.
Assim como endereços absolutos de memória não tem significado em um arquivo, eles
também não têm significado quando estamos efetuando a comunicação com um
processador diferente. Como a serialização substitui os endereços de memória por
números seriais, isto permite o transporte de uma coleção de objetos de uma máquina
para outra. Nós veremos este uso de serialização quando estudarmos a chamada a
métodos remotos (Servlets).

O programa abaixo salva em disco e recupera para a memória um vetor de objetos


contendo Empregados e Gerentes. Observe que alguns destes empregados compartilham
a mesma secretária.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 43
_______________________________________________________________________________________
import java.io.*;

public class ObjectFileTest


{ public static void main (String[] args)
{ try
{ Empregado[] vetEmpregados = new Empregado[3];

Empregado luiza = new Empregado ("Luiza Thomé", 3500,


new GregorianCalendar (1989, 10, 1));
vetEmpregados [0] = luiza;
vetEmpregados [1] = new Gerente ("Vinicius Aguiar", 4500,
new GregorianCalendar (1992, 12, 6), luiza);
vetEmpregados [2] = new Gerente ("Luciana Arruda", 2500,
new GregorianCalendar (1993, 1, 12), luiza);

// O arquivo empregados.dat será escrito no diretório onde


// se encontra o arquivo ObjectFileTest.class.
ObjectOutputStream out = new ObjectOutputStream(new
FileOutputStream("empregados.dat"));
out.writeObject(vetEmpregados);
out.close();

ObjectInputStream in = new
ObjectInputStream(new FileInputStream("empregados.dat"));
Empregado[] novoVetEmpregados = (Empregado[])in.readObject();

int i;
for (i = 0; i < novoVetEmpregados.length; i++)
novoVetEmpregados[i].aumentaSalario(100);
for (i = 0; i < novoVetEmpregados.length; i++)
novoVetEmpregados[i].print();
}
catch(Exception e)
{ e.printStackTrace();
System.exit(1);
}
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 44
_______________________________________________________________________________________
class Empregado implements Serializable
{ public Empregado(String n, double s, GregorianCalendar d)
{ nome = n;
salario = s;
dataContratacao = d;
}

public Empregado() {}

public void print()


{ System.out.println(nome + " " + salario
+ " " + anoContratacao());
}

public void aumentaSalario(double percentual)


{ salario *= 1 + percentual / 100;
}

public int anoContratacao()


{ return dataContratacao.getYear();
}

private String nome;


private double salario;
private GregorianCalendar dataContratacao;
}

class Gerente extends Empregado


{ public Gerente(String n, double s, GregorianCalendar d, Empregado e)
{ super(n, s, d);
nomeDaSecretaria = e;
}

public Gerente() {}

public void aumentaSalario(double percentual)


{ // adiciona 0.5% de bonus para cada ano de serviço
GregorianCalendar hoje = new GregorianCalendar();
double bonus = 0.5 * (hoje.getYear() - anoContratacao());
super.aumentaSalario(percentual + bonus);
}

public void print()


{ super.print();
System.out.print(“Secretária: “);
if (secretaria != null) secretaria.print();
}

private Empregado secretaria;


}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 45
_______________________________________________________________________________________
ACESSANDO BANCOS DE DADOS

A maioria das principais aplicações de uma empresa envolvem acesso a dados em tabelas
– na sua maioria, bancos de dados relacionais. Com o objetivo de suportar esses tipos de
aplicações, Java conta com a API JDBC. Esta API, que surgiu como um dos avanços da
versão 1.1 do SDK (JDK) da SUN.

JDBC é uma API Java para execução de expressões SQL. Sendo mais específico, JDBC é
um conjunto de classes e interfaces que permitem acesso a bases de dados relacionais de
maneira uniforme, numa solução similar ao ODBC. Existem atualmente mais de 20
implementações de drivers de bancos de dados compatíveis com JDBC, mas basicamente
podemos distinguir quatro categorias de soluções, que são definidas pela JavaSoft:

Tipo 1 – Ponte JDBC / ODBC

A ponte JDBC / ODBC foi desenvolvida em conjunto pela JavaSoft e a Intersolv, de


modo a aproveitar a grande quantidade de drivers ODBC que já existem instalados nas
máquinas. Esta arquitetura funciona como explicado abaixo.

O cliente (escrito em Java) utiliza a API JDBC. O driver JDBC converte as chamadas
JDBC em chamadas ODBC e passa estas chamadas para o driver ODBC, que por sua vez
acessa a API nativa do banco, que finalmente acessa os dados. A principal vantagem
desta solução é que uma aplicação pode facilmente se conectar a uma fonte de dados:
basta utilizar o driver ODBC correto. Em compensação, este tipo de conexão envolve
uma complexidade adicional e uma sobrecarga considerável na máquina, levando a
problemas de desempenho. Isto sem falar que os drivers ODBC devem estar instalados na
máquina cliente, causando os velhos problemas de administração de versões e
configuração. Por tudo isso a ponte JDBC / ODBC tente a ser uma solução passageira,
apenas para resolver questões imediatas.

Tipo 2 – Driver Java Parcial + API Nativa

Nesta solução o driver ODBC é eliminado e o driver JDBC converte as chamadas JDBC
em chamadas diretas à API nativa do banco. Essa API traduz para uma linguagem
específica do banco de dados as funções JDBC.

Devemos atentar para o fato de que geralmente o driver JDBC é, na verdade, construído
parte em Java e parte em C / C++. O driver deve possuir essa camada de C para que seja
possível fazer as chamadas às APIs nativas (que são escritas em C).

Esse tipo de arquitetura, assim como a ponte JDBC / ODBC, requer que algum código
seja instalado na máquina cliente. Entretanto, esta solução é mais rápida por não precisar
da camada ODBC.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 46
_______________________________________________________________________________________
Tipo 3 – Driver 100% Java + Protocolo de Rede (arquitetura em três camadas)

Nesta configuração o driver JDBC converte as chamadas JDBC em um protocolo de rede


independente (independente do banco de dados). Estas chamadas são convertidas, então,
em chamadas à API nativa do banco por um servidor intermediário (middleware). Essa
arquitetura na verdade consiste de três camadas.

Como podemos observar, o driver JDBC é escrito totalmente em Java, e sua única função
é implementar a lógica necessária para passar os comandos SQL para o servidor
intermediário de forma conveniente. Por isso esse tipo de driver é bem pequeno
(aproximadamente 200k).

Quanto ao servidor intermediário, pode ter sido escrito em java ou em qualquer outra
linguagem apropriada (C / C++). Se for feito em Java, pode utilizar qualquer driver
compatível com JDBC para chegar ao banco de dados. Se for feito em outra linguagem,
serão utilizadas as bibliotecas nativas do banco, como nos drivers do tipo 2.

Uma grande vantagem desta solução é ter no cliente uma solução 100% Java, sem a
necessidade de instalação de nenhum produto. Uma desvantagem é que o middleware é
geralmente proprietário, junto com o protocolo utilizado para trafegar na rede. Cada
fabricante usa o seu próprio middleware. Isto praticamente inviabiliza o uso de outras
tecnologias mais abertas e promissoras, como por exemplo, CORBA.

Tipo 4 – Driver 100% Java:

Nesta solução, o driver converte as chamadas JDBC diretamente para o protocolo de rede
utilizado pelo banco de dados. Escritos somente em Java, estes drivers não necessitam de
ODBC nem de API nativa, gerando um enorme ganho de desempenho e permitindo o
desenvolvimento de aplicações 100% Java.

Hoje em dia (junho de 1999), a oferta de drivers do tipo 4 ainda é pequena, mas a
tendência é que aumente bastante em breve. Os drivers do tipo 3 também têm seu lugar,
enquanto os drivers do tipo 1 e 2 tendem a morrer.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 47
_______________________________________________________________________________________

Arquitetura de Três Camadas

A arquitetura de uma aplicação determina como os seus componentes serão distribuídos


através do sistema. A maioria das aplicações é feita de três tipos fundamentais de
componentes.

Um componente de apresentação contém a lógica que apresenta a informação a uma fonte


externa e obtém entradas daquela fonte. Na maioria dos casos a fonte externa é um
usuário final trabalhando num terminal ou estação de trabalho, embora a fonte externa
possa ser um sistema robótico, um telefone ou algum outro dispositivo de entrada de
dados.

Um componente de negócio contém a lógica da aplicação que governa as funções do


negócio e os processos executados pela aplicação. Essas funções e processos são
executadas ou por um componente de apresentação, quando um usuário executa uma
opção, ou por uma outra função de negócio.

Um componente de acesso a dados contém a lógica que fornece à interface um sistema de


armazenamento de dados ou algum outro tipo de fonte de dados externos, como uma
aplicação externa.

Aplicações de uma Camada

A arquitetura one-tier é baseada em um ambiente onde todos os componentes são


combinados num simples programa integrado, rodando somente em uma máquina. Essa
arquitetura, totalmente centralizada, corresponde ao tradicional ambiente mainframe.

A alternativa one-tier oferece uma quantidade significativa de vantagens. Sendo a


aplicação centralizada em um único ambiente, é fácil gerenciar, controlar e garantir a
segurança de acesso a essas aplicações. Esses sistemas são seguros, confiáveis e suportam
vários usuários.

Por outro lado, existe uma grande quantidade de desvantagens associadas a essa
arquitetura. A primeira delas é a escalabilidade. Se a máquina corrente ficar
sobrecarregada, a única saída é fazer um upgrade para uma máquina de maior capacidade.
Aplicações one-tier são também extremamente dependentes do hardware e do ambiente
operacional. Essencialmente as companhias ficam presas a um fornecedor específico da
sua plataforma de hardware. Como resultado, não se pode tirar proveito de novas
tecnologias, até que elas sejam disponibilizadas por aquele fornecedor específico.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 48
_______________________________________________________________________________________
Aplicações de duas Camadas

Com o advento dos computadores pessoais, redes locais, bancos de dados relacionais,
poderosas aplicações e ferramentas desktop, a indústria de computação direcionou-se
para o mundo dos sistemas "abertos" e cliente/servidor. Tomadores de decisão podem
gerar seus próprios relatórios e manipular dados com ferramentas desktop poderosas, em
suas próprias estações de trabalho. Esse tipo de arquitetura permite o tratamento de
funções, anteriormente impossíveis de serem manipuladas.

A arquitetura cliente/servidor em duas camadas divide o processamento entre uma


estação desktop e uma máquina servidora. O ambiente cliente/servidor mais popular usa
um PC (Windows-based) com uma poderosa ferramenta de desenvolvimento GUI
(Graphical User Interface) e um servidor de banco de dados UNIX ou Windows NT.

Algumas vantagens surgem dessa arquitetura. As ferramentas GUI possibilitam um


desenvolvimento e distribuição de aplicações muito mais rápidos. Em função da
distribuição do processamento entre o cliente e o servidor a máquina servidora resulta em
custos mais baixos do que os sistemas mainframe. Os sistemas de bancos de dados, por
independerem de plataforma, permitem portabilidade mais fácil entre sistemas,
efetivamente quebrando a dependência no fornecimento do hardware. Considerando a
facilidade de uso das ferramentas GUI, o nível de qualificação dos desenvolvedores não
precisa ser alto.

Em contrapartida, surgem algumas desvantagens: perda de segurança, confiança e


controle é uma delas. Esse modelo é extremamente eficaz para aplicações de médio porte,
acessando poucos bancos de dados e que não suportam uma grande quantidade de
usuários. Sem as facilidades de controle e segurança, disponíveis nos sistemas
centralizados, cada aplicação cliente deve cuidar do seu próprio processo de segurança.
Com isso, muitas companhias ainda relutam em mover suas aplicações de missão crítica
para PCs.

Aplicações Multicamadas

Felizmente, existe uma forma de se obter o melhor das arquiteturas anteriores, sem as
suas desvantagens. Além de suportar os benefícios de ambas as arquiteturas (one e two-
tier), a arquitetura multi-tier também suporta os benefícios de uma arquitetura bastante
flexível.

As três camadas referem-se as três partes lógicas que compõem uma aplicação, e não ao
número de máquinas usadas pela aplicação. O modelo multi-tier divide a aplicação nas
seguintes camadas: lógica da apresentação, 1ógica do negócio e lógica de acesso aos
dados, podendo existir qualquer número de qualquer das camadas numa aplicação. Os
componentes da aplicação comunicam-se entre si utilizando uma interface abstrata, que
funciona como um contrato, em termos do que esta sendo tornado público. Essa mesma
camada abstrata esconde todos os detalhes da implementação das funções desempenhadas
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 49
_______________________________________________________________________________________
por um componente. Ela identifica a operação a ser realizada e define os parâmetros de
entrada e saída necessários a execução da operação. Esse tipo de infra-estrutura
possibilita serviços de localização, segurança e comunicação entre os componentes da
aplicação. São várias as vantagens advindas desta nova “onda” da informática. Algumas
delas são descritas nos parágrafos seguintes.

Reutilização de objetos por outras aplicações - Objetos podem ser compartilhados por
diversas aplicações. De fato, o que está sendo construído não é propriamente uma
aplicação, mas uma coleção de módulos (objetos) clientes e servidores que se comunicam
através de uma interface padronizada e abstrata e que, ao serem combinados, funcionam
como um sistema integrado de aplicações. Cada módulo é, na realidade, um objeto que
pode ser reutilizado e compartilhado por diversos sistemas. Essa versatilidade plug-and-
play é útil quando o setor de tecnologia da informação necessita suportar partes dife-
rentes, mas relacionadas, do negócio.

Facilidade de manutenção do sistema -- A vantagem mais óbvia dessa arquitetura é a


facilidade de manutenção. Desde que as funções da aplicação são isoladas em objetos
granulares, a 1ógica da aplicação pode ser modificada muito mais facilmente do que
antes - por exemplo, uma função que é realizada por uma aplicação financeira diz
respeito a cálculo de taxas e impostos. O algoritmo para esse tipo de cálculo muda
periodicamente, por força legal do cálculo de taxas. Se isolarmos essas regras de negócio
em objetos autônomos, os algoritmos podem ser trocados, sem afetar o resto da aplicação.

A aplicação passa a independer do fornecedor de banco de dados - Uma grande


vantagem da arquitetura multi-tier reside no fato de que a lógica da aplicação não é mais
vinculada diretamente a estruturas de banco de dados ou a um DBMS particular. Objetos
individuais da aplicação trabalham com as suas próprias estruturas de dados, que podem
corresponder a uma estrutura do banco ou podem ser estruturas derivadas de um diferente
número de fontes de dados. Quando os objetos na aplicação se comunicam, eles somente
necessitam passar os parâmetros, como especificado na interface abstrata, em vez de
passar os registros de um banco de dados, reduzindo assim o tráfego de rede. Os objetos
de acesso a dados são os únicos componentes da aplicação que fazem uma interface
diretamente com o banco de dados. Dessa forma um banco de dados pode ser migrado de
um fornecedor para outro, sem afetar a aplicação como um todo. Somente a camada da
lógica de acesso aos dados precisará ser alterada. Isso representa uma autonomia para se
reagir melhor a uma mudança tecnológica ou de negócio. Um outro fato importante é
que, na camada da lógica de apresentação, ou seja, o lado cliente da aplicação, não há a
menor necessidade de ter configurações para acesso a dados, como existe hoje nas
aplicações cliente/servidor com a instalação dos chamados database clients. Isto é o que
se chama de aplicações thin client.

A abstração da lógica de acesso a dados leva a um outro significativo beneficio: o


conceito de que os dados podem ser estendidos para incluir arquivos seqüenciais,
indexados, bancos não relacionais e mesmo legacy application systems. Um conjunto de
módulos de acesso a dados pode ser desenvolvido para prover acesso a esses ambientes
legados, com um conveniente conjunto de interfaces abstratas que são acessíveis de
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 50
_______________________________________________________________________________________
qualquer lugar para toda a organização. Num mundo multi-tier, um módulo de acesso a
dados que acessa legacy data hoje, pode ser substituído por um outro módulo que acesse
dados relacionais amanhã, sem afetar o resto da aplicação. O segredo disso tudo chama-se
interfaces.

Alta produtividade de desenvolvimento através de especialização - Com a tecnologia


two-tier, cada programador deve desenvolver todos os aspectos de uma aplicação,
incluindo apresentação, negócio e lógica de acesso a dados. Com isso, o fato de que
muitos programadores se superam em determinadas tarefas, e não em outras, não é
considerado, bem como o fato de que eles são mais produtivos quando especializados.

No mundo multi-tier, programadores com excelente skill para interface de usuários


podem se concentrar em desenvolver poderosos componentes de apresentação, e eles não
necessitam saber sobre os "internals" da lógica de negócios ou como dados são acessados
de um banco de dados. Isso vale também para analistas de banco de dados, que conhecem
a melhor maneira de acessar dados, ou analistas de negócio, que podem se concentrar em
desenvolver algoritmos de negócio. Tudo que eles precisam saber são as interfaces, e aqui
talvez a figura do contrato faça mais sentido. Quando um componente de negócio
precisar de dados, e só fazer a chamada apropriada ao componente de acesso a dados.

Infra-estrutura distribuída de computação - Uma infra-estrutura distribuída de


computação provê os serviços que permitem aos componentes da aplicação serem
transparentemente distribuídos por qualquer número de sistemas físicos, um conceito
normalmente conhecido como particionamento (partitioning).

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 51
_______________________________________________________________________________________

Acessando um Banco de Dados com Java

O que utilizar quando se pretende acessar um banco de dados a partir de uma aplicação
Java: JDBC ou SQLJ?

Ambas estas APIs são utilizadas no acesso a bancos de dados. Mas porque duas APIs e
não apenas uma? Na realidade você apenas necessita de JDBC, mas você verá que em
função da simplicidade de programação em SQLJ você desejará utilizar esta ferramenta.

JDBC é um padrão independente de vendedor desenvolvido pela Javasoft. JDBC foi


desenvolvido após ODBC (desenvolvido pela Microsoft). JDBC é a principal forma de
acesso a bancos de dados através de código Java. Vários fabricantes de SGBD, inclusive
Oracle, Intersolv e Weblogic, desenvolveram vários tipos de drivers JDBC.

SQLJ é um novo padrão para embutir código SQL diretamente em programas Java. É um
produto padrão desenvolvido pela Oracle, IBM, Sybase, Tandem e Informix. Para aqueles
que estão familiarizados com Pro*C (SQL embutido em C), você pode ver SQLJ como
Pro*Java. Então porque não se chama Pro*Java? Pro*qualquer coisa significa uma
tecnologia pro*prietária da Oracle, por exemplo, enquanto SQLJ é um padrão aberto.

JDBC

import java.sql.*;
public class JDBCExample
{ public static void main(String args[]) throws SQLException
{ DriverManager.registerDriver(new
oracle.jdbc.driver.OracleDriver());
Connection conn =
DriverManager.getConnection
("jdbc:oracle:thin:@sbd:1521:orcl", "scott",
"tiger");
// @nome_do_computador:porta:nome_da_instância
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(“SELECT ENAME, EMPNO, SAL “
+ “FROM EMP”);
while (rs.next())
{ String nome = rs.getString(1);
int numero = rs.getInt(2);
double salario = rs.getDouble(3);
System.out.println(nome + “ “ + numero + “ “ +
salario);
}

rs.close();
conn.close();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 52
_______________________________________________________________________________________
A primeira linha do método main é:

DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

Tudo o que você realmente necessita saber é que esta chamada faz com que JDBC saiba
que você está utilizando um driver da oracle.

A próxima chamada é:

Connection conn = DriverManager.getConnection


("jdbc:oracle:thin:@sbd:1521:orcl", "scott", "tiger");

Esta chamada cria uma objeto que representa uma conexão com o banco de dados. A
primeira parte do string de conexão, jdbc:oracle:thin, indica que deverá ser
utilizado um driver 100% java para estabelecer a conexão com o banco de dados. A
Segunda parte do string de conexão, @sbd:1521:orcl, especifica que a conexão
deverá ser efetuada com a instância orcl na máquina denominada sbd, cujo listener está
na porta 1521. A conexão é efetuada na conta do usuário scott que possui a password
tiger.

Agora que a conexão foi estabelecida podemos emitir comandos SQL para o banco de
dados:

Statement stmt = conn.createStatement();


ResultSet rs = stmt.executeQuery(“SELECT ENAME, EMPNO, SAL FROM EMP”);

As últimas linhas, rs.close() e conn.close(), permitem que JDBC saiba que você
terminou com o result set e que a conexão com o banco, bem como a correspondente
sessão, devem ser encerradas.

Tipos de Drivers Oracle JDBC

A Oracle fornece três tipos de drivers JDBC: um driver thin (fino) 100% Java, um OCI-
based fat driver e um driver do tipo server-side (também conhecido como KPRB). Todos
estes três drivers atualmente (fevereiro de 1999) atendem à especificação 1.2.2 de JDBC
e estão a caminho de atender à especificação de JDBC 2.0. Estes drivers são compatíveis
com JDK 1.0.2 e 1.1.6. Os drivers thin e fat podem ser utilizados com bancos de dados
Oracle a partir da versão 7.2. Todos estes drivers utilizam as mesmas APIs,
proporcionando flexibilidade máxima em como aplicações JDBC podem ser
desenvolvidas.

Como já mencionado, o driver Oracle do tipo thin é uma implementação 100% Java. Por
esta razão, é ideal para aplicações que necessitam ser 100% Java, tais como applets. Um
outro benefício do driver thin é que ele é pequeno (300k – ou 150k quando comprimido),
sendo interessante a sua utilização em aplicações que necessitam sofrer download, como

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 53
_______________________________________________________________________________________
as applets. Em termos de performance, o driver thin é ligeiramente mais lento do que seu
big-brother OCI-based, que foi implementado em C.
O driver Oracle do tipo fat possui uma camada JDBC implementada em cima da Oracle
Call Interface (OCI). OCI é uma biblioteca C para acesso a bancos de dados Oracle.
Como o driver fat necessita que esta biblioteca (OCI) seja pré-instalada, não pode ser
utilizado em aplicações 100% Java e por aplicações carregadas através de download. Este
driver, no entanto, é o ideal para utilização em aplicações middle-tier, tais como servlets.

O driver Oracle server-side é o último da família. Agora que o Oracle8i permite que
aplicações Java rodem dentro do banco de dados, estas aplicações necessitam de uma
forma de acessar os dados no banco de dados (a principal razão para executar Java no
banco é se o programa manipula muitos dados). Os drivers do tipo server-side foram
criados para este tipo de utilização. Como estes drivers sabem que o código está no banco
eles não necessitam abrir explicitamente uma conexão. Eles também evitam
completamente a latência devido à rede ou devido à comunicação entre processos
acessando diretamente dados SQL. Como você já deve saber, código Java no Oracle 8i é
executado acessando a mesma memória e ocupando o mesmo espaço dos processos que
acessam a instância do banco de dados.

Todos os três drivers mencionados anteriormente utilizam a mesma API. Isto dá aos
desenvolvedores de aplicações Java/JDBC grande flexibilidade em como desenvolver
aplicações Java. Por exemplo, digamos que uma aplicação Client/Server seja escrita
utilizando um driver JDBC-OCI. Neste caso, o cliente seria uma combinação de uma
aplicação Java com um driver e o servidor seria um banco de dados Oracle. Surge então o
Oracle8i e o desenvolvedor percebe que teria um grande ganho de desempenho se
executasse a parte da sua aplicação que faz muito acesso a dados, dentro do banco de
dados. Para efetuar esta modificação o desenvolvedor teria que carregar para dentro do
banco as classes que fazem acesso aos dados e modificar as linhas que estabelecem a
conexão para:

Connection conn =
new oracle.jdbc.driver.OracleDriver().defaultConnection();

Da mesma forma, o desenvolvedor poderia decidir modificar a sua aplicação para uma
applet Java. Neste caso, a única modificação JDBC necessária seria a especificação do
driver thin em vez do driver OCI-based no string de conexão. No mundo de hoje esta
flexibilidade deve ser levada em consideração. Ela permite que os desenvolvedores
possam rapidamente e facilmente adaptar suas aplicações para a configuração de
desenvolvimento do momento.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 54
_______________________________________________________________________________________
JDBC Não é Perfeito

Erros em comandos SQL só podem ser capturados em tempo de execução. Isto porque o
compilador Java utilizado para compilar o código JDBC não conhece a sintaxe e a
semântica de SQL. Em segundo lugar, escrever um comando SQL complexo em JDBC
pode ser bastante trabalhoso. Isto é ilustrado pelo código abaixo:

String nome = “CAFE BRASILEIRO”;


int qtd = 75;
PreparedStatement atualizarVendas = con.preparedStatement
(“UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?”);
atualizarVendas.setInt(1, qtd);
atualizarVendas.setString(2, nome);
atualizarVendas.execute();
atualizarVendas.close();

Observação: Um prepared statement deve ser utilizado quando valores dentro de um


comando SQL devem ser modificados dinamicamente e você pretende utilizar este
mesmo prepared statement diversas vezes, com diferentes valores de variáveis.

Este exemplo, nos trás duas questões: Há alguma coisa que pode ser feito para fazer com
que queries SQL em Java 1) sejam mais fáceis de escrever e de ler e 2) possam ser
verificadas (através de debug) em tempo de compilação? A resposta é SQLJ.

SQLJ

Para lhe dar uma idéia rápida de como SQLJ funciona vejamos o exemplo abaixo que faz
o mesmo que as linhas JDBC acima.

String nome = “CAFE BRASILEIRO”;


int qtd = 75;
#sql {UPDATE PRODUTOS SET QTD_SEMANA = :qtd WHERE NOME = :nome};

Note que no exemplo acima as variáveis qtd e nome foram diretamente embutidas no
comando SQL. Toda variável embutida em código SQL deve ser precedida por “:”.

SQLJ também resolve o problema do debug de código SQL. Como o código SQLJ não é
puro Java, ele necessita ser traduzido antes que possa ser compilado para o bytecode
Java. Este processo de tradução permite que os comandos SQL embutidos sejam
analisados semântica e sintaticamente, capturando erros de SQL em tempo de tradução
em vez de em tempo de execução (como ocorre com JDBC).

Há várias razões porque você deveria conhecer JDBC e SQLJ. JDBC deve ser utilizado
para gerar comandos SQL em tempo de execução enquanto SQLJ é ideal para comandos
SQL estáticos, isto é, quando o formato do comando SQL é conhecido no momento do
desenvolvimento da aplicação. Observe que em aplicações típicas a grande maioria dos
comandos SQL são estáticos.
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 55
_______________________________________________________________________________________
Em segundo lugar, a atual encarnação de SQLJ (da Oracle) é executada sobre JDBC. Isto
permite o desenvolvimento de aplicações SQLJ com qualquer configuração de driver,
assim como com JDBC (thin, fat ou server-side) mas isto também significa que o
desenvolvedor que utiliza SQLJ necessita conhecer os vários tipos de drivers JDBC.

Desenvolvendo uma Aplicação SQLJ

Existem 3 etapas no desenvolvimento de uma aplicação SQLJ:

1. Escrever o programa SQLJ.


2. Traduzir o programa SQLJ.
3. Executar o programa SQLJ.

Exemplo de Programa SQLJ:

import java.sql.*;
import sqlj.runtime.ref.DefaultContext;
import oracle.sqlj.runtime.Oracle;
#sql iterator MyIter (String ename, int empno, float sal);
public class MyExample
{ public static void main (String args[]) throws SQLException
{ Oracle.connect (“jdbc:oracle:thin:@sbd:1521:orcl”,
“scott”, “tiger”);
#sql { INSERT INTO EMP (ENAME, EMPNO, SAL)
VALUES (‘SALMAN’, 32, 20000) };
MyIter iter;

#sql iter = { SELECT ENAME, EMPNO, SAL FROM EMP };


while (iter.next())
{ System.out.println (iter.ename() +“ “+ iter.empno() +
“ “+ iter.sal());
}
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 56
_______________________________________________________________________________________
A primeira linha do método main é:

Oracle.connect (“jdbc:oracle:thin:@sbd:1521:orcl”,
“scott”, “tiger”);

Esta chamada estabelece uma conexão de banco de dados default para SQLJ utilizar. A
partir deste momento podemos escrever comandos SQLJ. O primeiro comando é:

#sql { INSERT INTO EMP (ENAME, EMPNO, SAL)


VALUES (‘SALMAN’, 32, 20000) };

O comando abaixo executa uma query. E o resultado da query não vai para um result set
como em JDBC. Em SQLJ não utilizamos result sets. Utilizamos iterators. A diferença
entre um result set e um iterator é que um result set pode armazenar o resultado de
qualquer query enquanto um iterator só pode armazenar o resultado de um tipo específico
de query. Por esta razão é preciso definir um iterator type para cada tipo de query
diferente.

#sql iter = { SELECT ENAME, EMPNO, SAL FROM EMP }

A linha de código abaixo define um tipo iterator denominado MyIter:

#sql iterator MyIter (String ename, int empno, float sal);

Olhando a definição acima podemos ver que este tipo de iterator pode armazenar
resultados com as seguintes características: a primeira coluna deve poder ser mapeada
para um String Java, a segunda para um int Java e a terceira para um float Java. Esta
definição também atribui nomes a estas colunas: ename, empno e sal respectivamente.

Uma instância de MyIter é criada com o comando:

MyIter iter;

E finalmente a instância iter é populada com dados. Então podemos acessar estes dados,
linha a linha, com o comando abaixo:

while (iter.next())
{ System.out.println (iter.ename() +“ “+ iter.empno() +“ “+
iter.sal());
}

O método next() de um iterator faz o mesmo que o método next de um result set.
Se o iterator não está na última linha de dados, next() retorna true e aponta o iterator
para a próxima linha. Caso contrário, retorna false. A maneira como os dados de
determinada linha são acessados em um iterator é um pouco diferente da forma como isto
é feito em com um result set em JDBC. O iterator (como você pode ver no exemplo
acima) possui métodos que correspondem aos nomes das colunas no iterator. Chamando
estes métodos, o dado na coluna correspondente é retornado.
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 57
_______________________________________________________________________________________
Traduzindo e Compilando um Programa SQLJ

Para traduzir um programa SQLJ faça:

>sqlj MyExample.sqlj

Há muitas opções que podem ser especificadas, mas a chamada acima é suficiente.

Enquanto o tradutor sqlj gera o arquivo MyExample.java ele verifica se o seu código
SQL embutido faz sentido. Ele analisa este código sintática e semanticamente. Ele pode
inclusive verificar se as tabelas, colunas e tipos de dados Java que você está utilizando
são apropriados. Ele efetua uma conexão com o banco de dados para efetuar esta
verificação. Se você deseja que o tradutor se conecte a um banco de dados de teste (e não
com o banco de produção) para efetuar esta verificação, você necessita especificar um
URL de um banco de dados com o qual a conexão deve ser efetuada. Você deverá
fornecer também a conta e a password do usuário.

Além de efetuar a verificação do SQL embutido em tempo de tradução, o translator


também gera um serialized object file (com a extensão .ser). Este arquivo contém uma
descrição das queries SQL feitas no programa SQLJ. Isto é feito com dois objetivos:

1) Os arquivos .java gerados pelo tradutor SQLJ efetuam chamadas a uma biblioteca
Java denominada SQLJ runtime. As classes nesta biblioteca utilizam o serialized
object file para descobrir que chamadas JDBC deve efetuar.

2) O serialized object file pode ser utilizado para customizar o programa SQLJ para um
fabricante específico de SGBD (isto é feito automaticamente pelo tradutor SQLJ).

A compilação dos arquivos .java gerados pelo tradutor é efetuada pelo tradutor SQLJ. O
compilador javac do JDK é executado em todos os arquivos .java. O processo de
compilação gera bytecode Java logo, após esta etapa, o programa SQLJ pode ser
executado chamando-se o interpretador java, assim:

>java MyExample

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 58
_______________________________________________________________________________________
JDBC

Inicialmente estudaremos a API JDBC 1.0, que acompanham a versão 1.1 do JDK.
Posteriormente estudaremos a API JDBC 2.0, que acompanham a versão 1.2 do JDK.

No estudo da API JDBC 1.0 aprenderemos como utilizar esta API para criar tabelas,
inserir valores em tabelas, emitir queries, recuperar os resultados de queries, e atualizar
tabelas. Você aprenderá a emitir os comandos statement e prepare statement, e verá,
ainda, como executar uma stored procedure e como capturar exceções e warnings.

No estudo da API JDBC 2.0 aprenderemos a mover o cursor em um scrollable result set,
como atualizar um result set e como fazer atualizações batch.

JDBC 1.0

Em um programa que utiliza JDBC a primeira coisa a fazer é importar os packages e


classes que você vai utilizar na nova classe que você vai criar na sua aplicação. As classes
dos programas exemplo apresentadas nesta apostila utilizam o package java.sql (a API
JDBC), que se torna disponível colocando-se a seguinte linha de código antes da
definição da class:

import java.sql.*;

Se você não incluir “import java.sql.*;” no seu código, você terá de escrever “java.sql. “
mais o nome da classe em frente de todos os métodos e campos JDBC que você utiliza.

Estabelecendo uma Conexão

Para se estabelecer uma conexão com o banco de dados é preciso:

1. Carregar o driver.
2. Fazer a conexão.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 59
_______________________________________________________________________________________
Como Carregar o Driver

Para carregar o driver do tipo bridge JDBC-ODBC é preciso escrever a seguinte linha de
código:

Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”);

A documentação do seu driver lhe informará o nome da classe que você deve utilizar. Se
o nome da classe é jdbc.DriverXYZ, então você deve carregar o diver assim:

Class.forName(”jdbc.DriverXYZ”);

Você não necessita criar uma instância do driver e registrá-lo no DriverManager porque
ao chamar Class.forName isto será feito automaticamente. Uma vez tendo-se carregado o
driver, ele se encontra disponível para uso.

Como fazer a Conexão

A conexão, realizada pelo driver, é efetuada com a seguinte linha de código:

Connection con = DriverManager.getConnection(url, ”Conta”, “Senha”);

A dificuldade aqui está em saber o que utilizar como url. Se você está utilizando um
driver do tipo ponte JDBC-ODBC, o URL JDBC começará com jdbc:odbc:. O resto do
URL é geralmente o nome do data source. Logo, se você está utilizando ODBC para
acessar um data source ODBC denominado “Uff_ds”, por exemplo, seu URL JDBC
seria: jdbc:odbc:Uff_ds.

Exemplo:

String url = “jdbc:odbc:Uff_ds”;


Connection con = DriverManager.getConnection (url, “Conta”, “Senha”);

Se você está utilizando um driver desenvolvido por terceiros, a documentação lhe dirá
que subprotocolo utilizar, isto é, o que colocar após jdbc: no URL JDBC. Por exemplo, se
um desenvolvedor de driver registrou o nome acme como o subprotocolo, a primeira e a
segunda parte do URL JDBC será: jdbc:acme: .

Se um dos drivers que você carregou reconhece o URL JDBC fornecido para o método
DriverManager.getConnection, este driver irá estabelecer a conexão com o DBMS
especificado no URL JDBC. A classe DriverManager gerencia todos os detalhes
necessários para estabelecer a conexão. A menos que você esteja escrevendo um driver,
você nunca utilizará os métodos da interface Driver, e o único método da classe
DriverManager que você realmente necessita conhecer é o
DriverManager.getConnection.
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 60
_______________________________________________________________________________________

A conexão retornada pelo método DriverManager.getConnection poderá ser utilizada


para criar comandos JDBC que passam seus comandos SQL para o SGBD. No exemplo
anterior o objeto con representa a conexão e será utilizado nos exemplos seguintes.

Exemplo:

import java.sql.*;
public class Lista_Empregados
{ public void lista_emp() throws Exception
{ String nome, salario;
Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection conn =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt = conn.createStatement();


ResultSet rset = stmt.executeQuery("SELECT * FROM EMP");

System.out.println ("Nome Salario");


while (rset.next())
{ nome = rset.getString ("ENAME");
salario = rset.getString("SAL");
System.out.println (nome+" "+salario);
}
}

public static void main (String[] args) throws Exception


{ Lista_Empregados emp = new Lista_Empregados();
emp.lista_emp();
}
}

No exemplo acima estamos utilizando um driver do tipo ponte JDBC-ODBC. A palavra


Oracle no comando DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");
representa o nome de uma fonte de dados ODBC.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 61
_______________________________________________________________________________________
Criando Tabelas

Primeiramente criaremos as tabelas do nosso banco de dados exemplo. Os nomes das


tabelas são: Produtos e Fornecedores. A tabela Produtos, contém as informações
necessárias sobre os tipos de produtos vendidos pela firma The Coffe Break. E a tabela
Fornecedores contém dados a respeito dos fornecedores de cada tipo de café vendido.

Layout da tabela Produtos:

Numero Number(3)
Nome Varchar2(32)
Num_Fornededor Number(4)
Preco Number(5,2)
Qtd_Semana Number(7,2)
Qtd_Total Number(9,2)

Numero Nome Num_Fornecedor Preco Qtd_Semana Qtd_total


101 Café Brasileiro 1001 7.99 0 0
102 Café Colombiano 1002 8.99 0 0
103 Café Francês 1003 9.99 0 0
104 Café Expresso 1001 5.77 0 0
105 Café Colombiano Descafeinado 1002 8.99 0 0
106 Café Frances Descafeinado 1003 9.99 0 0

Layout da tabela Fornecedores:

Numero Number(4)
Nome Varchar2(40)
Rua Varchar2(40)
Cidade Varchar2(20)
Estado Char(2)
CEP Char(9)

Numero Nome Rua Cidade Estado CEP


1001 Cia do Café Ltda R. das Laranjeiras 107 Rio RJ 22241-090
1002 Café Colombiano Ltda Av Brasil, 5240 Rio RJ 22348-030
1003 Café Francês Ltda R. México 70/403 Rio RJ 22127-040

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 62
_______________________________________________________________________________________
O comando abaixo cria a tabela PRODUTOS.

CREATE TABLE PRODUTOS


(NUMERO NUMBER(3)
NOME VARCHAR2(32)
NUM_FORNECEDOR NUMBER (4)
PRECO NUMBER (5,2)
QTD_SEMANA NUMBER (7,2)
QTD_TOTAL NUMBER (9,2))

O comando acima não termina com um terminados do SGBD, que varia de SGBD para
SGBD. Por exemplo, o Oracle utiliza um ; para indicar o final de um comando, enquanto
o Sysbase utiliza a palavra go. O driver que você estiver utilizando automaticamente
fornece o terminador apropriado e você não precisará inclui-lo no seu código JDBC.

Para criar a tabela PRODUTOS através de um programa Java:

String createTableProdutos = “CREATE TABLE PRODUTOS “ +


“(NUMERO INTEGER, NOME VARCHAR(32), “ +
“NUM_FORNECEDOR INTEGER, PRECO FLOAT, “ +
“QTD_SEMANA INTEGER, QTD_TOTAL INTEGER))”;

Os tipos de dados utilizados no comando CREATE TABLE acima são os tipos SQL
genéricos (também denominados tipos JDBC) que são definidos na classe java.sql.Types.

Alternativamente, é possível utilizar tipos de dados específicos do SGBD que se está


utilizando:

String createTableProdutos = “CREATE TABLE PRODUTOS “ +


“(NUMERO NUMBER(3), NOME VARCHAR2(32), “ +
“NUM_FORNECEDOR NUMBER(4), PRECO NUMBER (5,2), “ +
“QTD_SEMANA NUMBER(7,2), QTD_TOTAL NUMBER(9,2))“;

Criando Statements JDBC

Um objeto statement é utilizado para enviar o seu comando SQL para o SGBD. Você
simplesmente cria um objeto statement e o executa através dos métodos executeQuery ou
excuteUpdate, passando como parâmetro o string SQL que se deseja executar.

É utilizada uma instância da conexão ativa para criar um objeto do tipo Statement. No
exemplo abaixo, utilizamos nosso objeto Connection (con) para criar o objeto Statement
(stmt).

Statement stmt = con.createStatement();

Neste momento stmt existe, mas não possui um comando SQL para passar para o SGBD.
É preciso fornecer este string SQL para o método que utilizaremos para executar o objeto
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 63
_______________________________________________________________________________________
stmt. Para executar um comando SELECT utilizamos o método executeQuery, e para
comandos que criam ou modificam tabelas, utilizamos o método executeUpdate.

Stmt.executeUpdate (“CREATE TABLE PRODUTOS “ +


“(NUMERO NUMBER(3), NOME VARCHAR2(32), “ +
“NUM_FORNECEDOR NUMBER(4), PRECO NUMBER (5,2), “ +
“QTD_SEMANA NUMBER(7,2), QTD_TOTAL NUMBER(9,2))“);

Uma vez que criamos um string com o comando SQL que desejamos executar, e
associamos este string à variável createTableProdutos, poderíamos ter escrito o comando
que executa este string SQL, assim:

Stmt.executeUpdate (createTableProdutos);

Entrando com Dados em uma Tabela

Segue abaixo o código necessário para inserir uma linha na tabela PRODUTOS:

Statement stmt = com.createStatement();

stmt.executeUpdate (
“INSERT INTO FORNECEDORES(NUMERO, NOME, RUA, CIDADE, ESTADO, CEP) ” +
“VALUES(1001, ‘CIA DO CAFE LTDA’, ‘R. DAS LARANJEIRAS 107’, ‘RIO’, “ +
“‘RJ’, ‘22241-090’)”);

Observe que para inserir novas linhas na tabela PRODUTOS não é preciso instanciar um
novo objeto stmt.

stmt.executeUpdate (
“INSERT INTO FORNECEDORES(NUMERO, NOME, RUA, CIDADE, ESTADO, CEP) ” +
“VALUES(1002, ‘CAFE COLOMBIANO LTDA’, ‘AV. BRASIL, 5240’, ‘RIO’, “ +
“‘RJ’, ‘22348-030’)”);

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 64
_______________________________________________________________________________________
Exemplo:

import java.sql.*;
public class Cria_Tabela
{ public void cria_tab_Produtos() throws Exception
{ String nome, salario;
Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection conn =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

String createTableFornecedores = "CREATE TABLE FORNECEDORES " +


"(NUMERO NUMBER(4), NOME VARCHAR2(40), RUA VARCHAR2(40), " +
"CIDADE VARCHAR2(20), ESTADO CHAR(2), CEP CHAR(9))";

Statement stmt = conn.createStatement();


stmt.executeUpdate (createTableFornecedores);

stmt.executeUpdate (
“INSERT INTO FORNECEDORES(NUMERO, NOME, RUA, CIDADE, “ +
“ESTADO, CEP) ” +
“VALUES(1001, ‘CIA DO CAFE LTDA’, “ +
“‘R. DAS LARANJEIRAS 107’, ‘RIO’, ‘RJ’, ‘22241-090’)”);

stmt.executeUpdate (
“INSERT INTO FORNECEDORES(NUMERO, NOME, RUA, CIDADE, “ +
“ESTADO, CEP) ” +
“VALUES(1002, ‘CAFE COLOMBIANO LTDA’, “ +
“‘AV. BRASIL, 5240’, ‘RIO’, ‘RJ’, ‘22348-030’)”);
}

public static void main (String[] args) throws Exception


{ Cria_Tabela tab = new Cria_Tabela ();
tab.cria_tab_Produtos();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 65
_______________________________________________________________________________________
Obtendo Dados de uma Tabela

Agora que a tabela Fornecedores possui valores podemos escrever um comando SELECT
para recuperar estes valores.

JDBC retorna os resultados em um objeto do tipo ResultSet, logo é preciso declarar uma
instância da classe ResultSet para armazenar os resultados. O código abaixo declara um
objeto rs do tipo ResultSet e atribui o resultado de uma query a ele.

ResultSet rs = stmt.executeQuery(“SELECT * FROM FORNECEDORES”);

Utilizando o método Next

A variável rs, que é uma instância de ResultSet contém as linhas da tabela de


Fornecedores. Para acessar cada coluna é preciso navegar pelas linhas retornadas e
recuperar os valores de acordo com seus tipos. O método next nos permite percorrer o
cursor resultante da execução do comando select... Inicialmente o cursor encontra-se
posicionado logo acima da primeira linha de um objeto ResultSet, logo, a primeira
chamada ao método next move o cursor para a primeira linha, fazendo com que ela seja a
linha corrente. Sucessivas chamadas ao método next faz com que possamos percorrer o
cursor do início ao fim. Com a API JDBC 2.0 é possível, também, navegar em um cursor
de trás para frente, para posições específicas, e para posições relativas à linha corrente.

Utilizando os Métodos getXXX

Utilizamos os métodos getXXX de um tipo apropriado para recuperar o valor de cada


coluna. Por exemplo, a coluna NOME da tabela de Fornecedores armazena um valor do
tipo VARCHAR. O método para recuperar um valor de SQL do tipo VARCHAR é
getString. A Coluna NUMERO da tabela de Fornecedores é do tipo INTEGER e o
método utilizado para recuperar valores do tipo integer é getInt. A parte XXX do método
getXXX permite que JDBC saiba para que tipo de dado Java deve ser convertido o tipo
de dado SQL.

O código abaixo acessa os valores armazenados na linha corrente de rs e imprime em


cada o número e o nome de cada fornecedor.

String query = “SELECT * FROM FORNECEDORES”;


ResultSet rs = stmt.executeQuery(query);

System.out.println ("Nome Salario");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
String nome = rs.getString ("NOME");
System.out.println (numero+" "+nome);
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 66
_______________________________________________________________________________________
import java.sql.*;
public class Lista_Fornecedores
{ public void lista_fornec() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection conn =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt = conn.createStatement();

String query = “SELECT * FROM FORNECEDORES”;


ResultSet rs = stmt.executeQuery(query);

System.out.println ("Numero Nome");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
String nome = rs.getString ("NOME");
System.out.println (numero+" "+nome);
}
}

public static void main (String[] args) throws Exception


{ Lista_Fornecedores fornec = new Lista_Fornecedores();
fornec.lista_fornec();
}
}

O método getString é chamado para recuperar o valor armazenado na coluna Nome na


linha corrente do objeto rs. O valor que getString recupera é convertido de um
VARCHAR2 SQL para um String da linguagem Java e é atribuído ao objeto String s.

JDBC também permite que se recupere o valor de uma coluna através do seu número:

String s = rs.getString(2);

Observe que o número da coluna se refere ao número da coluna no result set, e não na
tabela original. Usar o número da coluna é ligeiramente mais eficiente.

JDBC permite que vários métodos getXXX sejam utilizados para recuperar certos tipos
de dados SQL. Por exemplo, o método getInt pode ser utilizado para recuperar qualquer
valor SQL numérico ou do tipo caracter. O dado recuperado será convertido para um
número int. Isto é, JDBC tentará converter um varchar em int. O método getInt deve ser
utilizado para recuperar apenas tipos SQL INTEGER, no entanto, não pode ser utilizado
para os tipos SQL BINARY, VARBINARY, LONGBINARY, DATE, TIME, ou
TIMESTAMP.

A tabela abaixo mostra que métodos podem ser legalmente utilizados para recuperar os
vários tipos de dados SQL e quais métodos são recomendados.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 67
_______________________________________________________________________________________

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 68
_______________________________________________________________________________________
Utilizando o Método getString

Embora o método getString possa seja recomendado para recuperar os tipos de dados
SQL CHAR e VARCHAR, é possível recuperar qualquer tipo de dado SQL básico. Você
não pode, no entanto, recuperar os novos tipos de dados SQL3 com ele. Recuperar
valores com getString pode ser muito útil, mas possui limitações. Se for utilizado para
recuperar um tipo numérico, getString converterá o tipo numérico para um objeto String
Java e o valor terá de ser convertido de volta para numérico antes de ser manipulado
como um número. Logo, se você desejar que uma aplicação recupere valores de qualquer
tipo SQL padrão (exceto os tipos SQL3), utilize o método getString.

Atualizando Tabelas

Suponha que após uma semana de trabalho, o proprietário da loja “The Coffee Break”
queira informar, para cada tipo de café, a quantidade (em kilos) vendida na semana.

O comando SQL que atualiza uma linha é:

String updateString = “UPDATE PRODUTOS SET QTD_SEMANA = 75 “ +


“WHERE NUMERO = 101”;

Utilizando o objeto Statement (stmt) o código abaixo executa o comando SQL contido no
string updateString:

stmt.executeUpdate(updateString);

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 69
_______________________________________________________________________________________
Exemplo:

Para executar este programa é preciso rodar o script CriaCafe.sql existente no diretório
c:\Java\TopicosAvancados\Banco_de_Dados

import java.sql.*;
public class Atualiza_Cafe
{ public void atualizaQtdCafe() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt = con.createStatement();

// Também poderia substituir por: String setenta_e_Cinco = “75”;


String updateString = “UPDATE PRODUTOS SET QTD_SEMANA = 75 “ +
“WHERE NUMERO = 101”;
stmt.executeUpdate(updateString);

String query = “SELECT * FROM PRODUTOS”;


ResultSet rs = stmt.executeQuery(query);

System.out.println ("Numero Qtd Vendida");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
int qtdSemana = rs.getInt ("QTD_SEMANA");
System.out.println (numero+" "+qtdSemana);
}
}

public static void main (String[] args) throws Exception


{ Atualiza_Cafe cafe = new Atualiza_Cafe();
cafe.atualizaQtdCafe();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 70
_______________________________________________________________________________________
Utilizando Prepared Statement

Se você deseja executar um objeto Statement muitas vezes, geralmente o tempo de


execução será reduzido utilizando-se o objeto PreparedStatement, em vez de Statement.
A vantagem de se utilizar o objeto PreparedStatement é que o comando SQL será enviado
do SGBD onde ele será compilado. Conseqüentemente, o objeto PreparedStatement
contém não apenas um comando SQL mas um comando SQL que foi previamente pré-
compilado. Isto significa que quando o comando PreparedStatement é executado, o
SGBD pode apenas executar o comando SQL PreparedStatement, sem ter que compilá-lo.

Embora objetos PreparedStatement possam ser utilizados para comandos SQL sem
parâmetros, você provavelmente os utilizará mais freqüentemente para comandos SQL
com parâmetros. A vantagem de se utilizar comandos SQL com parâmetros é que você
pode utilizar o mesmo comando fornecendo valores diferentes a cada execução.

Criando um objeto PreparedStatement

Assim como ocorre com objetos Statement, você cria um objeto PreparedStatement com
um objeto connection. Utilizando a nossa conexão aberta (con) dos exemplos anteriores,
você poderia escrever código como o que vem a seguir para criar um objeto
PreparedStatement que possui dois parâmetros:

String comando = “UPDATE PRODUTOS SET QTD_SEMANA = ? “ +


“WHERE NOME = ?”);
PreparedStatement atualizarVendas =
con.preparedStatement(comando);

A variável atualizarVendas agora contém o comando SQL “UPDATE PRODUTOS SET


QTD_SEMANA = ? WHERE NUMERO = ?”, que foi enviado (na maioria dos casos) ao
SGBD para uma pré-compilação.

Fornecendo Valores para Parâmetros de PreparedStatement

Você precisará fornecer valores para serem utilizados no local das interrogações (?), antes
de você executar o objeto PreparedStatement. Você efetua esta substituição chamando
um dos métodos setXXX definidos para a classe PreparedStatement. Se o valor que você
quer substituir é um número inteiro Java, então você chama o método setInt. Se o valor
que você deseja substituir é um string Java então você chama o método setString. Existe
um método setXXX para cada tipo de dado Java.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 71
_______________________________________________________________________________________
Para atualizarmos para 75 kilos, a quantidade vendida na semana do produto 101 seria
preciso o seguinte código:

atualizarVendas.setInt(1, 75);
atualizarVendas.setString(2, “CAFE BRASILEIRO”);
atualizarVendas.executeUpdate();

Após estes valores terem sido atribuídos a seus dois parâmetros de entrada, o comando
SQL em atualizaVendas será equivalente ao comando SQL no objeto do tipo String
(comando) abaixo. Logo, os dois fragmentos de código definidos abaixo são equivalentes
(mas não em termos de desempenho, uma vez que o objeto atualizarVendas contém o
comando SQL pré-compilado).

Fragmento de Código 1

String comando = “UPDATE PRODUTOS SET QTD_SEMANA = 75 “ +


“WHERE NOME = ‘CAFÉ BRASILEIRO’”);
stmt.executeUpdate(comando);

Fragmento de Código 2
PreparedStatement atualizarVendas = con.preparedStatement
(“UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?”);
atualizarVendas.setInt(1, 75);
atualizarVendas.setString(2, “CAFE BRASILEIRO”);
atualizarVendas.executeUpdate();

Utilize o método executeUpdate para executar o objeto Statement (stmt) e o objeto


PreparedStatement (atualizarVendas). Note, no entanto, que nenhum argumento é
fornecido para executeUpdate quando este método é utilizado para executar
atualizarVendas. A razão para isto é que o objeto atualizarVendas já contém o comando
SQL que deve ser executado.

Se o comando do fragmento de código 2 encontra-se dentro de um loop, e portanto será


executado várias vezes, é conveniente utilizar o objeto PreparedStatement.

Quando um parâmetro recebe um valor este valor é retido pelo parâmetro até que ele seja
substituído por outro valor ou até que o método clearParameters seja chamado. Utilizando
o objeto atualizarVendas (do tipo PreparedStatement), o seguinte fragmento de código
ilustra a reutilização de um prepared statement após ressetar o valor de apenas um de seus
parâmetros.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 72
_______________________________________________________________________________________
PreparedStatement atualizarVendas = con.prepareStatement
("UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?");
atualizarVendas.setInt(1, 175);
atualizarVendas.setString(2, "CAFE BRASILEIRO");
atualizarVendas.executeUpdate();

atualizarVendas.setString(2, "CAFE COLOMBIANO");


atualizarVendas.executeUpdate();

Exemplo:

Para executar este programa é preciso rodar o script CriaProd.sql existente no diretório
C:\Java\TopicosAvancados\Banco_de_Dados

import java.sql.*;
public class Atualiza_Duas_Linhas
{ public void atualizaLinhas() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

PreparedStatement atualizarVendas = con.prepareStatement


("UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?");
atualizarVendas.setInt(1, 175);
atualizarVendas.setString(2, "CAFE BRASILEIRO");
atualizarVendas.executeUpdate();

atualizarVendas.setString(2, "CAFE COLOMBIANO");


atualizarVendas.executeUpdate();

Statement stmt = con.createStatement();


String query = "SELECT * FROM PRODUTOS";
ResultSet rs = stmt.executeQuery(query);

System.out.println ("Numero Qtd Vendida");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
int qtdSemana = rs.getInt ("QTD_SEMANA");
System.out.println (numero+" "+qtdSemana);
}
}

public static void main (String[] args) throws Exception


{ Atualiza_Duas_Linhas linha = new Atualiza_Duas_Linhas();
linha.atualizaLinhas();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 73
_______________________________________________________________________________________
Utilizando um Loop para Atribuir Valores

Você pode tornar o código mais fácil utilizando um for ou um while para atribuir valores
para parâmetros de entrada:

PreparedStatement atualizarVendas;
String stringAtualizacao = "UPDATE PRODUTOS " +
"SET QTD_SEMANA = ? WHERE NOME = ?";
atualizarVendas = con.prepareStatement(stringAtualizacao);

int [] vendasDaSemana = {175, 150, 60, 155, 90};


String [] cafes = {"CAFE BRASILEIRO", "CAFE COLOMBIANO",
"CAFE FRANCES", "CAFE EXPRESSO",
"CAFE COLOMBIANO DESCAFEINADO"};

int comprimento = cafes.length;


for (int i = 0; i < comprimento; i++)
{ atualizarVendas.setInt(1, vendasDaSemana[i]);
atualizarVendas.setString(2, cafes[i]);
atualizarVendas.executeUpdate();
}

Observe que em uma aplicação real os dados provavelmente seriam digitados pelo
usuário.

Códigos de Retorno para o Método executeUpdate

Enquanto executeQuery retorna um objeto ResultSet que contém o resultado da query


enviada ao SGBD, o valor de retorno para executeUpdate é um int que indica quantas
linhas da tabela foram atualizadas. O código abaixo mostra o valor de retorno de
executeUpdate sendo atribuído à variável n:

atualizarVendas.setInt(1, 50);
atualizarVendas.setString(2, "CAFE BRASILEIRO");
int n = atualizarVendas.executeUpdate();

// Se n = 1, a linha foi atualizada com sucesso.

Quando o método executeUpdate é utilizado para executar um comando DDL, tal como
um comando CREATE TABLE, ele retorna um int 0.

Se o comando executeUpdate retorna 0, isto pode significar duas coisas: (1) o comando
executado foi um update que afetou zero linhas, ou (2) o comando executado foi um
comando DDL.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 74
_______________________________________________________________________________________
Exemplo:

Para executar este programa é preciso rodar o script CriaProd.sql existente no diretório
c:\Java\TopicosAvancados\Banco_de_Dados

import java.sql.*;
public class Atualiza_Com_Array
{ public void atualizaLinhas() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

PreparedStatement atualizarVendas;
String stringAtualizacao = "UPDATE PRODUTOS " +
"SET QTD_SEMANA = ? WHERE NOME = ?";
atualizarVendas = con.prepareStatement(stringAtualizacao);

int [] vendasDaSemana = {175, 150, 60, 155, 90};


String [] cafes = {"CAFE BRASILEIRO", "CAFE COLOMBIANO",
"CAFE FRANCES", "CAFE EXPRESSO",
"CAFE COLOMBIANO DESCAFEINADO"};

int comprimento = cafes.length;


for (int i = 0; i < comprimento; i++)
{ atualizarVendas.setInt(1, vendasDaSemana[i]);
atualizarVendas.setString(2, cafes[i]);
int n = atualizarVendas.executeUpdate();
if n = 0
System.out.println
(cafes[i]+" não foi encontrado.");
}

Statement stmt = con.createStatement();


String query = "SELECT * FROM PRODUTOS";
ResultSet rs = stmt.executeQuery(query);

System.out.println ("Numero Qtd Vendida");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
int qtdSemana = rs.getInt ("QTD_SEMANA");
System.out.println (numero+" "+qtdSemana);
}
}

public static void main (String[] args) throws Exception


{ Atualiza_Com_Array linha = new Atualiza_Com_Array();
linha.atualizaLinhas();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 75
_______________________________________________________________________________________
Utilizando Joins

Exemplo:

import java.sql.*;
public class Join
{ public void listaJoin() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt = con.createStatement();


String query = "SELECT F.NOME FNOME, P.NOME PNOME " +
"FROM FORNECEDORES F, PRODUTOS P " +
"WHERE F.NUMERO = NUM_FORNECEDOR";
ResultSet rs = stmt.executeQuery(query);

System.out.println ("Nome do Fornecedor Nome do Produto");


while (rs.next())
{ String fnome = rs.getString("FNOME");
String pnome = rs.getString("PNOME");
System.out.println (fnome+" "+pnome);
}
}

public static void main (String[] args) throws Exception


{ Join joinTabelas = new Join();
joinTabelas.listaJoin();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 76
_______________________________________________________________________________________
Utilizando Transações

Toda conexão é criada no modo auto-commit. Isto significa que cada comando SQL
individual é tratado como uma transação. Para ser mais preciso, um comando SQL é
comitado, por default, quando ele é concluído, não quando é executado. E um comando é
concluído quando todos os seus resultados e contadores de atualizações são recuperados.
Na maioria das situações, no entanto, um comando é concluído, e então comitado, logo
após a sua execução.

A maneira de se permitir que dois ou mais comandos sejam agrupados em uma transação
é desabilitando o modo auto-commit.

con.setAutoCommit(false);

Comitando uma Transação

Uma vez tendo-se desabilitado o modo auto-commit, nenhum comando SQL será
comitado até que você chame o método commit explicitamente. O código abaixo ilustra
uma transação:

con.setAutoCommit(false);
PreparedStatement atualizarVendas = con.prepareStatement
("UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?");
atualizarVendas.setInt(1, 17);
atualizarVendas.setString(2, "CAFE BRASILEIRO");
atualizarVendas.executeUpdate();

PreparedStatement atualizarTotal = con.prepareStatement


("UPDATE PRODUTOS SET QTD_TOTAL = ? WHERE NOME = ?");
atualizarTotal.setInt(1, 17);
atualizarTotal.setString(2, "CAFE BRASILEIRO");
atualizarTotal.executeUpdate();
con.commit();
con.setAutoCommit(true);

Neste exemplo, o modo auto-commit é desabilitado para a conexão, o que significa que
dois prepared statements atualizarVendas e atualizarTotal serão commitados quando o
método commit é chamado.

Para evitar conflitos ao longo de uma transação, os SGBD utilizam um mecanismo de


lock para bloquear o acesso por outros usuários ao dado que está sendo atualizado por
uma transação. Uma vez colocado um lock em uma linha de uma tabela, ele permanecerá
lá até que a transação seja comitada o sofra um rollback. O efeito deste lock evita que um
usuário leia uma informação suja, isto é, evita que o usuário leia uma informação que não
foi comitada e que, portanto, pode sofrer um rollback. Se você consegue ler um valor

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 77
_______________________________________________________________________________________
alterado mas ainda não comitado, e este valor sofre um rollback, você leu um valor
inválido.

O efeito de um lock no acesso a um dado é determinado pelo Transaction Isolation Level,


que varia desde “sem suporte a transações” a “suportando transações com regras
rigorosas a respeito”. Um exemplo de um transaction Isolation level é
TRANSACTION_READ_COMMITED, que não vai permitir que um valor seja acessado
até que ele seja comitado. Em outras palavras, se o transaction isolation level é designado
para TRANSACTION_READ_COMMITED, o SGBD não permitirá que leituras sujas
sejam efetuadas. A interface Connection inclui 5 valores que representam os níveis de
isolamento que você pode utilizar com JDBC.

Normalmente você não necessita fazer nada a respeito do transaction isolation level.
Você pode apenas utilizar o default para seu SGBD. JDBC permite que você descubra
qual o nível de isolamento que seu SGBD está utilizando, por default. Para fazer isto
utilize o método getTransactionIsolation da classe Connection. JDBC também nos
permite trocar o nível de isolamento com o método setIsolationLevel da classe
Connection. No entanto, para que estas modificações tenham efeito, é preciso que o
driver que você está utilizando dê suporte a níveis de isolamento.

Quando Chamar o Método rollback

Se você está tentando executar um ou mais comandos DML em uma transação e recebe
uma SQLException, você deveria chamar o método rollback para abortar a transação. Ao
capturar uma exceção SQLException você fica sabendo que algo saiu errado, daí a
necessidade de efetuar o rollback.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 78
_______________________________________________________________________________________
import java.sql.*;
public class Transacoes
{ public void atualizaLinha() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

con.setAutoCommit(false);
PreparedStatement atualizarVendas = con.prepareStatement
("UPDATE PRODUTOS SET QTD_SEMANA = ? WHERE NOME = ?");
atualizarVendas.setInt(1, 17);
atualizarVendas.setString(2, "CAFE BRASILEIRO");
atualizarVendas.executeUpdate();

PreparedStatement atualizarTotal = con.prepareStatement


("UPDATE PRODUTOS SET QTD_TOTAL = ? WHERE NOME = ?");
atualizarTotal.setInt(1, 17);
atualizarTotal.setString(2, "CAFE BRASILEIRO");
atualizarTotal.executeUpdate();
con.commit();
con.setAutoCommit(true);

Statement stmt = con.createStatement();


String query = "SELECT * FROM PRODUTOS";
ResultSet rs = stmt.executeQuery(query);

System.out.println ("Numero Qtd Semana Qtd Total");


while (rs.next())
{ int numero = rs.getInt("NUMERO");
int qtdSemana = rs.getInt ("QTD_SEMANA");
int qtdTotal = rs.getInt ("QTD_TOTAL");
System.out.println (numero+" "+qtdSemana+" "+qtdTotal);
}
}

public static void main (String[] args) throws Exception


{ Transacoes linha = new Transacoes();
linha.atualizaLinha();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 79
_______________________________________________________________________________________
Stored Procedures

É possível criar uma stored procedure através de um programa java, assim:

import java.sql.*;
public class CriaStoredProcedure
{ public void criaProcedure() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt = con.createStatement();

String createProcedure = “CREATE OR REPLACE PROCEDURE ... “;


stmt.executeUpdate(createProcedure);
}

public static void main (String[] args) throws Exception


{ CriaStoredProcedure procedure = new CriaStoredProcedure();
procedure.criaProcedure();
}
}

Chamando uma Stored Procedure

O primeiro passo é criar um objeto CallableStatement. Assim como objetos Statement e


PreparedStatement, isto é feito com o objeto Connection aberto. Um objeto
CallableStatement contém uma chamada a uma stored procedure. Ele não contém a stored
procedure, mas apenas a chamada. A primeira linha de código abaixo, cria uma chamada
à stored procedure utilizando a conexão con. Quando o driver encontra
“{call Nome_da_Stored_Procedure}”, traduz esta chamada para o comando SQL nativo
utilizado pelo SGBD para chamar stored procedures.

import java.sql.*;
public class ExecutaStoredProcedure
{ public void executaProcedure() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

CallableStatement cs = con.prepareCall("{call Remover_Produto}");


cs.execute();
}

public static void main (String[] args) throws Exception


{ ExecutaStoredProcedure procedure = new ExecutaStoredProcedure();
procedure.executaProcedure();
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 80
_______________________________________________________________________________________
Alguns SGBDs, ao executarem uma stored procedure, conseguem retornar um conjunto
de dados. Neste caso executaríamos esta stored procedure assim:

CallableStatement cs = con.prepareCall("{call Recupera_Produtos}");


ResultSet rs = cs.executeQuery();

Por outro lado, se a Stored Procedure executa apenas um comando DDL ou DML
deveríamos executá-la assim:

CallableStatement cs = con.prepareCall("{call Remover_Produto}");


cs.executeUpdate();

E se a stored procedure executa vários comandos DML e retorna ainda um conjunto de


dados deveríamos executá-la assim:

CallableStatement cs = con.prepareCall("{call Remover_Produto}");


cs.execute();

No caso do Oracle, que não é capaz de retornar um conjunto de dados em uma stored
procedure, podemos executar todas as stored procedures assim:

CallableStatement cs = con.prepareCall("{call Remover_Produto}");


cs.execute();

A classe CallableStatement é uma subclasse de PreparedStatement, logo um objeto


CallableStatement pode receber parâmetros, assim como objetos do tipo
PreparedStatement. Além disso, um objeto CallableStatement pode possuir parâmetros de
saída, e parâmetros de entrada e de saída. Parâmetros INOUT e o método execute são
raramente utilizados.

Capturando Exceções

No exemplo abaixo utilizamos dois blocos try e dois blocos catch. O primeiro bloco try
contém o método Class.forName, do package java.lang. Este método gera uma exceção
do tipo ClassNotFoundException, e o bloco catch abaixo trata esta exceção. O segundo
bloco try contém métodos JDBC, que geram exceções do tipo SQLException, logo um
bloco catch no final da aplicação pode tratar todas estas exceções, porque todas serão
exceções do mesmo tipo.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 81
_______________________________________________________________________________________
Recuperando Exceções

JDBC nos permite ver exceções e warnings gerados pelo seu SGBD e pelo runtime java.

Exemplo:

try
{ // Código que eventualmente pode gerar uma exceção.
}
catch (SQLException ex)
{ System.err.println (“SQLException: “ + ex.getMessage());
}

try
{ Class.forName(“myDriverClassName”);
}
catch (java.lang.ClassNotFoundException e)
{ System.err.print (“ClassNotFoundException: “);
System.err.println (e.getMessage());
}

Este exemplo ilustra como imprimir o componente mensagem de um objeto


SQLException, que é suficiente na maioria dos casos.

Existem, no entanto, três componentes que podem ser impressos: a mensagem (um string
que descreve a mensagem de erro), o SQL state (um string que identifica o erro de acordo
com a CONVENÇÃO x/Open SQLState), e o código de erro do fabricante do SGBD). O
objeto exception (e) é capturado, e seus três componentes são acessados com os métodos
getMessage, getSQLState, e getErrorCode.

O bloco catch abaixo captura mais de uma exceção, caso ocorra. Se existir uma segunda
exceção ela será concatenada à primeira (e), logo, e.getNextException pode ser chamado
para se verificar se há mais alguma exceção. Se existe outra exceção o loop continua e
imprime a próxima mensagem de erro. O loop continua até que não haja mais exceções.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 82
_______________________________________________________________________________________
try
{ // Código que pode gerar uma ou mais exceções.
}
catch (SQLException ex)
{ System.out.print (“\n--- SQLException Capturada ---\n“);
while (ex != null)
{ System.out.print (“Message: “ + ex.getMessage());
System.out.print (“SQLState: “ + ex.getSQLState());
System.out.print (“ErrorCode: “ + ex.getErrorCode());
System.out.print (““);
ex = ex.getNextException();
}
}

SQLState é um código definido em X/Open e ANSI-92 que identifica a exceção. Dois


exemplos de SQLState são:

8001 -- No suitable driver


HY011 -- Operation invalid at this time

Já o código de erro do fabricante do SGBD é depende do driver.

Recuperando Warnings

Objetos do tipo SQLWarnings são uma subclasse de SQLException e tratam de warnings


no acesso a bancos de dados. Warnings não param a execução de uma aplicação, como as
exceções. Eles simplesmente alertam o usuário de que algo inesperado aconteceu. Por
exemplo, um warning pode lhe avisar que um privilégio que você tentou revogar não foi
revogado. Ou um warning pode lhe avisar que um erro ocorreu no momento da
desconexão solicitada.

Um warning pode ser reportado por um objeto Connection, Statement (incluindo


PreparedStatement e CallableStatement), ou ResultSet. Cada uma destas classes possui
um método getWarnings, que deve ser invocado para ver o primeiro warning reportado
para o objeto em questão. Se um getWarnings retorna um warning você pode chamar o
método getNextWarning para recuperar warnings adicionais. Isto significa, no entanto,
que se você deseja capturar um warning gerado para um statement, você deve capturá-lo
antes de executar o próximo statement.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 83
_______________________________________________________________________________________
Exemplo:

Statement stmt = con.createStatement();


ResultSet rs = stmt.executeQuery (“select * from produtos”);
While (rs.next())
{ String nome = rs.getString(“NOME”);
System.out.print (“Café disponivel: “ + nome);
SQLWarning warning = stmt.getWarnings();
if (warning != null)
{ while (warning != null)
{ System.out.print (“Message:“ + warning.getMessage());
System.out.print(“SQLState:“ + warning.getSQLState());
System.out.print(“ErrorCode:“+warning.getErrorCode());
System.out.print (““);
warning = warning.getNextException();
}
}
SQLWarning warn = rs.getWarnings();
if (warn != null)
{ while (warn != null)
{ System.out.print (“Message: “ + warn.getMessage());
System.out.print (“SQLState: “ + warn.getSQLState());
System.out.print (“ErrorCode:“ + warn.getErrorCode());
System.out.print (““);
warn = warn.getNextException();
}
}
}

O tratamento de warnings não é comum. O warning mais comum é o do tipo


DataTruncation, uma subclasse de SQLWarning. Todos os objetos do tipo
DataTruncation possuem um SQLState = 01004. Métodos de DataTruncation nos
permitem descobrir qual coluna ou parâmetro foi truncada, se o truncamento ocorreu
numa operação de leitura ou de escrita, quantos bytes deveriam ter sido transferidos e
quantos foram efetivamente transferidos.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 84
_______________________________________________________________________________________
JDBC 2.0

O package java.sql que faz parte do JDK 1.2 (conhecido como JDBC 2.0 API) inclui
várias características novas não incluídas no package java.sql que faz parte do JDK 1.1
(conhecido como JDBC 1.0 API). Todos os exemplos de código apresentados até aqui
foram escritos utilizando a API JDBC 1.0.

Com a API JDBC 2.0 será possível:

1. Navegar para frente e para trás em um result set, ou mover-se para uma linha
específica.
2. Fazer atualizações em tabelas do banco utilizando métodos da linguagem Java em vez
de utilizar SQL.
3. Enviar vários comandos SQL para o banco como uma unidade, ou batch.
4. Utilizar os novos tipos de dados SQL3 como valores de colunas.

Até o momento que este material foi escrito (junho de 99) não havia nenhum driver
implementado com as características aqui apresentadas, no entanto, vários drivers
estavam em desenvolvimento.

Navegando em um Cursor em Ambas as Direções

Scrollable Result Sets tornam possível a criação de aplicações gráficas com grids para a
exibição de result sets. Outra possibilidade é a navegação no ResultSet para atualização
de dados.

O código abaixo mostra como criar um objeto ResultSet scrollable:

Statement stmt = com.CreateStatement (ResultSet.TYPE_SCROLL_SENSITIVE,


ResultSet.CONCUR_READ_ONLY);
ResultSet srs = stmt.executeQuery(“SELECT NOME, PRECO FROM PRODUTOS”);

Observe que este código adiciona dois argumentos ao método CreateStatement. O


primeiro argumento é uma das três constantes adicionadas ao ResultSet API para indicar
o tipo do objeto ResultSet:

• TYPE_FORWARD_ONLY - É possível navegar apenas para frente,


como com API JDBC 1.0.
• TYPE_SCROLL_INSENSITIVE - É possível navegar em ambas as direções.
O result set reflete as modificações
nele efetuadas enquanto ele está aberto.
• TYPE_SCROLL_SENSITIVE - É possível navegar em ambas as direções.
O result set não reflete as modificações
nele efetuadas enquanto ele está aberto.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 85
_______________________________________________________________________________________
O segundo argumento é uma das duas constantes que indicam se o ResultSet é read-only
ou pode ser atualizado. Estas variáveis são: CONCUR_READ_ONLY e CONCUR_UPDATE. É
preciso especificar os dois parâmetros, no entanto, como ambos são numéricos é preciso
tomar cuidado para não inverter a ordem, uma vez que o compilador não vai detectar o
erro.

Se você não especificar nenhuma constante para o tipo de navegação e possibilidade de


atualização, o default é: TYPE_FORWARD_ONLY e CONCUR_READ_ONLY. (Como acontece
quando se utiliza a API JDBC 1.0).

Especificando-se TYPE_SCROLL_INSENSITIVE ou TYPE_SCROLL_SENSITIVE você obtém


um objeto ResultSet scrollable. A diferença entre eles tem a ver com o fato do result set
refletir ou não as modificações nele efetuadas enquanto ele está aberto e se certos
métodos podem ser chamados para detectar estas modificações. Todos os tipos de result
set tornarão as modificações visíveis se forem fechados e reabertos. Você deve ter em
mente que independentemente do tipo de result set que você venha a escolher, o
funcionamento deste result set estará limitado pelo que o seu SGBD e Driver provêem.

Quando você cria um novo objeto do tipo ResultSet, o cursor é inicialmente posicionado
antes do primeira linha. Com um scrollable result set é possível utilizar os métodos next
(assim como com a API JDBC 1.0) e o método previous. Ambos estes métodos retornam
false quando o cursor vai além do result set. (posição após o último ou antes do primeiro).

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 86
_______________________________________________________________________________________
Exemplo:

import java.sql.*;
public class TiposDeResultSet
{ public void lista() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_READ_ONLY);
String query = "SELECT NOME " +
"FROM FORNECEDORES";
ResultSet rs = stmt.executeQuery(query);

System.out.println ("Nome do Fornecedor");


while (rs.next())
{ String fnome = rs.getString("NOME");
System.out.println (fnome);
}
}

public static void main (String[] args) throws Exception


{ TiposDeResultSet tipos = new TiposDeResultSet();
tipos.lista();
}
}

E para processar os dados de trás para frente:

import java.sql.*;
public class TiposDeResultSet
{ public void lista() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT NOME FROM FORNECEDORES");
rs.afterLast();
System.out.println ("Nome do Fornecedor");
while (rs.previous())
{ String fnome = rs.getString("NOME");
System.out.println (fnome);
}
}

public static void main (String[] args) throws Exception


{ TiposDeResultSet tipos = new TiposDeResultSet();
tipos.lista();
}
}
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 87
_______________________________________________________________________________________
É possível mover o cursor para uma linha específica:

rs.absolute(4); // Move o cursor para a quarta linha.

Se o cursor tem 500 linhas a linha de código abaixo move o cursor para a linha 497:

rs.absolute(-4);

Com o método relative você pode especificar quantas linhas você deseja mover o cursor e
em que direção:

rs.absolute(4); // Move o cursor para a quarta linha.


...
rs.relative(-3); // Move o cursor para a primeira linha.
...
rs.relative(2); // Move o cursor para a terceira linha.

O método getRow nos permite verificar em que linha o cursor encontra-se posicionado.

rs.absolute(4); // Move o cursor para a quarta linha.


int numeroLinha = rs.getRow(); // numeroLinha deveria ser 4.
rs.relative(-3);
int numeroLinha = rs.getRow(); // numeroLinha deveria ser 1.
rs.relative(2);
int numeroLinha = rs.getRow(); // numeroLinha deveria ser 3.

Quatro métodos adicionais nos permitem verificar se o cursor se encontra em uma


posição específica: isFirst, isLast, isBeforeFirst e isAfterLast. O código abaixo
testa se o cursor está posicionado após a última linha. Caso não esteja movemos o cursor
para esta posição:

if (rs.isAfterLast() == false)
{ rs.afterLast();
}
while (rs.previous())
{ String fnome = rs.getString("NOME");
System.out.println (fnome);
}

Nas duas próximas seções você verá como utilizar os dois métodos moveToInsertRow e
moveToCurrentRow.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 88
_______________________________________________________________________________________
Atualizando Result Sets

É preciso criar uma Result Set atualizável. Para fazer isso forneça a constante
CONCUR_UPDATABLE da classe ResultSet para o método createStatement. O objeto
Statement criado irá produzir um objeto do tipo ResultSet atualizável cada vez que uma
query for executada. O código abaixo cria um result set atualizável e scrollable. Com um
scrollable result set você pode navegar pelas linhas que deseja modificar e se o tipo for
TYPE_SCROLL_SENSITIVE, você poderá recuperar o novo valor em uma linha do cursor,
após tê-lo atualizado.

import java.sql.*;
public class TiposDeResultSet
{ public void lista() throws Exception
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery("SELECT * FROM FORNECEDORES");

// Agora você pode utilizar novos métodos JDBC 2.0 da


// interface ResultSet para inserir uma nova linha em rs,
// deletar uma linha de rs, ou modificaro valor de uma
// coluna de rs.

. . .

Atualizando um Result Set Programaticamente

Uma modificação é uma alteração de um valor de uma coluna na linha corrente. Vamos
supor que desejamos aumentar o preço de um produto. Com JDBC 1.0 fazemos:

stmt.executeUpdate(“UPDATE PRODUTOS “ +
“SET PRECO = 10.99 “ +
“WHERE NUMERO = 101”);

Utilizando a API JDBC 2.0 podemos escrever:

rs.first();
rs.updateFloat(“PRECO”, 10.99);

Uma operação de update em JDBC 2.0 afeta a linha corrente do cursor. Existe um método
updateXXX para cada tipo de dado. updateString, updateBigDecimal, updateInt, etc.

Até este momento, o preço do produto número 101 é 10.99 em rs, mas o preço na tabela
PRODUTOS ainda não foi alterado. Para que esta atualização seja efetuada no banco e
_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 89
_______________________________________________________________________________________
não apenas no result set é preciso chamar o método updateRow da interface ResultSet,
conforme vem abaixo:

rs.first();
rs.updateFloat(“PRECO”, 10.99);
rs.updateRow();

Se você tivesse movido o cursor para outra linha antes de executar rs.updateRow(),
a alteração teria sido perdida.

Após executar os comandos:

rs.first();
rs.updateFloat(“PRECO”, 10.99);

é possível cancelar a alteração chamando o método cancelRowUpdates. Este método


cancela todas as modificações efetadas em uma linha e ainda não confirmadas através de
rs.updateRow().

rs.first();
rs.updateFloat(“PRECO”, 10.99);
rs.cancelRowUpdates();
rs.updateFloat(“PRECO”, 11.99);
rs.updateRow();

Todas as movimentações em um cursor fazem referência a linhas em um objeto result set,


e não a linhas da tabela no banco de dados. Se uma query seleciona cinco linhas de uma
tabela de banco de dados, existirão 5 linhas no result set. A ordem das linhas no result set
não tem nada a ver com a ordem das linhas na tabela. De fato, a ordem das linhas na
tabela é indeterminada. O SGBD acompanha que linha foi selecionada e faz as
atualizações nas linhas apropriadas, que podem ser encontradas em qualquer local da
tabela. Quando uma linha é inserida, por exemplo, não há como saber onde na tabela ela
foi inserida.

Inserindo e Deletando Linhas Programaticamente

Como inserir uma linha com a API JDBC 1.0:

Stmt.executeUpdate(“INSERT INTO PRODUTOS “ +


“VALUES (107, ‘CAFÉ BOLIVIANO’, 1003, 10.44, 0, 0)”;

Como inserir uma linha com a API JDBC 2.0:

É possível inserir uma nova linha em um result set e na tabela correspondente em um


único passo. Você constrói uma nova linha em algo denominado “the insert row”,
uma linha especial associada a todo objeto ResultSet. Esta linha não faz realmente parte

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 90
_______________________________________________________________________________________
do result set. Você pode pensar nela como um buffer separado no qual uma nova linha é
composta.

O primeiro passo será mover o cursor para a insert row, chamando-se o método
moveToInsertRow. O próximo passo é designar um valor para cada coluna na linha. Isto
deve ser feito chamando-se os métodos updateXXX para cada valor. E fianlmente chama-
se o método insertRow. Este método simultaneamente insere a linha no result set e na
tabela a partir da qual o result set foi montado.

O código abaixo cria um objeto ResultSet scrollable e atualizável que contém todas as
linhas e colunas da tabela produtos.

Connection con =
DriverManager.getConnection("jdbc:odbc:Oracle","Scott","Tiger");

Statement stmt =
con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery("SELECT * FROM FORNECEDORES");

E o fragmento de código abaixo, utiliza o objeto ResultSet (rs) para inserir um novo
produto.

rs.moveToInsertRow();
rs.updateInt(“NUMERO”, 107);
rs.updateString(“NOME”, “CAFÉ BOLIVIANO”);
rs.updateInt(“NUM_FORNECEDOR”, 1003);
rs.updateFloat(“PRECO”, 10.44);
rs.updateFloat(“QTD_SEMANA”, 0);
rs.updateFloat(“QTD_TOTAL”, 0);
rs.insertRow(); // Insere no result set e no banco ao mesmo tempo.

Como você pode utilizar o número da coluna em vez de seu nome, poderíamos fazer o
seguinte:

rs.updateInt(1, 107);
rs.updateString(2, “CAFÉ BOLIVIANO”);
...

Após chamar o método insertRow, você pode começar a construir outra linha para ser
inserida, ou você pode mover o cursor de volta para uma linha do result set. Para por o
cursor em uma linha específica você pode utilizar os métodos: first, last, beforeFirst,
afterLast, e absolute. Você também pode utilizar os métodos previous, relative e
moveToCursorRow. Você pode chamar o método moveToCurrentRow apenas quando o
cursor está na insert row.

Quando você chama o método moveToInsertRow, o resultset registra qual é a linha


corrente. Consequentemente, o método moveToCurrentRow pode mover o cursor da

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 91
_______________________________________________________________________________________
insert row para a linha que era a corrente antes do método moveToInsertRow ser
chamado.

O result set rs acima é atualizável, scrollable, e sensível a modificações efetuadas por ele
próprio e por outros. Embora seu tipo seja TYPE_SCROLL_SENSITIVE, é possível que um
método getXXX chamado após a inserção de uma linha não recupere valores para a nova
linha inserida. Existem métodos da interface DatabaseMetaData que nos indicarão o que
é visível e o que é detectado nos diferentes tipos de result set para o seu driver e SGBD.
Estes métodos são discutidos em detalhe em JDBC Database Access with Java, mas eles
estão fora do escopo deste curso.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 92
_______________________________________________________________________________________
import java.sql.*;
public class InsereLinha
{ public static void main (Strings args[])
{ try
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
}
catch (java.lang.ClassNotFoundException e)
{ System.err.print(“ClassNotFoundException: “);
System.err.println(e.getMessage());
}
try
{ Connection con =
DriverManager.getConnection
("jdbc:odbc:Oracle","Scott","Tiger");
Statement stmt =
con.createStatement
(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery
("SELECT NOME FROM PRODUTOS");

rs.moveToInsertRow();
rs.updateInt(“NUMERO”, 107);
rs.updateString(“NOME”, “CAFÉ BOLIVIANO”);
rs.updateInt(“NUM_FORNECEDOR”, 1003);
rs.updateFloat(“PRECO”, 10.44);
rs.updateFloat(“QTD_SEMANA”, 0);
rs.updateFloat(“QTD_TOTAL”, 0);
rs.insertRow(); // Insere no result set e no banco
// ao mesmo tempo.
rs.updateInt(“NUMERO”, 108);
rs.updateString(“NOME”, “CAFÉ PILAO”);
rs.updateInt(“NUM_FORNECEDOR”, 1001);
rs.updateFloat(“PRECO”, 7.48);
rs.updateFloat(“QTD_SEMANA”, 0);
rs.updateFloat(“QTD_TOTAL”, 0);
rs.insertRow(); // Insere uma Segunda linha

rs.beforeFirst();
System.out.println ("Nome do Fornecedor");
while (rs.previous())
{ String fnome = rs.getString("NOME");
System.out.println (fnome);
}
rs.close();
stmt.close();
con.close();
}
catch (SQLException ex)
{ System.err.println(“SQLexception: “ + ex.getMessage());
}
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 93
_______________________________________________________________________________________
Deletando uma Linha

Para deletar uma linha basta mover o cursor para a linha que se deseja remover, para
então, chamar o método deleteRow. Por exemplo:

rs.absolute(4);
rs.deleteRow();

Assim, a quarta linha é removida do result set e da tabela no banco de dados.

Alguns drivers JDBC removem a linha deletada do result set. Outros drivers JDBC
utilizam uma linha em branco para demonstrar que ali havia uma linha que foi removida.
Desta forma, ainda é possível utilizar o método absolute para posicionar-se em uma linha
específica. Isto é, o número absoluto de uma linha não muda com a deleção de uma linha
anterior.

Lembre-se que os drivers funcionam de maneira diferente. Se você pretende escrever uma
aplicação para rodar com diferentes bancos de dados, você não deveria escrever código
que dependa da existência de uma linha em branco no local de uma linha deletada.

Vendo Modificações em Result Sets

Se você ou qualquer outro usuário modifica os dados de um result set, a modificação


efetuada será sempre visível se você fechar e então reabrir o result set. Em outras
palavras, se você executar novamente a query que criou o result set.

A questão é se você verá as modificações efetuadas por você e por outros usuários
enquanto o result set ainda está aberto. (Geralmente você está mais interessado nas
modificações efetuadas pelos outros usuários). A resposta a esta questão depende do
SGBD, do driver, e do tipo de objeto ResultSet que você possui.

Com um objeto ResultSet do tipo TYPE_SCROLL_SENSITIVE, você pode sempre ver as


atualizações feitas por quem quer que seja. Você geralmente pode ver inserções e
deleções, mas a única maneira de se ter certeza é utilizando os métodos da interface
DatabaseMetaData que retornam esta informação.

Você pode, até certo ponto regular que modificações são visíveis aumentando ou
diminuindo o transaction isolation level para a sua conexão com o banco de dados. Por
exemplo, a seguinte linha de código (onde con é um objeto de uma conexão ativa)
designa TRANSACTION_READ_COMMITTED para o isolation level da conexão.

con.setTransactionIsolation(TRANSACTION_READ_COMMITTED);

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 94
_______________________________________________________________________________________
Com este nível de isolamento, o seu objeto ResultSet não mostrará nenhuma modificação
antes que elas sejam comitadas.

Em um result set do tipo TYPE_SCROLL_INSENSITIVE, você geralmente não pode ver as


modificações efetuadas no result set enquanto ele está aberto. Este tipo de result set deve
ser utilizado quando o programador deseja trabalhar com uma visão consistente dos
dados e, portanto, não deseja ver as modificações efetuadas por terceiros.

Você pode utilizar o método refreshRow para obter os últimos valores para uma linha
diretamente do banco de dados. Este método pode ser muito custoso especialmente se o
banco de dados retorna várias linhas quando o refresh é executado.

Mesmo quando um result set é sensitive e as modificações são visíveis, uma aplicação
pode nem sempre ver a última modificação efetuada em determinada linha, se o driver
recupera várias linhas de cada vez e faz um cache delas. Neste caso, a utilização do
método refreshRow é a única maneira de se ter certeza que você está vendo a versão
mais atualizada do dado.

Para utilizar o método refreshRow, o result set deve ser sensitive, caso contrário nada
acontecerá. Um exemplo da necessidade de um refresh seria, por exemplo, para verificar
se o assento que você está reservando (em uma passagem aérea) ainda está disponível.

import java.sql.*;
public class VerificaPreco
{ public static void main (Strings args[])
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection
("jdbc:odbc:Oracle","Scott","Tiger");
Statement stmt = con.createStatement
(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet rs = stmt.executeQuery("SELECT * FROM PRODUTOS");

rs.absolute(4);
Float preco1 = rs.getFloat(“PRECO”);
// faz algo . . .

rs.absolute(4);

rs.refreshRow();
Float preco2 = rs.getFloat(“PRECO”);
if (preco2 > preco1)
{ // Faz algo . . .
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 95
_______________________________________________________________________________________
Fazendo Atualizações em Modo Batch

Um Batch Update é um conjunto de múltiplos comandos UPDATE que são submetidos


ao banco para processamento na forma batch. Enviar vários comandos UPDATE para o
banco de dados, como uma unidade, pode em algumas situações, ser muito mais eficiente
do que enviar cada comando UPDATE separadamente. Esta é uma das características
providas pela API JDBC 2.0.

Com a API JDBC 1.0 você pode submeter atualizações ao banco de dados
individualmente com o método executeUpdate. Múltiplos comandos executeUpdate
podem ser enviados em uma mesma transação, mas embora sejam comitados ou sofreram
rollback como uma unidade, eles ainda são processados individualmente.

Com a API JDBC 2.0, objetos Statement, PreparedStatement e CallableStatement


possuem a habilidade de manter uma lista dos comandos que devem ser submetidos
juntos como um batch. Eles são criados com uma lista associada que está inicialmente
vazia. Você adiciona comandos SQL a esta lista com o método addBatch e pode esvaziá-
la com o método clearBatch. Para enviar todos os comandos para o banco de dados, deve
ser utilizado o método executeBatch.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 96
_______________________________________________________________________________________
Exemplo:

import java.sql.*;
public class VerificaPreco
{ public static void main (Strings args[])
{ Class.forName ("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con =
DriverManager.getConnection
("jdbc:odbc:Oracle","Scott","Tiger");

con.setAutoCommit(false); // Desabilita o auto-commit


// mode para que a transação não seja automaticamente
// comitada ou rolbecada quando o método executeBatch
// é chamado. Para permitir o correto tratamento de
// erros, você deve sempre desabilitar o auto-commit
// mode antes de começar um batch update.

Statement stmt = con.createStatement(); // Esta linha cria


// um objeto Statement (que como todo objeto
// Statement) possui uma lista de comandos associada a
// ele. Inicialmente esta lista encontra-se vazia.

smt.addBatch(“INSERT INTO PRODUTOS “ +


“VALUES(109,‘AMARETO’,1003, 9.99, 0, 0)”);
smt.addBatch(“INSERT INTO PRODUTOS “ +
“VALUES(110,‘HAZELNUT’,1003, 9.99, 0, 0)”);
smt.addBatch(“INSERT INTO PRODUTOS “ +
“VALUES(111,‘AMARETO DESCAF.’,1003, 9.99, 0, 0)”);
smt.addBatch(“INSERT INTO PRODUTOS “ +
“VALUES(112,‘ HAZELNUT DESCAF.’,1003, 9.99, 0, 0)”);
// Cada uma das linhas de código acima adiciona um
// comando à lista de comandos do objeto stmt.

int [] contador = stmt.executeBatch();

/* Nesta linha stmt envia os quatro comandos adicionados à


sua lista de comandos para serem executados no banco de dados como
um batch. Note que stmt utiliza o método executeBatch para enviar
as inserções para o banco, e não o método executeUpdate, que envia
apenas um comando e retorna um único contador de atualizações. O
banco de dados executará os comandos na mesma ordem em que foram
adicionados à lista e retornará um contador de atualizações para
cada comando da lista. Estes contadores serão armazenados no array
de inteiros denominado contador. Neste caso, os contadores valerão
1 uma vez que estas inserções afetam uma única linha. Agora a
lista de comandos é esvaziada uma vez que os quatro comandos
adicionados previamente enviados para execução no banco de dados.
A qualquer momento esta lista pode ser esvaziada com o método
clearBatch. */

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 97
_______________________________________________________________________________________
Tratamento de Exceções em Batch Update

Há duas exceções que podem ser geradas durante uma operação de Batch Update:
SQLException e BatchUpdateException.

Todos os métodos da API JDBC geram exceções do tipo SQLException (através de um


objeto deste tipo) quando ocorre um problema no acesso ao banco de dados. O método
executeBatch gerará uma exceção do tipo SQLException para adicionar um comando
que retorna um result set, isto é, se você adicionar um SELECT, ou alguns dos métodos
da interface DatabaseMetaData que também retornam um result set. A exceção ocorre
quando o comando que retorna um result set é executado, e não quando ele é adicionado à
lista de comandos.

Quando cada comando é executado ele deve retornar um contador de atualização que
pode ser adicionado a um array de contadores de atualização. A tentativa de colocar um
result set em um array de contadores de atualização causará um erro e fará com que
executeBatch gere uma exceção do tipo SQLException. Em outras palavras, apenas
comandos que retornam um contador de atualização (INSERT, DELETE, UPDATE, CREATE
TABLE, DROP TABLE, ALTER TABLE, etc) podem ser executados como um batch com o
comando executeBatch.

Se nenhuma SQLException foi gerada, você sabe que não ocorreram problemas de acesso
ao banco de dados e que todos os comandos produziram contadores de atualização. Se um
dos comandos não pôde ser executado por alguma outra razão, o método executeBatch
gera uma exceção do tipo BatchUpdateException. Além das informações que todas as
exceções possuem, esta exceção contém um array com os contadores de atualização para
os comandos que executaram com sucesso antes da exceção ser gerada. Como os
contadores de atualização estão na mesma ordem dos comandos que os produziram, você
pode saber quantos comandos foram executados com sucesso e quais são eles.

BatchUpdateException é derivada de SQLException. Isto significa que você pode utilizar


todos os métodos disponíveis para um objeto do tipo SQLException. O código abaixo
imprime informações de SQLException e os contadores de atualização contidos em um
objeto do tipo BatchUpdateException. Como getUpdateCounts retorna um array de int, é
utilizado um for loop para imprimir cada um dos contadores de atualização.

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 98
_______________________________________________________________________________________
try
{ // Faz algumas atualizações
}
catch (BatchUpdateException b)
{ System.err.println(“SQLException: “ +b.getMessage());
System.err.println(“SQLState: “ +b.getSQLState());
System.err.println(“Message: “ +b.getMessage());
System.err.println(“Fabricante do SGBD: “ +b.getErrorCode());
System.err.println(“Contadores de Atualização: “);
int [] contadoresDeAtualizacao = b.getUpdateCounts();
for (int i = 0; i < contadoresDeAtualizacao.length; i++)
{ System.err.print(contadoresDeAtualizacao [i] + “ “);
}
}

_______________________________________________________________________________________
Apostila de Java – Tópicos Avançados 99

Você também pode gostar