web-dev-qa-db-fra.com

Comment créer des chaînes multilignes pour des requêtes SQL lisibles, maintenables et rapides?

J'ai quelques commandes SQL que j'essaie de trouver la meilleure façon de les avoir dans le code afin que:
1. Ils sont très lisibles
2. Ils seraient faciles à mettre à jour
3. Ils ne seront pas des frais généraux en raison de la construction de nombreuses chaînes.

J'ai quelque chose comme le suivant qui ne s'est pas avéré trop bon.

    public class SQLHandler {  
      private static final String CUSTOMER_TABLE = "customer_table";  
      private static final String CUSTOMER_ID = "id";  
      private static final String CUSTOMER_FIRST_NAME = "first_name";    
      private static final String CUSTOMER_LAST_NAME = "last_name"; 
      private static final String CUSTOMER_TELEPHONE = "customer_telephone";  
      private static final String REP_ID = "customer_representative_id";    


      private static final String REP_TABLE = "representative_table";  
      private static final String REP_ID = "id";  
      private static final String REP_FIRST_NAME = "first_name";    
      private static final String LAST_LAST_NAME = "last_name"; 
      private static final String DEPT_ID = "rep_dep_id";  

      public static ArrayList<Representatives> getRepresentatives(int customerId) {  
       StringBuilder sb = new StringBuilder();
       sb.append("SELECT")   
       .append(REP_TABLE).append(".").append(REP_ID)  
       .append(",")   
       .append(REP_TABLE)  
       .append(".")   
       .append(REP_FIRST_NAME).append(" FROM")
       .append(CUSTOMER_TABLE).append(" JOIN ").append(REP_TABLE)   
       .append("ON").append(REP_TABLE).append(".")     
       .append(REP_ID).append("=").append(CUSTOMER_TABLE)    
      .append(".").append(REP_ID)  
      .append(" AND")  
      .append(CUSTOMER_TABLE).append(".").append(CUSTOMER_ID)   
      .append("=").append(String.valueOf(customerId));  

    // do query  
    }   
} 

Comme vous pouvez le voir, aucun des (3) n'est atteint.
Je ne peux pas facilement mettre à jour la requête et si je la revoyais, je ne me souviendrais pas exactement de quoi il s'agissait.
Comment puis-je améliorer cela? (Impossible de le formater correctement dans la publication)

4
Jim

Ma suggestion est:

public class SQLHandler {  

    private String sql=
    "SELECT \n"+
    "   c.customer_representative_id, \n"+
    "   c.first_name \n"+
    "FROM \n"+
    "   customer_table c JOIN representative_table r ON \n"+
    "   r.customer_representative_id=c.id \n"+
    "WHERE \n"+
    "   customer_table.id=? \n";

    // methods and so on...

}

Le "\n" les caractères à la fin de chaque ligne permettent une belle impression à des fins de débogage.

Utilisateur ultérieur un PreparedStatement pour remplacer "?" empêchant ainsi l'injection sql:

PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(customed_id);
ResultSet rs = ps.executeQuery();
/// etc...

ne autre alternative est la lecture de la chaîne SQL à partir d'un fichier qui peut être fait avec la classe Properties. Un tel fichier peut avoir plusieurs propriétés, chacune étant une requête SQL différente ou d'autres types de valeurs de configuration que vous souhaitez.

Exemple de fichier (le " \" permet d'autoriser une valeur multiligne pour une propriété mais elle est lue comme une seule ligne)

SQL1=\
SELECT \
    c.customer_representative_id, \
    c.first_name \
FROM \
    customer_table c JOIN representative_table r ON \
    r.customer_representative_id=c.id \
WHERE \
    customer_table.id=? \

Qui peut être chargé comme ceci:

import Java.util.Properties;
///...
Properties prop = new Properties();
prop.load(new FileInputStream("sql_queries.conf"));
String sql = prop.getProperty("SQL1");

Ensuite, utilisez un PreparedStatement ...

Suggestion de bonus:

Créez des vues avec les jointures que vous effectuerez fréquemment, afin que les requêtes soient plus simples dans le code ou dans les fichiers de propriétés, comme:

SELECT
    v.customer_representative_id,
    v.first_name,
        //other columns
FROM
    my_reps_view v
WHERE
    v.customer_table.id=?
5
Tulains Córdova

Littéraux à cordes brutes

Des travaux sont en cours pour introduire chaîne brute littéraux dans le langage Java. Parallèlement à cela vient le chaînes multi-lignes souhaité dans cette Question.

JEP 326

Voir JEP 326: littéraux de chaîne bruts .

Extraits:

Ajoutez des littéraux de chaîne bruts au langage de programmation Java. Un littéral de chaîne brut peut s'étendre sur plusieurs lignes de code source et n'interprète pas les séquences d'échappement, telles que\n ou les échappements Unicode, de la forme\uXXXX…

fournir des chaînes ciblées pour des grammaires autres que Java,…

fournir des chaînes qui s'étendent sur plusieurs lignes de source sans fournir d'indicateurs spéciaux pour les nouvelles lignes.

L'imbrication SQL est l'un des exemples de cas d'utilisation donnés dans le JEP.

Mise à jour du statut par Brian Goetz

Voir Littéraux de chaîne bruts - où nous en sommes, comment nous sommes arrivés ici par Brian Goetz, 2018-03-27.

Ils se penchent vers l'utilisation d'un nombre quelconque de backticks comme début-arrêt de chaîne littérale brute délimiteurs . Un seul backtick serait le plus courant, mais utilisez un certain nombre d'entre eux si la valeur du texte contient des backticks. Donc pas échappant nécessaire du tout.

Extraits:

… Les choses se sont largement stabilisées avec les littéraux à cordes brutes…

    String ss = `a multi-
     line-string`;

À propos, vous pouvez trouver l'article, Nouvelles méthodes sur Java Strings With JDK 11 par Dustin Marx pour être intéressant. Il couvre quelques nouvelles méthodes de coupe de chaîne Unicode-savvy ajoutées à Java 11. Celles-ci peuvent être liées à la fonctionnalité éventuelle de littéraux de chaîne brute.

3
Basil Bourque

Par souci d'exhaustivité, vous pouvez ajouter une étape supplémentaire à la compilation pour permettre d'injecter la valeur du commentaire javadoc comme en utilisant projet de chaîne multiligne et avoir quelque chose comme:

/**
 * SELECT 'Hello world'
 * FROM dual
 */
@Multiline
public static String getHelloWorld;

Il remplacera l'annotation au moment de la compilation par

public static String getHelloWorld= "SELECT 'Hello world'\nFROM dual";

Et juste au cas où vous avez des inquiétudes, je l'ai utilisé avec un gestionnaire d'entité JPA comme

javax.persistence.Query qry = em.createQuery(QueryRepository.getHelloWorld); 

QueryRepository est un conventionnel classe pour les constantes .

Tutoriel sur le développement et l'utilisation: http://www.adrianwalker.org/2011/12/Java-multiline-string.html

1
Ruslan López

Si vous ne voulez pas la surcharge d'un style "fluide" Java constructeur de requêtes (comme jOOQ ou quelque chose), j'ai utilisé des chaînes statiques pour les requêtes formatées comme suit:

public class SQLHandler {
    private static final String REPRESENTATIVES_QUERY =
        " SELECT" +
        "   representative_table.id," +
        "   representative_table.first_name," +
        " FROM" +
        "   customer_table" +
        " JOIN representative_table ON" +
        "   representative_table.id = customer_table.id" +
        " WHERE" +
        "   customer_table.id = ?";

    public static ArrayList<Representatives> getRepresentatives(int customerId) {
        // do query
    }
}
0
not22

Voir: syntaxe sur plusieurs lignes

Il prend également en charge les variables dans une chaîne multiligne, par exemple:

String name="zzg";
String lines = ""/**~!{
    SELECT * 
        FROM user
    WHERE name="$name"
}*/;
System.out.println(lines);

Production:

SELECT * 
    FROM user
    WHERE name="zzg"
0
Sanjiv

Pour les grandes requêtes compliquées, j'utilise généralement un modèle de chaîne. De cette façon, quand je veux changer la requête, c'est juste un gros .sql fichier que je peux éditer avec une coloration syntaxique pour démarrer. Il y a beaucoup de bibliothèques de modèles de chaînes différentes, je vais laisser cela généralisé.

Il est certainement plus lent de traiter le modèle que de concaténer un tas de chaînes, mais parfois cela en vaut la peine pour la facilité de maintenance.

Modèle de chaîne

SELECT *
FROM table1
JOIN table2 ON table1.this = table2.that
WHERE table1.foo = ?
AND table2.baz = ?

Java

StringTemplate template = new MyQueryTemplate().
String query = template.process();
PreparedStatement statement = connection.prepareStatement(query);
statement.setString(1, "bar");
statement.setString(2, "boo");
ResultSet resultSet = statement.executeQuery();
0
Matt