JDBC Tópicos avançados de acesso a base de dados relacional A gestão de instruções SQL em JDBC 1 : proposta de uma arquitectura para evitar a manipulação de código SQL embutido em código JAVA. A arquitectura tem como objectivos proporcionar: • a modificação da string de uma query no momento da compilação, mas sem recorrer à concatenação de strings; • a modificação de uma query sem modificar o código java; • a separação da camada de execução de instruções SQL do código Java da aplicação. As instruções SQL devem ser definidas num ficheiro de “propriedades” 2. A cada uma é dado um nome adequado. O nome é a chave e a string (query) é o valor de uma propriedade. A query pode conter variáveis “where”, como varNome no exemplo seguinte: # Sample ResourceBundle properties file query1= SELECT * from pessoa query2= SELECT * from pessoa where nome='Maria' query3= SELECT * from pessoa where nome='varNome' varNome será substituída em run-time. Do lado da aplicação Java, a estrutura de dados que guarda a atribuição de valores a estas variáveis é uma Hashtable. 1 Baseado em [Chitaguppi,2005], disponível em http://java.about.com/library/weekly/uc_querycache1.htm Ver em http://java.sun.com/docs/books/tutorial/essential/attributes/properties.html 2 A arquitectura é exemplificada através de 3 classes: 1. Teste_Acesso_BD.java - Classe para testar o acesso a uma BD relacional com recurso a um ficheiro de texto do tipo '.properties' que guarda todos os comandos SQL, o que permite separar realmente o código JAVA do código SQL. Esta classe funciona com duas outras auxiliares, SQLCache.java e QueryResult.java. 2. SQLCache.java - Classe que obtêm instruções SQL guardadas num ficheiro de propriedades e que as manipula, por exemplo para verificar a correcção de uma query. 3. QueryResult.java - Classe auxiliar para tratar os resultados de uma query. Guarda o resultado de um ResultSet numa tabela, representada por um Vector que integra matrizes do tipo DataElement. Teste_Acesso_BD.java import java.sql.*; import java.util.*; /** * Classe para testar o acesso a uma BD relacional * com recurso a um ficheiro de texto do tipo '.properties' * que guarda todos os comandos SQL, o que permite separar * realmente o código JAVA do código SQL */ public class Teste_Acesso_BD { Connection connect; Statement stmt; // instância de SQLCache guardará as strings que representam comandos SQL SQLCache cache; /** * Construtor que carrega as intruções SQL do ficheiro de properties * para o atributo 'cache' (instância de SQLCache) */ public Teste_Acesso_BD() { cache = new SQLCache(); cache.loadSQLStatements(); } /** * Carrega o driver MySQL e abre a Connection em localhost, sem utilizar password. */ public void carregaDriverEAbreConnection(){ try { try { Class.forName("com.mysql.jdbc.Driver").newInstance(); } catch (Exception ex) { ex.printStackTrace(); } connect = DriverManager.getConnection( "jdbc:mysql://localhost:3306/test?"+ "user=root&password=password"); } catch (SQLException ex) { System.out.println("SQLException: " + ex.getMessage()); System.out.println("SQLState: " + ex.getSQLState()); System.out.println("VendorError: " + ex.getErrorCode()); ex.printStackTrace(); } } /** ** Abre um Statement. */ public void abreStatement(){ try { stmt = connect.createStatement(); } catch(Exception e){ e.printStackTrace(); } } /** * Fecha um Statement. */ public void fechaStatement(){ try { stmt.close(); } catch(Exception e){ e.printStackTrace(); } } /** * Fecha uma Connection. */ public void fechaConnection(){ try { connect.close(); } catch(Exception e){ e.printStackTrace(); } } /** * Método que executa uma query com recurso às classes * auxiliares SQLCache e QueryResult. */ public QueryResult executeSelect( String qryName, Hashtable params) throws Exception { // instância de QueryResult guardará o resultado da query QueryResult result = null; try { String query = cache.compileQuery(qryName,params); ResultSet rs = stmt.executeQuery(query); result = new QueryResult(); result.populateData(rs); } catch(Exception e){ e.printStackTrace(); } return result; } /** * O método main testa a execução de uma query com parâmetros. */ public static void main(String args[]){ } } Teste_Acesso_BD testeBD = new Teste_Acesso_BD(); testeBD.carregaDriverEAbreConnection(); testeBD.abreStatement(); Hashtable params = new Hashtable(); params.put("varNome", "Antonio"); String qryName = new String("query3"); QueryResult qr; try { qr = testeBD.executeSelect(qryName, params); System.out.println(qr.tabela()); } catch(Exception ex){ ex.printStackTrace();} testeBD.fechaStatement(); testeBD.fechaConnection(); /** * O ficheiro sql.properties tem o seguinte conteúdo para teste: * * * # Sample ResourceBundle properties file * query1= SELECT * from pessoa * query2= SELECT * from pessoa where nome='Maria' * query3= SELECT * from pessoa where nome='varNome' * * ( Para conhecer os ficheiros de propriedades ver em * http://java.sun.com/docs/books/tutorial/i18n/resbundle/propfile.html ) * */ SQLCache.java import java.util.*; import java.io.*; /** * Classe que obtêm instruções SQL guardadas num ficheiro de propriedades * e que as manipula, por exemplo para verificar a correcção de uma query. */ public class SQLCache { /** * As instruções SQL obtidas a partir do ficheiro ficarão guardadas * num objecto do tipo Properties * (ver http://java.sun.com/j2se/1.4.2/docs/api/java/util/Properties.html *e http://java.sun.com/docs/books/tutorial/essential/attributes/properties.html ) */ private Properties props = new Properties(); /** * Nome do ficheiro de propriedades */ private static final String SQL_FILE = "..\\..\\..\\..\\sql.properties"; // várias hipóteses de acesso ao ficheiro sql.properties: // quando se usa uma classe normal: //private static final String SQL_FILE = "..\\..\\..\\..\ \sql.properties"; // quando se acede à base a partir de um servlet: // 1ª hipótese: //private static final String SQL_FILE = "..\\..\\sql.properties"; // e copiar à mão o ficheiro sql.properties para "C:\Program Files\netbeans-5.5\enterprise3\apache-tomcat-5.5.17" // 2ª hipótese: // posicionar na raiz de C: desde "C:\Program Files\netbeans-5.5\enterprise3\apache-tomcat-5.5.17\webapps" // e depois percorrer o Path até ao ficheiro de propriedades // private static final String SQL_FILE = "..\\..\\..\\..\\..\\..\\Users\\Jorge\\Desktop\\ISCTE\\PRede\ \PRede_2007-08\\Testes\\TesteMySQL\\sql.properties"; /** * Carrega instruções SQL a partir do ficheiro de propriedades e guarda-as num objecto Properties */ public void loadSQLStatements() { try { // carrega as instruções SQL a partir de um ficheiro de propriedades InputStream in = getClass().getClassLoader().getResourceAsStream(SQL_FILE); System.out.println("in: " + in); props.load(in); System.out.println("props" + props); in.close(); } catch(Exception e) { e.printStackTrace(); } } /** * Método chamado antes de executar uma query, para substituir * as variáveis na query pelos parâmetros devidos. * Os parâmetros estão guardados numa Hastable * (ver http://java.sun.com/j2se/1.4.2/docs/api/java/util/Hashtable.html) */ public String compileQuery(String qryName, Hashtable params) throws Exception { String qry = getSQLStatement(qryName); for(Enumeration enu = params.keys(); enu.hasMoreElements();) { String key = (String) enu.nextElement(); String value = (String) params.get(key); System.out.println(key); qry = qry.replaceAll(key,value); } return qry; } } /** * Método que verifica se uma instrução SQL existe */ public String getSQLStatement(String sqlName) throws Exception { String sqlStm = (String) props.get(sqlName); if(sqlStm == null) { throw new Exception("A instrução SQL não foi encontrada."); } else { return sqlStm; } } QueryResult.java import java.util.*; import java.sql.*; /** * Classe auxiliar para tratar os resultados de uma query. * Guarda o resultado de um ResultSet numa tabela, * representada por um Vector que integra matrizes do tipo DataElement. */ public class QueryResult { private Vector tabela = new Vector(); /** * Classe que guarda o resultado de um ResultSet numa tabela, * representada por um Vector que integra matrizes do tipo DataElement */ public void populateData(ResultSet rs) throws SQLException { ResultSetMetaData metaData = rs.getMetaData(); int colCnt = metaData.getColumnCount(); while(rs.next()) { DataElement[] obj = new DataElement[colCnt]; for(int i=0;i<colCnt;i++) { DataElement element = new DataElement(); element.dataType = metaData.getColumnType(i+1); element.dataTypeName = metaData.getColumnTypeName(i+1); element.colName = metaData.getColumnName(i+1); element.value = rs.getObject(i+1); System.out.println(element.value); obj[i] = element; } tabela.addElement(obj); } } /** * Inspector para a tabela dos dados retirados do ResultSet */ Vector tabela(){ return tabela; } /** * Classe embutida que representa cada elemento que é retirado do ResulSet, * qualquer que seja o seu tipo */ private class DataElement { int dataType; String dataTypeName; Object value; String colName; } }