Conexão com bancos de dados usando JDBC

Conexão com bancos de dados - JDBC

O banco de dados é onde persistimos (armazenamos) os dados que pertencem ao nosso sistema. A maioria dos bancos de dados comerciais hoje em dia é do tipo relacional e derivam de uma estrutura diferente da orientada a objetos.

Para executarmos o manuseio de informações em um banco de dados, devemos fazer uso de sub-linguagens de banco de dados (Database Sub Language – DSL), voltadas para as operações do banco de dados. Geralmente, as sub-linguagens de dados são compostas da combinação de recursos para a definição de dados (Data Definition Language – DDL) e recursos específicos para a manipulação de dados (Data Manipulation Language – DML). A conhecida linguagem de consulta SQL (Structured Query Language) é uma destas linguagens que fornece suporte tanto a DDL como a DML.

Para efetivarmos a conexão de um programa desenvolvido em Java com uma base de dados qualquer, a linguagem Java implementa um conceito de ponte, que implementa todas as funcionalidades que um banco de dados padrão deve nos fornecer.

Uso do JDBC na aplicação Java.

No desenho acima podemos notar o relacionamento direto da JDBC com o banco de dados, porém este relacionamento depende também da extensão desta ponte que é representada pela implementação JDBC escolhida. Esta implementação depende do banco de dados com o qual queremos nos comunicar e a ela damos o nome de Driver. Para gerenciar estes drivers de conexão, a linguagem Java possui um gerente de drivers chamado java.sql.DriverManager.

O driver do MySQL pode ser adicionado através das Propriedades do Projeto, na aba Biblioteca utilize o botão Adicionar Biblioteca e selecione MySQL JDBC Driver.

caso você queria colocar o driver de outro banco de dados é preciso clicar no botão Adicionar JAR/Pasta e selecionar o arquivo .jar que representa o drive desejado (Obs: no site do fabricante é possível obter este driver).

Adicionar biblioteca no NetBeans.
Adicionar driver do MySQL no NetBeans.

Para criarmos uma conexão com o banco de dados primeiramente é necessário informarmos qual driver de conexão o DriverManager deve utilizar.

Feita essa disponibilização do arquivo de driver, na programação se faz necessário registrar o driver do banco de dados no sistema. Para isso, basta carregá-lo através do método Class.forName(). Esse método abre uma classe que se registra com o DriverManager.getConnection().

A partir da classe DriverManager, podemos utilizar um método chamado getConnection, com o qual poderemos nos conectar a uma determinada base de dados utilizando uma String padrão de conexão.

package material.jdbc;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * Classe utilizada para conectar e desconectar do banco de dados.
 */
public class Conexao {
  public static void main(String[] args) {
    Conexao conexao = new Conexao();
    Connection conn = conexao.conectar();
    conexao.desconectar(conn);
  }

  public Connection conectar() {
    Connection conn = null;
    try {
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");
      System.out.println("Conectou no banco de dados.");
    } catch (SQLException ex) {
      System.out.println("Erro: Não conseguiu conectar no BD.");
    } catch (ClassNotFoundException ex) {
      System.out.println("Erro: Não encontrou o driver do BD.");
    }

    return conn;
  }

  public void desconectar(Connection conn) {
    try {
      if(conn != null && !conn.isClosed()) {
        conn.close();
        System.out.println("Desconectou do banco de dados.");
      }
    } catch (SQLException ex) {
      System.out.println("Não conseguiu desconectar do BD.");
    }
  }
}

Na linha 20 estamos carregando o driver do banco de dados MySQL com.mysql.jdbc.Driver.

Na linha 21, utilizamos o método getConnection da classe DriverManager para criar uma conexão com o banco de dados armazenado em um objeto do tipo java.sql.Connection.

O método getConnection() recebe três parâmetros: 1˚ url de conexão com o banco de dados, 2˚ usuário e 3˚ senha.

Para fazer a conexão com o banco de dados precisamos tratar algumas exceções que podem ocorrer:

Na linha 23, tratamos a exceção java.sql.SQLException que é lançada caso não consiga criar uma conexão com o Banco de Dados.

Na linha 25, tratamos a exceção java.lang.ClassNotFoundException que é lançada caso não consiga carregar o driver do banco de dados.

Depois de utilizar a conexão com o banco de dados é muito importante que liberemos esta conexão, ou seja, precisamos encerrar a conexão com o banco de dados.

Na linha 32 temos o método desconectar que recebe uma Connection como parâmetro, dentro deste método estamos verificando se a conexão com o banco ainda não foi encerrada para depois encerra-la. Quando tentamos encerrar uma conexão com o banco também pode ocorrer alguma exceção para isso na linha 38 estamos tratando caso ocorra alguma java.sql.SQLException.

Quando executamos esta classe, temos a seguinte saída no console:

Conectou no banco de dados.
Desconectou do banco de dados.

Consulta de dados

Uma vez conectado, podemos então solicitar a execução de comandos SQL, para que tenhamos acesso a este tipo de chamada ao banco de dados é necessário criarmos um java.sql.Statement.

O Statement é o objeto que servirá de instrumento para a execução de comandos SQL, porém é importante ressaltar que o intuito de uma consulta a um banco de dados é o de receber uma informação, logo, é fundamental que essa resposta seja armazenada em uma estrutura. Para isso podemos utilizar a classe java.sql.ResultSet que deve receber o retorno do método executeQuery que é responsável por executar a consulta.

Exemplo:

package material.jdbc;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * Classe utilizada para demonstrar a consulta ao banco de dados.
 */
public class Consulta {
  public static void main(String[] args) {
    Consulta consultaBD = new Consulta();
    consultaBD.consulta();
  }

  public void consulta() {
    Conexao conexao = new Conexao();
    Connection conn = conexao.conectar();
    try {
      String consulta = "SELECT * FROM PESSOA WHERE NOME like 'A%'";

      Statement stm = conn.createStatement();
      ResultSet resultado = stm.executeQuery(consulta);

      while(resultado.next()) {
        System.out.print(resultado.getLong("ID"));
        System.out.print(" - " + resultado.getString("NOME"));
        System.out.print(" - " + resultado.getInt("IDADE") + "\n");
      }
    } catch (SQLException ex) {
      System.out.println("Não conseguiu consultar os dados de Pessoa.");
    } finally {
      conexao.desconectar(conn);
    }
  }
}

Quando executamos essas classe, temos a seguinte saída no console:

Conectou no banco de dados.
1 - Ana - 30
5 - Amanda - 23
Desconectou do banco de dados.

Note pelo exemplo anterior que o método responsável por executar a consulta na base de dados foi o executeQuery na linha 24. Observe também a forma como o ResultSet foi percorrido na linha 26.

A classe ResultSet oferece o método next() para percorrer a resposta dada pelo banco de dados. Caso nossa consulta acima lista houvesse retornado 4 linhas, por padrão o ResultSet estaria com seu ponteiro posicionado na posição -1, logo, antes de ler a primeira linha de resposta de sua consulta é necessário a chamada a este método next() para que o ponteiro passe para a primeira posição e possa resgatar as informações desejadas. O método next() retorna true, caso ainda existam novas linhas na resposta e false quando não tem mais registros para percorrer.

É importante ressaltar que apenas um único objeto ResultSet pode ser aberto por vez por Statement ao mesmo tempo.

Para resgatar as informações desejadas, a classe ResultSet oferece métodos do tipo get para todos os tipos de dados primitivos, exceto char. Todos estes métodos devem receber como parâmetro o nome da coluna de resposta, assim como no exemplo anterior, ou o número correspondente a posição da coluna retornada.

Manipulação de dados

Podemos utilizar a classe Statement para guardar (persistir) ou atualizar as informações na base de dados, utilizando o método execute.

package material.jdbc;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * Classe utilizada para demonstrar como adicionar ou atualizar dados
 *  no banco de dados.
 */
public class Manipulacao {
  public static void main(String[] args) {
    Manipulacao manipulacao = new Manipulacao();
    manipulacao.inserir();
    manipulacao.atualizar();
  }

  public void inserir() {
    Conexao conexao = new Conexao();
    Connection conn = conexao.conectar();
    try {
      String adicionar = "INSERT INTO PESSOA (NOME, IDADE) VALUES ('Rafael', 25)";

      Statement stm = conn.createStatement();
      stm.execute(adicionar);
      System.out.println("Adicionou a pessoa Rafael no BD.");
    } catch (SQLException ex) {
      System.out.println("Não conseguiu adicionar uma pessoa no BD.");
    } finally {
      conexao.desconectar(conn);
    }
  }

  public void atualizar() {
    Conexao conexao = new Conexao();
    Connection conn = conexao.conectar();
    try {
      String atualizar = "UPDATE PESSOA SET NOME = 'Cristiano' WHERE NOME = 'Rafael'";

      Statement stm = conn.createStatement();
      stm.executeUpdate(atualizar);
      System.out.println("Atualizou o nome de Rafael para Cristiano.");
    } catch (SQLException ex) {
      System.out.println("Não conseguiu atualizar uma pessoa no BD.");
    } finally {
      conexao.desconectar(conn);
    }
  }
}

Na linha 24, pedimos para o Statement adicionar um novo registro na tabela PESSOA do banco de dados.

Na linha 40, pedimos para o Statement atualizar um registro da tabela PESSOA do banco de dados.

Note que para adicionar ou atualizar as informações no banco de dados, podemos utilizar o método execute.

A classe Statement possui o método execute para adicionar ou atualizar um registro no banco de dados e o método executeUpdate para atualizar as informações no banco de dados, a diferença é que este método retorna um inteiro com a quantidade de registros que foram alterados.

Após a conclusão das operações e leitura ou de manipulação de dados, é importante a chamada ao método close(), tanto da classe Statement como da classe Connection, para que a conexão com o banco de dados seja finalizada.

Relação de algumas bases de dados

| Banco          | Driver                          | String de Conexão                                   |
| -------------- | ------------------------------- | --------------------------------------------------- |
| Microsoft ODBC | sun.jdbc.odbc.JdbcOdbcDriver    | jdbc:odbc:<<nome da base>>                          |
| MySQL          | com.mysql.jdbc.Driver           | jdbc:mysql://<<ipDoBanco>>/<<baseDeDados>>          |
| Oracle         | oracle.jdbc.driver.OracleDriver | jdbc:oracle:thin:@<<ipDoBanco>>:1521:<<nomeDaBase>> |

Exemplo de aplicação C.R.U.D. (Create, Read, Update, Delete)

Neste exemplo vamos, passo a passo, criar uma aplicação completa de acesso a uma base de dados utilizando JDBC.

Para esta aplicação, iremos criar um sistema de cadastro de veículos. Para tal, criamos a classe Carro:

package material.jdbc.exemplo;

/**
 * Classe utilizada para representar um carro.
 */
public class Carro {
  private String placa;
  private String modelo;
  private Double potencia;

  public String getModelo() {
    return modelo;
  }

  public void setModelo(String modelo) {
    this.modelo = modelo;
  }

  public String getPlaca() {
    return placa;
  }

  public void setPlaca(String placa) {
    this.placa = placa;
  }

  public Double getPotencia() {
    return potencia;
  }

  public void setPotencia(Double potencia) {
    this.potencia = potencia;
  }
}

Agora que já temos modelado a nossa classe principal, devemos definir como nosso programa irá interagir com a base de dados.

Para estabelecermos a conexão com a base de dados de uma maneira mais simples, faremos uso da classe Conexao:

package material.jdbc.exemplo;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * Classe utilizada para executar as ações sobre o banco de dados.
 */
public class Conexao {
  private Connection conn = null;
  private Statement stm = null;
  private ResultSet rs = null;

  private Connection conectar() {
    try {
      String usuario = "root";
      String senha = "root";
      String ipDoBanco = "localhost";
      String nomeDoBanco = "carro";
      String stringDeConexao = "jdbc:mysql://" + ipDoBanco + "/" + nomeDoBanco;
      Class.forName("com.mysql.jdbc.Driver");
      conn = DriverManager.getConnection(stringDeConexao, usuario, senha);
      System.out.println("Conectou no banco de dados.");
    } catch (SQLException ex) {
      System.out.println("Erro: Não conseguiu conectar no BD.");
    } catch (ClassNotFoundException ex) {
      System.out.println("Erro: Não encontrou o driver do BD.");
    }

    return conn;
  }

  public ResultSet executarConsulta(String consulta) {
    conn = conectar();
    try {
      stm = conn.createStatement();
      rs = stm.executeQuery(consulta);
    } catch (SQLException ex) {
      System.out.println("Não conseguiu executar a consulta\n" + consulta);
      //Caso ocorra algum erro desconecta do banco de dados.
      desconectar();
    }

    return rs;
  }

  public boolean executarDML(String dml) {
    boolean ok = false;

    conn = conectar();
    try {
      stm = conn.createStatement();
      stm.execute(dml);
      ok = true;
    } catch (SQLException ex) {
      System.out.println("Nao conseguiu executar o DML\n" + dml);
    } finally {
      desconectar();
    }

    return ok;
  }

  public void desconectar() {
    fecharResultSet(this.rs);
    fecharStatement(this.stm);
    fecharConnection(this.conn);
  }

  public void fecharConnection(Connection conn) {
    try {
      if(conn != null && !conn.isClosed()) {
        conn.close();
        System.out.println("Desconectou do banco de dados.");
      }
    } catch (SQLException ex) {
      System.out.println("Nao conseguiu desconectar do BD.");
    }
  }
    
  public void fecharStatement(Statement stm) {
    try {
      if(stm != null && !stm.isClosed()) {
        stm.close();
      }
    } catch (SQLException ex) {
      System.out.println("Erro ao fechar o procedimento de consulta.");
    }
  }

  public void fecharResultSet(ResultSet resultado) {
    try {
      if(resultado != null && !resultado.isClosed()) {
        resultado.close();
      }
    } catch (SQLException ex) {
      System.out.println("Erro ao fechar o resultado da consulta.");
    }
  }
}

Utilizaremos um padrão de projeto chamado DAO (Data Acess Object). O DAO deve saber buscar os dados do banco e converter em objetos para ser usado pela sua aplicação. Semelhantemente, deve saber como pegar os objetos, converter em instruções SQL e mandar para o banco de dados. Desta forma conseguimos distinguir fortemente a modelagem do sistema da modelagem de dados e das regras de negócio.

Geralmente, temos um DAO para cada objeto do domínio do sistema, ou seja, para nosso exemplo criaremos uma classe CarroDAO com as quatro operações básicas, definidas por seus métodos.

package material.jdbc.exemplo;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Classe utilizada para executar as operações no banco de dados,
 * que envolvem o Carro.
 */
public class CarroDAO {
  public void incluir(Carro carro) {
    String incluir = "INSERT INTO CARRO VALUES ('" + carro.getPlaca() + "', '"
        + carro.getModelo() + "', "
        + carro.getPotencia() + ")";
        
    Conexao conexao = new Conexao();
    conexao.executarDML(incluir);
  }

  public Carro consultarPorPlaca(String placa) {
    Conexao conexao = new Conexao();
    Carro carro = null;
    try {
      String consulta = "SELECT * FROM CARRO WHERE PLACA = '" + placa + "'";
      ResultSet rs = conexao.executarConsulta(consulta);

      if(rs.next()) {
        carro = new Carro();
        carro.setModelo(rs.getString("MODELO"));
        carro.setPlaca(rs.getString("PLACA"));
        carro.setPotencia(rs.getDouble("POTENCIA"));
      }
    } catch (SQLException ex) {
      System.out.println("Nao conseguiu consultar os dados do Caminhao.");
    } finally {
      conexao.desconectar();
    }

    return carro;
  }

  public void alterarPorPlaca(Carro carro) {
    String update = "UPDATE CARRO SET MODELO = '" + carro.getModelo() + 
    "', POTENCIA = " + carro.getPotencia() + "WHERE PLACA = '" + 
    carro.getPlaca() + "'";
    Conexao conexao = new Conexao();
    conexao.executarDML(update);
  }

  public void excluir(Carro carro) {
    String delete = "DELETE FROM CARRO WHERE PLACA='" + carro.getPlaca() + "'";

    Conexao conexao = new Conexao();
    conexao.executarDML(delete);
  }
}

Para que a classe acima não apresente nenhum erro de compilação, é necessário que você acrescente a biblioteca (arquivo .jar) correspondente ao seu banco de dados. Para o nosso exemplo, devemos importar o arquivo correspondente ao banco de dados MySQL.

Observe que nosso método incluir() apenas recebe o objeto Carro a ser inserido na base de dados e internamente faz toda a operação relacionada ao banco de dados. Desta forma, conseguimos isolar toda esta interação com a base de dados do restante do código, tornando-o mais simples de se realizar qualquer tipo de manutenção.

O método consultarPorPlaca recebe apenas a placa de um carro (imagine que esta informação é uma chave da tabela e que não devemos ter mais de um carro com a mesma placa) e que retorna um objeto do tipo Carro com todos os seus atributos devidamente alimentados.

O método alterarPorPlaca() recebe um objeto Carro e a partir de sua placa faz a atualização nos atributos placa e potencia.

O método excluir() recebe o Carro como parâmetro e o apaga da base de dados,

Para testarmos nosso sistema, crie um programa semelhante ao abaixo:

package material.jdbc.exemplo;

import java.util.Scanner;

/**
 * Classe utilizada para testar o CRUD de Carro.
 */
public class TestarCarro {
  public static void main(String[] args) {
    CarroDAO carroDAO = new CarroDAO();
    char opcao = ' ';
    do {
      Carro carro = null;
      opcao = menu();
      switch(opcao) {
        case 'I':
          carro = coletarDados();
          carroDAO.incluir(carro);
          break;
        case 'E':
          String placaExcluir = consultarPlaca();
          carro = carroDAO.consultarPorPlaca(placaExcluir);
          carroDAO.excluir(carro);
          break;
        case 'A':
          carro = coletarDados();
          carroDAO.alterarPorPlaca(carro);
          break;
        case 'C':
          String placaConsultar = consultarPlaca();
          carro = carroDAO.consultarPorPlaca(placaConsultar);
          break;
      }
      mostrarDadosCarro(carro);
    } while(opcao != 'S');
  }

  public static char menu() {
    Scanner s = new Scanner(System.in);
    char opcao = ' ';
        
    System.out.println("Escolha a sua opcao: ");
    System.out.println("\t(I)ncluir");
    System.out.println("\t(E)xcluir");
    System.out.println("\t(A)lterar");
    System.out.println("\t(C)onsultar");
    System.out.println("\t(S)air");
    System.out.print("\nOpcao: ");
    opcao = s.nextLine().toUpperCase().charAt(0);

    return opcao;
  }

  public static String consultarPlaca() {
    Scanner s = new Scanner(System.in);
    System.out.print("Digite a placa do carro: ");
    return s.nextLine();
  }

  public static Carro coletarDados() {
    Scanner s = new Scanner(System.in);
    Carro carro = new Carro();

    System.out.print("Digite a placa do carro: ");
    carro.setPlaca(s.nextLine());
    System.out.print("Digite o modelo do carro: ");
    carro.setModelo(s.nextLine());
    System.out.print("Digite a potencia do carro: ");
    carro.setPotencia(s.nextDouble());

    return carro;
  }

  public static void mostrarDadosCarro(Carro carro) {
    if(carro != null) {
      System.out.println("\n############### DADOS DO CARRO #################");
      System.out.println("PLACA: " + carro.getPlaca());
      System.out.println("MODELO: " + carro.getModelo());
      System.out.println("POTENCIA DO MOTOR: " + carro.getPotencia());
      System.out.println("############### DADOS DO CARRO #################\n");
    }
  }
}

Ao executarmos a classe TestarCarro, temos a seguinte saída no console:

Escolha a sua opcao:
	(I)ncluir
	(E)xcluir
	(A)lterar
	(C)onsultar
	(S)air

Opcao:

A console fica aguardando até que digitemos alguma opção, primeiro vamos criar um novo carro para isto vamos entrar com a opção I:

Digite a placa do carro: abc-1234
Digite o modelo do carro: X3
Digite a potencia do carro: 450
Conectou no banco de dados.
Desconectou do banco de dados.

############### DADOS DO CARRO #################
PLACA: abc-1234
MODELO: X3
POTENCIA DO MOTOR: 450.0
############### DADOS DO CARRO #################

Escolha a sua opcao:
	(I)ncluir
	(E)xcluir
	(A)lterar
	(C)onsultar
	(S)air

Opcao:

Se consultarmos todos os carros cadastrados no banco de dados aparecerá o carro que acabamos de criar:

mysql> select * from carro;
+----------+--------+----------+
| placa    | modelo | potencia |
+----------+--------+----------+
| abc-1234 | X3     |   450.00 |
+----------+--------+----------+
1 row in set <0.05 sec>

Agora vamos utilizar a opção C para consultar um carro pela placa abc-1234:

Opcao: C
Digite a placa do carro: abc-1234
Conectou no banco de dados.
Desconectou do banco de dados.

############### DADOS DO CARRO #################
PLACA: abc-1234
MODELO: X3
POTENCIA DO MOTOR: 450.0
############### DADOS DO CARRO #################

Escolha a sua opcao:
	(I)ncluir
	(E)xcluir
	(A)lterar
	(C)onsultar
	(S)air

Opcao:

Agora vamos utilizar a opção A para alterar as informações de modelo e potencia:

Opcao: A
Digite a placa do carro: abc-1234
Digite o modelo do carro: X4
Digite a potencia do carro: 350
Conectou no banco de dados.
Desconectou do banco de dados.

############### DADOS DO CARRO #################
PLACA: abc-1234
MODELO: X4
POTENCIA DO MOTOR: 350.0
############### DADOS DO CARRO #################

Escolha a sua opcao:
	(I)ncluir
	(E)xcluir
	(A)lterar
	(C)onsultar
	(S)air

Opcao:

Se consultarmos todos os carros cadastrados no banco de dados aparecerá o carro que acabamos de alterar:

mysql> select * from carro;
+----------+--------+----------+
| placa    | modelo | potencia |
+----------+--------+----------+
| abc-1234 | X4     |   350.00 |
+----------+--------+----------+
1 row in set <0.00 sec>

Agora vamos utilizar a opção E para apagar o carro do banco de dados.

Opcao: E
Digite a placa do carro: abc-1234
Conectou no banco de dados.
Desconectou do banco de dados.
Conectou no banco de dados.
Desconectou do banco de dados.

############### DADOS DO CARRO #################
PLACA: abc-1234
MODELO: X4
POTENCIA DO MOTOR: 350.0
############### DADOS DO CARRO #################

Escolha a sua opcao:
	(I)ncluir
	(E)xcluir
	(A)lterar
	(C)onsultar
	(S)air

Opcao:

Se consultarmos todos os carros, não teremos nenhum registro no banco de dados:

mysql> select * from carro;
Empty set <0.00 sec>

Conteúdos relacionados