31.13. Exemplo de utilização de Thread no JDBC

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();
        }
    }
}
SourceForge.net Logo