Nota: Esta seção foi escrita pelo tradutor, não fazendo parte do manual original.
Este exemplo serve para testar a segurança das threads no driver JDBC, usando tanto a API proprietária quanto a API padrão. É fornecido junto com a distribuição do código fonte do PostgreSQL, no arquivo src/interfaces/jdbc/example/threadsafe.java. Além da tradução, foram introduzidas pequenas alterações com relação ao código original.
No Fedora Core 2 foi utilizado "export CLASSPATH=/usr/share/java/pg74.1jdbc3.jar:." para executar o programa a partir da linha de comando, e "/usr/share/java/pg74.1jdbc3.jar" na configuração de Bibliotecas do Usuário do BlueJ para o programa ser executado nesta IDE.
import java.io.*; import java.sql.*; // Embora não seja comum no código do usuário, será // utilizada a API LargeObject neste exemplo. import org.postgresql.largeobject.*; /* * Este exemplo testa a segurança de thread do driver. * * Isto é feito executando várias comandos, em threads diferentes. * Cada thread possui o seu próprio objeto Statement, o que é (na * minha compreensão da especificação do jdbc) o requerimento mínimo. * * Foi utilizada a imagem "/usr/share/backgrounds/images/dragonfly.jpg" * do Fedora Core 2 para fazer o teste. Deve ser substituída por outra * caso esta imagem não exista no sistema. * */ public class ThreadSafe { Connection db; // Conexão com o servidor de banco de dados Statement st; // Declaração para executar os comandos String strImagem = "/usr/share/backgrounds/images/dragonfly.jpg"; public void go() throws ClassNotFoundException, FileNotFoundException, IOException, SQLException { // Banco de dados, usuário e senha igual a "teste" // CREATE USER teste WITH password 'teste'; // CREATE DATABASE teste WITH OWNER teste ENCODING 'LATIN1'; String url = "jdbc:postgresql://localhost/teste?charSet=LATIN1"; String usr = "teste"; String pwd = "teste"; // Carregar o driver Class.forName("org.postgresql.Driver"); // Conectar com o servidor de banco de dados System.out.println("Conectando ao banco de dados\nURL = " + url); db = DriverManager.getConnection(url, usr, pwd); System.out.println("Conectado...Criando a declaração"); st = db.createStatement(); // Limpar o banco de dados (no caso de uma falha anterior) e inicializar cleanup(); // Como são utilizados LargeObjects, devem ser utilizadas Transações db.setAutoCommit(false); // Executar os testes usando os métodos do JDBC, e depois do LargeObjects doexample(); // Limpar o banco de dados cleanup(); // Fechar a conexão System.out.println("Fechando a conexão"); st.close(); db.close(); } /* * Remover a tabela (caso exista). Nenhum erro é mostrado. */ public void cleanup() { try { st.executeUpdate("drop table tbl_basica1"); } catch (Exception ex) { // Ignorar todos os erros } try { st.executeUpdate("drop table tbl_basica2"); } catch (Exception ex) { // Ignorar todos os erros } } /* * Executar o exemplo */ public void doexample() throws SQLException { System.out.println("\n---------------------------------------" + "\nEste teste executa três Threads." + "\nDuas simplesmente inserem dados na" + "\ntabela e depois realizam uma consulta.\n" + "\nEnquanto estas duas threads executam," + "\numa terceira thread executa carregando" + "\ne depois lendo dados de um LargeObject.\n" + "\nSe tudo der certo será executado sem" + "\nnenhum erro, e o driver é Thread Safe.\n" + "\nPorque utilizar LargeObject?" + "\nPorque ambos utilizam a conexão de rede," + "\ne se o bloqueio no fluxo não for feito" + "\nde forma correta o servidor ficará" + "\nconfuso!" + "\n---------------------------------------"); thread3 thread3 = null; try { // Criar duas threads novas Thread thread0 = Thread.currentThread(); Thread thread1 = new thread1(db); Thread thread2 = new thread2(db); thread3 = new thread3(db); // Executar e aguardar por elas thread1.start(); thread2.start(); thread3.start(); // Fazer uma pausa temporária no objeto thread em execução, // para permitir que as outras threads executem. System.out.println("Aguardando a execução das threads"); while (thread1.isAlive() || thread2.isAlive() || thread3.isAlive()) Thread.yield(); } finally { // Limpar após thread3 (o finally garante que é executado // mesmo ocorrendo uma excessão dentro do bloco try { }) if (thread3 != null) thread3.cleanup(); } System.out.println("Nenhuma excessão ocorreu. Isto é um bom sinal,\n" + "porque significa que estamos tão seguros para\n" + "thread quanto podemos conseguir."); } // Esta é a primeira thread. É igual ao teste básico class thread1 extends Thread { Connection c; Statement st; public thread1(Connection c) throws SQLException { this.c = c; st = c.createStatement(); } public void run() { try { System.out.println("Thread 1 executando..."); // Criar a tabela que armazena os dados st.executeUpdate("create table tbl_basica1 (a int2, b int2)"); // Inserir alguns dados utilizando Statement st.executeUpdate("insert into tbl_basica1 values (1,1)"); st.executeUpdate("insert into tbl_basica1 values (2,1)"); st.executeUpdate("insert into tbl_basica1 values (3,1)"); // Utilizar PreparedStatement para fazer muitas inserções PreparedStatement ps = db.prepareStatement("insert into tbl_basica1 values (?,?)"); for (int i = 2; i < 2000; i++) { ps.setInt(1, 4); // "coluna a" = 4 ps.setInt(2, i); // "coluna b" = i ps.executeUpdate(); // porque insert não retorna dados if ((i % 50) == 0) DriverManager.println( "Thread 1 executou " + i + " inserções"); } ps.close(); // Sempre fechar ao terminar // Consultar a tabela DriverManager.println("Thread 1 realizando a consulta"); ResultSet rs = st.executeQuery("select a, b from tbl_basica1"); int cnt = 0; if (rs != null) { // Percorrer o conjunto de resultados mostrando os valores. // Observe que é necessário chamar .next() // antes de ler qualquer resultado. while (rs.next()) { int a = rs.getInt("a"); // Nome da coluna int b = rs.getInt(2); // Número da coluna //System.out.println(" a="+a+" b="+b); cnt++; } rs.close(); // é necessário fechar ao terminar } DriverManager.println("Thread 1 leu " + cnt + " linhas"); // O método cleanup() remove a tabela. System.out.println("Thread 1 terminou"); } catch (SQLException se) { System.err.println("Thread 1: " + se.toString()); se.printStackTrace(); System.exit(1); } } } // Esta é a segunda thread. É semelhante ao teste básico e a thread1, // exceto por utilizar outra tabela. class thread2 extends Thread { Connection c; Statement st; public thread2(Connection c) throws SQLException { this.c = c; st = c.createStatement(); } public void run() { try { System.out.println("Thread 2 executando..."); // Criar a tabela que armazena os dados st.executeUpdate("create table tbl_basica2 (a int2, b int2)"); // Utilizar PreparedStatement para fazer muitas inserções PreparedStatement ps = db.prepareStatement("insert into tbl_basica2 values (?,?)"); for (int i = 2; i < 2000; i++) { ps.setInt(1, 4); // "coluna a" = 4 ps.setInt(2, i); // "coluna b" = i ps.executeUpdate(); // porque insert não retorna dados // c.commit(); if ((i % 50) == 0) DriverManager.println( "Thread 2 executou " + i + " inserções"); } ps.close(); // Sempre fechar ao terminar // Consultar a tabela DriverManager.println("Thread 2 realizando a consulta"); ResultSet rs = st.executeQuery( "select * from tbl_basica2 where b>1"); int cnt = 0; if (rs != null) { // Descobrir os números das colunas. // // É melhor fazer neste ponto, porque os métodos chamados // passando os nomes das colunas realizam internamente esta // chamada toda toda vez que são chamados. Isto realmente // melhora o desempenho em consultas grandes. // int col_a = rs.findColumn("a"); int col_b = rs.findColumn("b"); // Percorrer o conjunto de resultados mostrando os valores. // Repetindo, é necessário chamar .next() // antes de ler qualquer resultado. while (rs.next()) { int a = rs.getInt(col_a); // Número da coluna int b = rs.getInt(col_b); // Número da coluna //System.out.println(" a="+a+" b="+b); cnt++; } rs.close(); // é necessário fechar o resultado ao terminar } DriverManager.println("Thread 2 leu " + cnt + " linhas"); // O método cleanup() remove a tabela. System.out.println("Thread 2 terminou"); } catch (SQLException se) { System.err.println("Thread 2: " + se.toString()); se.printStackTrace(); System.exit(1); } } } // Esta é a terceira thread. Carrega e lê um LargeObject // utilizando a API LargeObject do PostgreSQL. // // A finalidade é testar se FastPath funciona junto com // consultas JDBC normais. class thread3 extends Thread { Connection c; Statement st; LargeObjectManager lom; LargeObject lo; int oid; public thread3(Connection c) throws SQLException { this.c = c; //st = c.createStatement(); // Criar o BLOB lom = ((org.postgresql.PGConnection)c).getLargeObjectAPI(); oid = lom.create(); System.out.println("Thread 3 criou o BLOB com oid " + oid); } public void run() { try { System.out.println("Thread 3 executando..."); DriverManager.println( "Thread 3: Carregando dados no BLOB " + oid); lo = lom.open(oid); FileInputStream fis = new FileInputStream(strImagem); // Utilizar um buffer pequeno para dar chance às outras threads byte buf[] = new byte[128]; int rc, bc = 1, bs = 0; while ((rc = fis.read(buf)) > 0) { DriverManager.println("Thread 3 leu o bloco " + bc + " " + bs + " bytes"); lo.write(buf, 0, rc); bc++; bs += rc; } lo.close(); fis.close(); DriverManager.println("Thread 3: Lendo o BLOB " + oid); lo = lom.open(oid); bc = 0; while (buf.length > 0) { buf = lo.read(buf.length); if (buf.length > 0) { String s = new String(buf); bc++; DriverManager.println("Thread 3 bloco " + bc); DriverManager.println("Bloco " + bc + " obteve " + s); } } lo.close(); System.out.println("Thread 3 terminou"); } catch (Exception se) { System.err.println("Thread 3: " + se.toString()); se.printStackTrace(); System.exit(1); } } public void cleanup() throws SQLException { if (lom != null && oid != 0) { System.out.println("Thread 3: Removendo BLOB com oid=" + oid); lom.delete(oid); } } } public static void main(String args[]) { System.out.println( "PostgreSQL - Teste de Segurança de Thread v6.4 rev 1\n"); try { ThreadSafe threadsafe = new ThreadSafe(); threadsafe.go(); } catch (Exception ex) { System.err.println("Exceção capturada.\n" + ex); ex.printStackTrace(); } } }