Pergunta

O projeto atual não usa Hibernate (por várias razões) e nós estamos usando suporte SimpleJdbc da Primavera para realizar todas as operações de nossa DB. Temos uma classe de utilitário que abstrai todas as operações CRUD, mas operações complexas são realizadas utilizando consultas SQL personalizado.

Atualmente nossas consultas são armazenados como constantes de corda dentro das classes de serviço a si mesmos e são alimentados a um utilitário para ser executado pelo SimpleJdbcTemplate. Estamos em um impasse onde a legibilidade tem de ser equilibrada com manutenção. código SQL dentro da própria classe é mais sustentável, uma vez que reside com o código que o utiliza. Por outro lado, se nós armazenamos essas consultas em um arquivo externo (plana ou XML) o próprio SQL seria mais legível, em comparação com sintaxe da cadeia java escapou.

Já alguém encontrou um problema semelhante? O que é um bom equilíbrio? Onde você guarda o seu SQL personalizada no seu projeto?

A consulta de exemplo é a seguinte:

private static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
"    FROM PRODUCT_SKU T \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.ID) as minimum_id_for_price \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID, S.SALE_PRICE \n" +
"    ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.SALE_PRICE) as minimum_price_for_product \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID \n" +
"    ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) \n" +
"WHERE T.PRODUCT_ID IN (:productIds)";

Esta é a forma como ele se pareceria em um arquivo SQL plana:

--namedQuery: FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS
FROM PRODUCT_SKU T 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.ID) as minimum_id_for_price 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.SALE_PRICE) as minimum_price_for_product 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID 
) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
WHERE T.PRODUCT_ID IN (:productIds)
Foi útil?

Solução

Eu armazenado SQL que ambas as cadeias dentro de uma classe Java e como arquivos separados que foram carregados em tempo de execução. Eu muito preferido este último por duas razões. Primeiro, o código é mais legível por uma larga margem. Em segundo lugar, é mais fácil para testar o SQL em isolamento, se você armazená-lo em um arquivo separado. Além disso, era mais fácil conseguir alguém melhor do que eu em SQL para me ajudar com as minhas consultas quando estavam em arquivos separados.

Outras dicas

Eu tenho que correr para isso também, atualmente, pela mesma razão - um projeto baseado em jdbc Primavera. A minha experiência é que, enquanto não é ótimo ter a lógica no próprio SQL, não há realmente nenhum lugar melhor para ele, e colocar no código do aplicativo é mais lento do que ter o db fazê-lo e não necessariamente mais claro.

maiores armadilhas que eu vi com isso é onde o SQL começa a proliferar em todo o projeto, com múltiplas variações. "Get A, B, C a partir de Foo". "Get A, B, C, E a partir de Foo", etc.etc. Este tipo de proliferação é especialmente provável que o projeto atinge uma certa massa crítica - pode não parecer um problema com 10 procedimentos, mas quando há 500 consultas espalhados por todo o projeto torna-se muito mais difícil de descobrir se você já tiver feito algo . Abstraindo as operações CRUD básicos coloca muito à frente do jogo aqui.

A melhor solução, AFAIK, é ser rigorosamente consistente com o SQL codificado - comentou, testado e em um local consistente. Nosso projeto tem 50 de linha de consultas SQL descomentei. O que eles querem dizer? Quem sabe?

Como para consultas em arquivos externos, eu não vejo o que esta compra - Você ainda é tão dependente do SQL, e com excepção da melhoria estética (questionável) de manter o sql fora das aulas, suas classes ainda são tão dependentes do sql -.eg você geralmente recursos separados para obter a flexibilidade de plug-in recursos de substituição, mas você não poderia conecte consultas SQL substituição, que mudaria o significado semântico da classe ou não funciona em todos. Portanto, é um código-limpeza ilusória.

Uma solução bastante radical seria usar Groovy para especificar suas consultas. Groovy tem suporte em nível de idioma para cordas de várias linhas e interpolação string (divertidamente conhecido como gstrings).

Por exemplo, utilizando Groovy, a consulta que você especificou acima seria simplesmente:

class Queries
    private static final String PRODUCT_IDS_PARAM = ":productIds"

    public static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
    """    FROM PRODUCT_SKU T 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.ID) as minimum_id_for_price 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
        ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.SALE_PRICE) as minimum_price_for_product 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID 
        ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
    WHERE T.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) """

Você pode acessar essa classe a partir do código Java, assim como você seria como se estivesse definido em Java, por exemplo.

String query = QueryFactory.FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS;

Eu vou admitir que a adição de Groovy para o seu classpath apenas para fazer suas consultas SQL olhar mais agradável é um pouco de uma "marreta para quebrar uma noz" solução, mas se você estiver usando Spring, já há um justo chance que você Groovy em seu caminho de classe.

Além disso, há provavelmente um monte de outros lugares em seu projeto onde você poderia usar Groovy (em vez de Java) para melhorar o seu código, (especialmente agora que Groovy é de propriedade da Primavera). Exemplos incluem escrever casos de teste, ou substituir Java beans com feijão Groovy.

Nós usamos procedimentos armazenados. Isso é bom para nós, porque usar o Oracle Grão Fino Access. Isso nos permite restringir um usuário de ver um relatório particular ou resultados de pesquisa, limitando seu acesso ao procedimento pertinente. Também nos dá um pouco de um aumento de desempenho.

Por que não usar procedimentos armazenados em vez de consultas codificação de disco rígidos? Armazenadas Procs vai aumentar capacidade de manutenção e proporcionar mais segurança para coisas como ataques SQL Interjection.

Na classe é provavelmente o melhor - Se as consultas são longas o suficiente para que o escape é um problema que você provavelmente vai querer estar a olhar para ambos procedimentos armazenados ou simplificar a consulta .

Uma opção é usar iBatis . É bastante leve em comparação com um ORM full-blown como Hibernate, mas fornece um meio para armazenar suas consultas SQL fora seus arquivos .java

Nós armazenamos todo o nosso SQL em uma classe como um bando de cordas finais estáticos. Para facilitar a leitura que espalhá-lo sobre algumas linhas concatenadas usando +. Além disso, eu não tenho certeza se você precisa escapar qualquer coisa -. "Strings" está entre aspas simples no SQL

Nós tivemos um projeto onde usamos a abordagem exato que você está, a não ser que exteriorizada cada consulta para um arquivo de texto separado. Cada arquivo foi lido em (uma vez) utilizando framework ResourceLoader da Primavera e a aplicação trabalhado através de uma interface como este:

public interface SqlResourceLoader {
    String loadSql(String resourcePath);
}

A vantagem com isso foi que ter o SQL em um formato escapou-un permitido para depuração mais fácil - basta ler o arquivo em uma ferramenta de consulta. Uma vez que você tem mais do que algumas consultas de complexidade moderada, lidar com un / escapando de / para o código durante o teste e depuração (particularmente para ajuste) foi inestimável.

Nós também tivemos que suportar um par de bancos de dados diferentes, por isso permitido para trocar a plataforma com mais facilidade.

Com base na questão como você explicou, não há escolha real aqui, exceto para mantê-lo no código, e se livrar dos / n caracteres em todos os lugares. Esta é a única coisa que você mencionou como afetando a legibilidade e eles são absolutamente desnecessário.

A menos que você tiver outros problemas com ele que é no código de seu problema é facilmente resolvido.

Eu imagino que armazenar uma consulta em um arquivo externo, e depois ter o aplicativo lê-lo quando necessário apresenta uma falha de segurança enorme.

O que acontece se um gênio do mal tem acesso a esse arquivo e muda sua consulta?

Por exemplo, alterações

 select a from A_TABLE;

A

 drop table A_TABLE;

ou

 update T_ACCOUNT set amount = 1000000

Além disso, ele acrescenta a complexidade de ter que mantain duas coisas: Código Java e arquivos de consulta SQL.

EDIT: Sim, você pode alterar suas consultas sem recompilar seu aplicativo. Eu não vejo o grande negócio lá. Você pode recompilar as classes que detêm / criar sqlQueries única, se o projeto é muito grande. Além disso, se a documentação é pobre, talvez você ia acabar mudando o arquivo incorreto, e que vai se transformar em uma enorme bug em silêncio. Nenhuma exceção ou erro códigos serão lançados, e quando você percebe o que você fez, pode ser tarde demais.

Uma abordagem diferente seria ter algum tipo de SQLQueryFactory, bem documentado, e com métodos que retornam uma consulta SQL que você deseja usar.

Por exemplo

public String findCheapest (String tableName){

      //returns query.
}

O que você precisa aqui é SQLJ que é um SQL Java-Preprocessor. Infelizmente, aparentemente, nunca decolou, embora eu tenha visto alguns IBM e da Oracle implementações. Mas eles estão bastante desatualizado.

Se eu fosse você e tinha muitas e muitas consultas sobre o sistema, eu teria guardado-los em um arquivo separado e carregá-los em tempo de execução .

De minha experiência, é melhor deixar as instruções SQL no código não separá-los torna as coisas mais sustentável (como anotações vs. configuração arquivos), mas agora eu tenho um debate com um membro da equipe sobre isso.

Eu tenho um pequeno programa que o acesso prancheta e fuga / texto simples unscape com strings literais Java.

Eu tê-lo como um atalho no "início rápido" barra de ferramentas de modo a única coisa que precisa fazer é

Ctrl+C, Click jar, Ctrl+V

De qualquer quando eu quero executar o meu "código" na ferramenta SQL, ou vice-versa.

Então, eu costumo ter algo como isto:

String query = 
    "SELECT a.fieldOne, b.fieldTwo \n"+
    "FROM  TABLE_A a, TABLE b \n"+ 
    "... etc. etc. etc";


logger.info("Executing  " + query  );

PreparedStatement pstmt = connection.prepareStatement( query );
....etc.

Que se transforma em:

    SELECT a.fieldOne, b.fieldTwo 
    FROM  TABLE_A a, TABLE b
    ... etc. etc. etc

Ou porque em alguns projetos que não posso criar arquivos separados, ou porque eu sou paranóico e eu sinto alguns bits vai ser inserido / excluídas durante a leitura do arquivo externo (geralmente um invisível \ n que faz

select a,b,c 
from 

para

select a,b,cfrom 

IntelliJ IDEA faz o mesmo para você automagicamente, mas só a partir de simples ao código.

Aqui está uma versão antiga I recuperado. Um pouco quebrado e não manipula?.

Deixe-me saber se alguém melhora-lo.

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Transforms a plain string from the clipboard into a Java 
 * String literal and viceversa.
 * @author <a href="http://stackoverflow.com/users/20654/oscar-reyes">Oscar Reyes</a>
 */
public class ClipUtil{

    public static void main( String [] args ) 
                                throws UnsupportedFlavorException,
                                                     IOException {

        // Get clipboard
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Clipboard clipboard = toolkit.getSystemClipboard();

        // get current content.
        Transferable transferable = clipboard.getContents( new Object() ); 
        String s = ( String ) transferable.getTransferData( 
                                                DataFlavor.stringFlavor );

        // process the content
        String result = process( s );

        // set the result
        StringSelection ss = new StringSelection( result );
        clipboard.setContents( ss, ss );

    }
    /**
     * Transforms the given string into a Java string literal 
     * if it represents plain text and viceversa. 
     */
    private static String process( String  s ){
        if( s.matches( "(?s)^\\s*\\\".*\\\"\\s*;$" ) ) {
            return    s.replaceAll("\\\\n\\\"\\s*[+]\n\\s*\\\"","\n")
                       .replaceAll("^\\s*\\\"","")
                       .replaceAll("\\\"\\s*;$","");
        }else{
            return     s.replaceAll("\n","\\\\n\\\" +\n \\\" ")
                        .replaceAll("^"," \\\"")
                        .replaceAll("$"," \\\";");
        }
    }
}

Uma vez que você já usa o Primavera por que não colocar o SQL no arquivo de configuração Primavera e DI-lo para a classe DAO? Essa é uma maneira simples de exteriorizar a string SQL.

HTH Tom

Eu prefiro a opção externa. I suportar vários projectos e encontrá-lo muito mais difícil para apoiar SQL interna porque você tem que compilar e reimplantar cada vez que você quiser fazer uma pequena alteração no SQL. Ter o SQL em um arquivo externo permite que você faça mudanças grandes e pequenas facilmente com menos risco. Se você está apenas a edição do SQL, não há nenhuma chance de colocar em um erro de digitação que quebra a classe.

Para Java, eu uso a classe propriedades que representa o SQL em um arquivo .properties, que permite que você passe as consultas SQL ao redor se você quiser voltar a usar as consultas ao invés de ler o arquivo em várias vezes.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top