Java e SQL: retornar null ou lançar exceção?

Este é outro assunto debatido, mas desta vez estou procurando apenas as respostas simples e documentadas. O cenário :

Vamos supor o seguinte método:

public static Hashtable getSomeDogs(String colName, String colValue) { Hashtable result = new Hashtable(); StringBuffer sql = null; Dog dog = null; ResultSet rs = null; try { sql = new StringBuffer(); sql.append("SELECT * FROM ").append("dogs_table"); sql.append(" WHERE ").append(colName).append("='"); sql.append(colValue).append("'"); rs = executeQuery(sql.toString()); while (rs.next()) { dog= new Dog(); //...initialize the dog from the current resultSet row result.put(new Long(dog.getId()), dog); } } catch (Exception e) { createErrorMsg(e); result = null; //i wonder.... } finally { closeResultSet(rs); //this method tests for null rs and other stuff when closing the rs. } return result; } 

Questões :

1. Como você sugere melhorar esta técnica de devolver alguns cães, com algum atributo?

2. rs.next () retornará false para um ResultSet nulo ou gerará uma exceção, como esta:

String str = nulo; System.out.println (str.toString ());

3. E se, ao inicializar um object dog a partir da linha atual do ResultSet, algo de ruim acontecer, como: falha na conexão, o valor incompatível foi passado para o setter da propriedade dog, etc? Eu posso ter 10 elementos no hashtable agora ou nenhum (primeira linha). Qual será a próxima ação: a) return null hashtable; b) devolver o resultado hashtable, como está neste estágio; c) lançar uma exceção: qual será o tipo de exceção aqui?

4. Acho que todos concordarão com isso: nada de ruim acontece, não há linhas no interrogatório, um valor nulo será retornado. No entanto, @ Thorbjørn Ravn Andersen disse aqui que eu deveria retornar um NullObject em vez de um valor nulo. Eu imagino o que seja isto.

5. Tenho notado pessoas e grupos de pessoas dizendo que se deve dividir o aplicativo em camadas, ou níveis de algum tipo. Considerando o exemplo acima, quais camadas estão aqui, exceto as que eu posso imaginar:

Layer1 :: Database layer, onde as ações são executadas: este método.

Layer2 :: ??? : alguma camada onde o novo object Dog é construído: meu object Dog.

Camada3 ::? : alguma camada onde eu pretendo fazer algo com a coleção de cães: camada de GUI, principalmente, ou uma sub-camada de interface do usuário.

Seguindo o stream do aplicativo, se uma exceção ocorrer na primeira camada, qual é a melhor solução para isso? Meus pensamentos: pegue a exceção, registre a exceção, retorne algum valor. Essa é a melhor prática?

Manny obrigado por suas respostas, estou ansioso para ver o que outras pessoas pensam sobre esses assuntos.

Você faz cinco perguntas

1. Como você sugere melhorar esta técnica de devolver alguns cães, com algum atributo?

Vários, na verdade.

  • Seu método é estático – isso não é terrível, mas leva a você usando outro “executeQuery” estático, que cheira a Singleton para mim …
  • A class “Dogs” viola uma prática de nomeação OO – os substantivos no plural não são bons nomes de classs, a menos que uma instância da class possua uma coleção de coisas – e parece que Dogs é na verdade “Dog”.
  • O HashTable é praticamente obsoleto. HashMap ou ConcurrentHashMap fornecem melhor desempenho.
  • Eu não consigo ver uma razão para criar a primeira parte de sua consulta com vários anexos – não é ruim, mas é menos legível do que poderia ser, então sql.append (“SELECT * FROM dogs_table WHERE”); faz um começo mais sensato se você estiver indo apenas para codificar as colunas selecionadas (*) e o nome da tabela (dogs_table) de qualquer maneira.

2. rs.next () retornará false para um ResultSet nulo ou gerará uma exceção

Isso não parece ser uma pergunta, mas sim, rs.next () retorna false assim que não há mais linhas para processar.

3. E se, ao inicializar um object dog da linha atual do ResultSet, algo ruim acontecer?

Se “algo ruim acontecer”, o que você faz a seguir depende de você e do seu design. Há a abordagem de perdão (retorne todas as linhas que você puder) e o implacável (lance uma exceção). Eu tendem a inclinar-se para a abordagem “implacável”, já que com a abordagem “perdoar”, os usuários não sabem que você não retornou todas as linhas que existem – apenas todas as que você obteve antes do erro. Mas pode haver casos para a abordagem do perdão.

4. Acho que todos concordarão com isso: nada de ruim acontece, não há linhas no interrogatório, um valor nulo será retornado.

Isso não é algo em que há uma resposta correta óbvia. Primeiro, não é o que está acontecendo no método como está escrito. Ele retornará um HashTable vazio (é o que significa um “object nulo”). Em segundo lugar, null nem sempre é a resposta no caso “nenhum resultado encontrado”.

Eu vi null, mas também vi uma variável de resultado vazia. Eu afirmo que ambos são abordagens corretas, mas eu prefiro a variável de resultado vazia. No entanto, é sempre melhor ser consistente, portanto, escolha um método para retornar “sem resultados” e mantenha-o.

5. Tenho notado pessoas e grupos de pessoas dizendo que se deve dividir o aplicativo em camadas, ou níveis de algum tipo.

Isso é mais difícil de responder do que os outros, sem ver o restante da sua inscrição.

Eu evitaria o seguinte

  sql.append("SELECT * FROM ").append("dogs_table"); sql.append(" WHERE ").append(colName).append("='"); sql.append(colValue).append("'"); 

e, em vez disso, use um PreparedStatement com seus methods de setString() parâmetro associados ( setString() ) etc. Isso evitará problemas com valores para colValue com cotações e ataques de injeção SQL (ou mais geralmente, colValue formando alguma syntax SQL).

Eu nunca retornaria um nulo se a coleção estivesse apenas vazia. Isso parece muito contra-intuitivo e completamente inesperado do ponto de vista do cliente.

Eu não recomendaria retornar um nulo em condições de erro, já que seu cliente precisa verificar isso explicitamente (e provavelmente esquecerei). Eu retornaria uma coleção vazia se necessário (ou pode ser análogo ao seu comentário sobre um object nulo), ou mais provavelmente lançaria uma exceção (dependendo das circunstâncias e da gravidade). A exceção é útil porque carregará algumas informações relacionadas ao erro encontrado. Null não lhe diz nada.

O que você deve fazer se encontrar um problema ao construir um object Dog ? Eu acho que isso depende de quão robusto e resiliente você quer que seu aplicativo seja. É um problema para retornar um subconjunto de Dog s, ou seria completamente catastrófico e você precisa relatar isso? Esse é um requisito de aplicativo (eu tive que atender a qualquer cenário no passado – melhor esforço ou tudo ou nada ).

Um par de observações. Eu usaria o HashMap em vez do antigo Hashtable (sincronizado para todos os accesss e, mais importante, não um Collection apropriado – se você tiver um Collection você pode passá-lo para qualquer outro método esperando qualquer Collection ), e StringBuilder por StringBuffer por razões similares . Não é um grande problema, mas vale a pena conhecer.

Padrão de object nulo é um padrão de design em que você sempre retorna um object para evitar NPE: s e qualquer verificação nula em seu código. No seu caso, isso significa que, em vez de retornar null , retorna uma Hashtable vazia.

O raciocínio é que, como é uma coleção e seu outro código acessará como tal, ela não será desfeita se você retornar uma coleção vazia; não será iterado, não conterá nada de surpreendente, não causará o lançamento de NPEs e outros enfeites.

Para ser preciso, um Objeto Nulo é uma implementação especial de uma class / interface que não faz absolutamente nada e, portanto, não tem efeitos colaterais de qualquer tipo. Devido à sua natureza de não ser null ele tornará seu código mais limpo desde quando você souber que sempre obterá um object de suas chamadas de método, não importa o que aconteça dentro do método, você não precisa nem procurar por nulos nem fazer código reagindo a eles! Como o Null Object não faz nada, você pode até tê-los como singletons simplesmente por aí e, assim, economizar memory fazendo isso.

Não crie consultas SQL concatenando cadeias de caracteres, como você está fazendo:

 sql = new StringBuffer(); sql.append("SELECT * FROM ").append("dogs_table"); sql.append(" WHERE ").append(colName).append("='"); sql.append(colValue).append("'"); 

Isso torna seu código vulnerável a um ataque de segurança conhecido, injeção de SQL . Em vez de fazer isso, use um PreparedStatement e defina os parâmetros chamando os methods apropriados set...() nele. Note que você só pode usar isso para definir valores de coluna, você não pode usar isso para construir dinamicamente um nome de coluna, como você está fazendo. Exemplo:

 PreparedStatement ps = connection.prepareStatement("SELECT * FROM dogs_table WHERE MYCOL=?"); ps.setString(1, colValue); rs = ps.executeQuery(); 

Se você usar um PreparedStatement , o driver JDBC automaticamente cuidará da fuga de determinados caracteres que possam estar presentes no colValue , para que os ataques de injeção SQL não funcionem mais.

Se ocorrer um erro, apresente uma exceção. Se não houver dados, retorne uma coleção vazia, não uma nula. (Além disso, geralmente você deve retornar o ‘Mapa’ mais genérico, não a implementação específica),

Você pode reduzir significativamente a quantidade de código JDBC padrão usando o Spring-JDBC em vez do antigo JDBC. Aqui está o mesmo método reescrito usando Spring-JDBC

 public static Hashtable getSomeDogs(String colName, String colValue) { StringBuffer sql = new StringBuffer(); sql.append("SELECT * FROM ").append("dogs_table"); sql.append(" WHERE ").append(colName).append("='"); sql.append(colValue).append("'"); Hashtable result = new Hashtable(); RowMapper mapper = new RowMapper() { public Object mapRow(ResultSet rs, int rowNum) throws SQLException { Dogs dog = new Dogs(); //...initialize the dog from the current resultSet row result.put(new Long(dog.getId()), dog); } }; (Hashtable) jdbcTemplate.queryForObject(sql, mapper); } 

spring cuida de:

  1. Iterando sobre o ResultSet
  2. Fechando o ResultSet
  3. Lidando com exceções consistentemente

Como outros já mencionaram, você deve usar um PreparedStatement para construir o SQL em vez de um String (ou StringBuffer). Se por algum motivo você não puder fazer isso, poderá melhorar a legibilidade da consulta construindo o SQL desta forma:

  String sql = "SELECT * FROM dogs_table " + "WHERE " + "colName" + " = '" + colValue + "'"; 
    Intereting Posts