Le moyen le plus propre de créer une chaîne SQL en Java


107

Je veux créer une chaîne SQL pour manipuler la base de données (mises à jour, suppressions, inserts, sélections, ce genre de chose) - au lieu de la terrible méthode de concat de chaîne utilisant des millions de "+" et des guillemets qui sont au mieux illisibles - là doit être une meilleure façon.

J'ai pensé à utiliser MessageFormat - mais il est censé être utilisé pour les messages des utilisateurs, même si je pense que cela ferait un travail raisonnable - mais je suppose qu'il devrait y avoir quelque chose de plus aligné sur les opérations de type SQL dans les bibliothèques java sql.

Groovy serait-il bon?

Réponses:


76

Tout d'abord, pensez à utiliser les paramètres de requête dans les instructions préparées:

PreparedStatement stm = c.prepareStatement("UPDATE user_table SET name=? WHERE id=?");
stm.setString(1, "the name");
stm.setInt(2, 345);
stm.executeUpdate();

L'autre chose qui peut être faite est de conserver toutes les requêtes dans le fichier de propriétés. Par exemple dans un fichier queries.properties peut placer la requête ci-dessus:

update_query=UPDATE user_table SET name=? WHERE id=?

Puis à l'aide d'une simple classe utilitaire:

public class Queries {

    private static final String propFileName = "queries.properties";
    private static Properties props;

    public static Properties getQueries() throws SQLException {
        InputStream is = 
            Queries.class.getResourceAsStream("/" + propFileName);
        if (is == null){
            throw new SQLException("Unable to load property file: " + propFileName);
        }
        //singleton
        if(props == null){
            props = new Properties();
            try {
                props.load(is);
            } catch (IOException e) {
                throw new SQLException("Unable to load property file: " + propFileName + "\n" + e.getMessage());
            }           
        }
        return props;
    }

    public static String getQuery(String query) throws SQLException{
        return getQueries().getProperty(query);
    }

}

vous pouvez utiliser vos requêtes comme suit:

PreparedStatement stm = c.prepareStatement(Queries.getQuery("update_query"));

C'est une solution assez simple, mais qui fonctionne bien.


1
Je préfère utiliser un constructeur SQL propre comme celui-ci: mentabean.soliveirajr.com
TraderJoeChicago

2
Puis-je vous suggérer de mettre l' InputStreamintérieur de l' if (props == null)instruction afin de ne pas l'instancier lorsqu'elle n'est pas nécessaire.
SyntaxRules

64

Pour du SQL arbitraire, utilisez jOOQ . jOOQ prend actuellement en charge SELECT, INSERT, UPDATE, DELETE, TRUNCATEet MERGE. Vous pouvez créer du SQL comme ceci:

String sql1 = DSL.using(SQLDialect.MYSQL)  
                 .select(A, B, C)
                 .from(MY_TABLE)
                 .where(A.equal(5))
                 .and(B.greaterThan(8))
                 .getSQL();

String sql2 = DSL.using(SQLDialect.MYSQL)  
                 .insertInto(MY_TABLE)
                 .values(A, 1)
                 .values(B, 2)
                 .getSQL();

String sql3 = DSL.using(SQLDialect.MYSQL)  
                 .update(MY_TABLE)
                 .set(A, 1)
                 .set(B, 2)
                 .where(C.greaterThan(5))
                 .getSQL();

Au lieu d'obtenir la chaîne SQL, vous pouvez également l'exécuter simplement en utilisant jOOQ. Voir

http://www.jooq.org

(Avertissement: je travaille pour l'entreprise derrière jOOQ)


cela ne serait-il pas dans de nombreux cas une mauvaise solution car vous ne pouvez pas laisser la base de données analyser au préalable l'instruction avec des valeurs différentes pour "5", "8", etc.? Je suppose que l'exécution avec jooq le résoudrait?
Vegard

@Vegard: Vous avez un contrôle total sur la façon dont jOOQ doit rendre les valeurs de liaison dans sa sortie SQL: jooq.org/doc/3.1/manual/sql-building/bind-values . En d'autres termes, vous pouvez choisir de rendre "?"ou de lier les valeurs en ligne.
Lukas Eder

oui, mais en ce qui concerne les moyens propres de construire SQL, ce serait un code un peu compliqué à mes yeux si vous n'utilisez pas JOOQ pour exécuter. dans cet exemple, vous définissez A sur 1, B sur 2, etc., mais vous devez le faire une fois de plus lorsque vous exécutez si vous n'exécutez pas avec JOOQ.
Vegard

1
@Vegard: Rien ne vous empêche de passer une variable à l'API jOOQ et de reconstruire l'instruction SQL. En outre, vous pouvez extraire les valeurs de liaison dans leur ordre en utilisant jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() , ou nommer les valeurs de liaison par leur nom en utilisant jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . Ma réponse contient juste un exemple très simpliste ... Je ne sais pas si cela répond à vos préoccupations, cependant?
Lukas Eder

2
C'est une solution coûteuse.
Trieuse

15

Une technologie à considérer est SQLJ - un moyen d'intégrer des instructions SQL directement dans Java. À titre d'exemple simple, vous pouvez avoir ce qui suit dans un fichier appelé TestQueries.sqlj:

public class TestQueries
{
    public String getUsername(int id)
    {
        String username;
        #sql
        {
            select username into :username
            from users
            where pkey = :id
        };
        return username;
    }
}

Il existe une étape de précompilation supplémentaire qui prend vos fichiers .sqlj et les traduit en Java pur - en bref, il recherche les blocs spéciaux délimités par

#sql
{
    ...
}

et les transforme en appels JDBC. L'utilisation de SQLJ présente plusieurs avantages clés:

  • fait abstraction complète de la couche JDBC - les programmeurs n'ont qu'à penser à Java et SQL
  • le traducteur peut être amené à vérifier vos requêtes de syntaxe, etc. par rapport à la base de données au moment de la compilation
  • possibilité de lier directement des variables Java dans les requêtes en utilisant le préfixe ":"

Il existe des implémentations du traducteur pour la plupart des principaux fournisseurs de bases de données, vous devriez donc pouvoir trouver facilement tout ce dont vous avez besoin.


Celui-ci est obsolète maintenant, selon wikipedia.
Zeus

1
Au moment de la rédaction de cet article (janvier 2016), SQLJ est qualifié sur Wikipédia de «obsolète» sans aucune référence. At-il été officiellement abandonné? Si tel est le cas, je vais coller un avertissement en haut de cette réponse.
Ashley Mercer

NB La technologie est toujours prise en charge par exemple dans la dernière version d'Oracle, 12c . J'admets que ce n'est pas la norme la plus moderne, mais cela fonctionne toujours et présente certains avantages (tels que la vérification à la compilation des requêtes par rapport à la base de données) qui ne sont pas disponibles dans d'autres systèmes.
Ashley Mercer

12

Je me demande si vous recherchez quelque chose comme Squiggle . Le jDBI est également très utile . Cela ne vous aidera pas avec les requêtes.


9

Je voudrais jeter un oeil à Spring JDBC . Je l'utilise chaque fois que j'ai besoin d'exécuter des SQL par programme. Exemple:

int countOfActorsNamedJoe
    = jdbcTemplate.queryForInt("select count(0) from t_actors where first_name = ?", new Object[]{"Joe"});

C'est vraiment génial pour tout type d'exécution SQL, en particulier pour les requêtes; il vous aidera à mapper les ensembles de résultats aux objets, sans ajouter la complexité d'un ORM complet.


comment puis-je obtenir une vraie requête SQL exécutée? Je veux l'enregistrer.
kodmanyagha

5

J'ai tendance à utiliser les paramètres JDBC nommés de Spring pour pouvoir écrire une chaîne standard comme "select * from blah where colX = ': someValue'"; Je pense que c'est assez lisible.

Une alternative serait de fournir la chaîne dans un fichier .sql séparé et de lire le contenu en utilisant une méthode utilitaire.

Oh, cela vaut également la peine de jeter un œil à Squill: https://squill.dev.java.net/docs/tutorial.html


Je suppose que vous voulez dire que vous utilisez BeanPropertySqlParameterSource? Je suis presque d'accord avec vous, la classe que je viens de mentionner est cool lorsque vous utilisez strictement des beans, mais sinon, je recommanderais d'utiliser ParameterizedRowMapper personnalisé pour construire des objets.
Esko

Pas assez. Vous pouvez utiliser n'importe quel SqlParameterSource avec des paramètres JDBC nommés. Cela convenait à mes besoins d'utiliser un MapSqlParameterSource, plutôt que la variété de haricots. Dans tous les cas, c'est une bonne solution. Les RowMappers, cependant, traitent de l'autre côté du puzzle SQL: transformer les ensembles de résultats en objets.
GaryF

4

Je seconde les recommandations pour l'utilisation d'un ORM comme Hibernate. Cependant, il y a certainement des situations où cela ne fonctionne pas, donc je vais profiter de l'occasion pour vanter certaines choses que j'ai aidé à écrire: SqlBuilder est une bibliothèque java pour construire dynamiquement des instructions SQL en utilisant le style "builder". c'est assez puissant et assez flexible.


4

J'ai travaillé sur une application de servlet Java qui a besoin de construire des instructions SQL très dynamiques à des fins de reporting ad hoc. La fonction de base de l'application est de nourrir un tas de paramètres de requête HTTP nommés dans une requête pré-codée et de générer un tableau de sortie bien formaté. J'ai utilisé Spring MVC et le framework d'injection de dépendances pour stocker toutes mes requêtes SQL dans des fichiers XML et les charger dans l'application de création de rapports, avec les informations de mise en forme du tableau. Finalement, les exigences en matière de rapports sont devenues plus compliquées que les capacités des cadres de mappage de paramètres existants et j'ai dû écrire les miennes. Ce fut un exercice de développement intéressant et a produit un cadre pour la cartographie des paramètres beaucoup plus robuste que tout ce que j'ai pu trouver.

Les nouveaux mappages de paramètres se présentaient comme tels:

select app.name as "App", 
       ${optional(" app.owner as "Owner", "):showOwner}
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = ${integer(0,50):serverId}
   and app.id in ${integerList(50):appId}
 group by app.name, ${optional(" app.owner, "):showOwner} sv.name
 order by app.name, sv.name

La beauté du cadre résultant était qu'il pouvait traiter les paramètres de requête HTTP directement dans la requête avec une vérification de type et une vérification de limite appropriées. Aucun mappage supplémentaire requis pour la validation d'entrée. Dans l'exemple de requête ci-dessus, le paramètre nommé serverId serait vérifié pour s'assurer qu'il pouvait être converti en un entier et qu'il était compris entre 0 et 50. Le paramètre appId serait traité comme un tableau d'entiers, avec une limite de longueur de 50. Si le champ showOwnerest présent et mis à "true", les bits de SQL entre guillemets seront ajoutés à la requête générée pour les mappages de champs optionnels. field Plusieurs autres mappages de types de paramètres sont disponibles, y compris des segments facultatifs de SQL avec d'autres mappages de paramètres. Il permet un mappage de requête aussi complexe que le développeur peut le proposer. Il a même des contrôles dans la configuration du rapport pour déterminer si une requête donnée aura les mappages finaux via un PreparedStatement ou simplement exécutée en tant que requête pré-construite.

Pour les exemples de valeurs de requête Http:

showOwner: true
serverId: 20
appId: 1,2,3,5,7,11,13

Cela produirait le SQL suivant:

select app.name as "App", 
       app.owner as "Owner", 
       sv.name as "Server", sum(act.trans_ct) as "Trans"
  from activity_records act, servers sv, applications app
 where act.server_id = sv.id
   and act.app_id = app.id
   and sv.id = 20
   and app.id in (1,2,3,5,7,11,13)
 group by app.name,  app.owner,  sv.name
 order by app.name, sv.name

Je pense vraiment que Spring ou Hibernate ou l'un de ces frameworks devrait offrir un mécanisme de mappage plus robuste qui vérifie les types, permet des types de données complexes comme les tableaux et d'autres fonctionnalités similaires. J'ai écrit mon moteur uniquement pour mes besoins, il n'est pas tout à fait lu pour la version générale. Cela ne fonctionne qu'avec les requêtes Oracle pour le moment et tout le code appartient à une grande entreprise. Un jour, je pourrai prendre mes idées et créer un nouveau framework open source, mais j'espère que l'un des grands acteurs existants relèvera le défi.


3

Pourquoi voulez-vous générer tout le SQL à la main? Avez-vous regardé un ORM comme Hibernate En fonction de votre projet, il fera probablement au moins 95% de ce dont vous avez besoin, faites-le d'une manière plus propre que le SQL brut, et si vous avez besoin d'obtenir les dernières performances, vous pouvez créer le Requêtes SQL qui doivent être réglées manuellement.


3

Vous pouvez également consulter MyBatis ( www.mybatis.org ). Il vous aide à écrire des instructions SQL en dehors de votre code java et mappe les résultats SQL dans vos objets java, entre autres choses.


3

Google fournit une bibliothèque appelée Room Persitence Library qui fournit une manière très propre d'écrire du SQL pour les applications Android , essentiellement une couche d'abstraction sur la base de données SQLite sous-jacente . Ci-dessous est un extrait de code court du site officiel:

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

Il y a plus d'exemples et une meilleure documentation dans les documents officiels de la bibliothèque.

Il y en a aussi un appelé MentaBean qui est un ORM Java . Il a des fonctionnalités intéressantes et semble être un moyen assez simple d'écrire du SQL.


Selon la documentation de la chambre : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Ce n'est donc pas une bibliothèque ORM générique pour SGBDR. Il est principalement destiné aux applications Android.
RafiAlhamd le

2

Lisez un fichier XML.

Vous pouvez le lire à partir d'un fichier XML. Il est facile à entretenir et à utiliser. Il existe des analyseurs standard STaX, DOM, SAX disponibles pour créer quelques lignes de code en java.

Faites plus avec les attributs

Vous pouvez avoir des informations sémantiques avec des attributs sur la balise pour aider à faire plus avec le SQL. Cela peut être le nom de la méthode ou le type de requête ou tout ce qui vous aide à moins coder.

Maintaince

Vous pouvez mettre le xml en dehors du pot et le maintenir facilement. Mêmes avantages qu'un fichier de propriétés.

Conversion

XML est extensible et facilement convertible en d'autres formats.

Cas d'utilisation

Metamug utilise xml pour configurer les fichiers de ressources REST avec sql.


Vous pouvez utiliser yaml ou json si vous les aimez. Ils sont mieux que de les stocker dans un fichier de propriétés simple
Trieur

La question est de savoir comment CONSTRUIRE SQL. Pour construire SQL, si vous avez besoin d'utiliser XML, Parser, Validation, etc., c'est une surcharge. La plupart des premières tentatives qui impliquaient XML pour construire SQL sont rejetées en faveur d'annotation. La réponse acceptée par Piotr Kochański est simple et élégante et précise - résout le problème et est maintenable. REMARQUE: il n'y a AUCUNE autre manière de maintenir un meilleur SQL dans une autre langue.
RafiAlhamd le

J'ai supprimé mon commentaire précédent I don't see a reason to make use of XML. , car je ne pouvais pas le modifier.
RafiAlhamd le

1

Si vous placez les chaînes SQL dans un fichier de propriétés et que vous le lisez ensuite, vous pouvez conserver les chaînes SQL dans un fichier texte brut.

Cela ne résout pas les problèmes de type SQL, mais au moins, cela facilite beaucoup le copier-coller depuis TOAD ou sqlplus.


0

Comment obtenir la concaténation de chaînes, à part les longues chaînes SQL dans PreparedStatements (que vous pouvez facilement fournir dans un fichier texte et charger de toute façon en tant que ressource) que vous coupez sur plusieurs lignes?

Vous ne créez pas directement des chaînes SQL, n'est-ce pas? C'est le plus grand non-non de la programmation. Veuillez utiliser PreparedStatements et fournir les données en tant que paramètres. Cela réduit considérablement les risques d'injection SQL.


Mais si vous n'exposez pas une page Web au public, l'injection SQL est-elle un problème pertinent?
Vidar

4
L'injection SQL est toujours pertinente, car elle peut se produire accidentellement ou intentionnellement.
sleske

1
@Vidar - vous n'exposez peut-être pas la page Web au public maintenant , mais même le code qui sera "toujours" interne finit souvent par obtenir une sorte d'exposition externe un peu plus loin. Et c'est à la fois plus rapide et plus sûr de le faire correctement du premier coup que de devoir auditer l'ensemble de la base de code pour des problèmes plus tard ...
Andrzej Doyle

4
Même un PreparedStatement doit être créé à partir d'une chaîne, non?
Stewart

Oui, mais il est sûr de créer un PreparedStatement à partir d'une chaîne, tant que vous créez un PreparedStatement sûr. Vous devriez probablement écrire une classe PreparedStatementBuilder pour les générer, pour cacher le désordre de la concaténation des choses.
JeeBee
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.