web-dev-qa-db-fra.com

Java - Stockage d'instructions SQL dans un fichier externe

Je recherche une Java/framework/technique de stockage des instructions SQL dans un fichier externe. L'équipe de support (y compris les DBA) devrait être en mesure de modifier (légèrement) l'instruction pour les conserver dans synchroniser en cas de modification du schéma de la base de données ou à des fins de réglage.

Voici les prérequis:

  • Le fichier doit être lisible à partir d'une application Java mais doit également être modifiable par l'équipe de support sans avoir besoin d'éditeurs sophistiqués
  • Idéalement, le fichier devrait être au format texte brut mais XML est également OK
  • Autoriser le stockage/la récupération des instructions DML et DDL
  • De nouvelles instructions peuvent être ajoutées à un stade ultérieur (l'application est suffisamment flexible pour les récupérer et les exécuter)
  • Les instructions peuvent être regroupées (et exécutées en tant que groupe par l'application)
  • Les déclarations doivent permettre des paramètres

Remarques:

  • Une fois récupérées, les instructions seront exécutées à l'aide du JDBCTemplate de Spring
  • Hibernate ou Spring IOC container ne sera pas utilisé

Jusqu'à présent, j'ai réussi à trouver les bibliothèques Java Java, qui utilisent des fichiers externes pour stocker les instructions SQL. Cependant, je suis principalement intéressé par le stockage plutôt que par une bibliothèque qui cache toutes les "complexités" JDBC .

  • Bibliothèque Axamol SQL

    Exemple de contenu de fichier:

    <s:query name="get_emp">
      <s:param name="name" type="string"/>
      <s:sql databases="Oracle">
        select    *
        from      scott.emp
                  join scott.dept on (emp.deptno = dept.deptno)
        where     emp.ename = <s:bind param="name"/>
      </s:sql>
    </s:query>
    
  • iBATIS

    Exemple de contenu de fichier:

    <sqlMap namespace="Contact"">
        <typeAlias alias="contact"
            type="com.sample.contact.Contact"/">
        <select id="getContact"
            parameterClass="int" resultClass="contact"">
                select CONTACTID as contactId,
                       FIRSTNAME as firstName,
                       LASTNAME as lastName from
                       ADMINISTRATOR.CONTACT where CONTACTID = #id#
        </select>
    </sqlMap>
    <insert id="insertContact" parameterClass="contact">
    INSERT INTO ADMINISTRATOR.CONTACT( CONTACTID,FIRSTNAME,LASTNAME)
            VALUES(#contactId#,#firstName#,#lastName#);
     </insert>
    <update id="updateContact" parameterClass="contact">
    update ADMINISTRATOR.CONTACT SET
    FIRSTNAME=#firstName# ,
    LASTNAME=#lastName#
    where contactid=#contactId#
    </update>
    <delete id="deleteContact" parameterClass="int">
    DELETE FROM ADMINISTRATOR.CONTACT WHERE CONTACTID=#contactId#
    </delete>
    
  • WEB4J

    -- This is a comment 
     ADD_MESSAGE   {
     INSERT INTO MyMessage -- another comment
      (LoginName, Body, CreationDate)
      -- another comment
      VALUES (?,?,?)
     }
    
    -- Example of referring to a constant defined above.
    FETCH_RECENT_MESSAGES {
     SELECT 
     LoginName, Body, CreationDate 
     FROM MyMessage 
     ORDER BY Id DESC LIMIT ${num_messages_to_view}
    }
    

Quelqu'un peut-il recommander une solution qui a fait ses preuves?

45
Adrian

Créez simplement un simple Java avec des paires clé-valeur comme celle-ci:

users.select.all = select * from user

Déclarez un champ privé de type Propriétés dans votre classe DAO et injectez-le à l'aide de la configuration Spring qui lira les valeurs du fichier.

UPDATE : si vous souhaitez prendre en charge les instructions SQL sur plusieurs lignes, utilisez cette notation:

users.select.all.0 = select *
users.select.all.1 = from   user
58
Boris Pavlović

Si vous devez faire cela, vous devriez regarder le projet MyBatis . Je ne l'ai pas utilisé, mais je l'ai entendu à plusieurs reprises.

Séparer SQL et Java n'est pas mon approche préférée, car SQL est en fait du code, et est étroitement couplé au code Java qui l'appelle. Maintenance et débogage) le code séparé peut être difficile.

N'utilisez absolument pas de proc stockés pour cela. Ils ne doivent être utilisés que pour améliorer les performances en réduisant le trafic entre la base de données et l'application.

8
John Stauffer

Une solution simple que nous avons implémentée face à cela était d'externaliser le SQL/DML dans un fichier (mySql.properties), puis d'utiliser MessageFormat.format (String [] args) pour injecter des propriétés dynamiques dans le SQL.

Par exemple: mySql.properties:

select    *
    from      scott.emp
              join scott.dept on (emp.deptno = dept.deptno)
    where     emp.ename = {0}

Méthodes utilitaires:

public static String format(String template, Object[] args) {
    String cleanedTemplate = replaceSingleQuotes(template);
    MessageFormat mf = new MessageFormat(cleanedTemplate);
    String output = mf.format(args);
    return output;
}
private static String replaceSingleQuotes(String template) {
    String cleaned = template.replace("'", "''");
    return cleaned;
}

Ensuite, utilisez-le comme ceci:

String sqlString = youStringReaderImpl("/path/to/file");
String parsedSql = format(sqlString, new String[] {"bob"});
7
Rich Kroll

Coller ici mon answer to Clean way to externalize long (+20 lines sql) when using spring jdbc? :

J'ai fait face au même problème il y a quelque temps et j'ai trouvé YAML. Il prend en charge les valeurs de propriété de chaîne multi-lignes, vous pouvez donc écrire quelque chose comme ceci dans vos fichiers de requête:

selectSomething: >
  SELECT column1, column2 FROM SOMETHING

insertSomething: >
  INSERT INTO SOMETHING(column1, column2)
  VALUES(1, '1')

Ici, selectSomething et insertSomething sont des noms de requête. C'est donc très pratique et contient très peu de caractères spéciaux. Les requêtes sont séparées par des lignes vides et chaque texte de requête doit être mis en retrait. Notez que les requêtes peuvent absolument contenir leur indentation, de sorte que ce qui suit est parfaitement valide:

anotherSelect: <
  SELECT column1 FROM SOMETHING
  WHERE column2 IN (
    SELECT * FROM SOMETHING_ELSE
  )

Vous pouvez ensuite lire le contenu du fichier dans une table de hachage à l'aide de la bibliothèque SnakeYAML, en utilisant le code ci-dessous:

import org.Apache.commons.io.FilenameUtils;
import org.Apache.commons.io.FileUtils;
import Java.io.FileReader;

import org.yaml.snakeyaml.Yaml;
import Java.io.File;
import Java.io.FileNotFoundException;

public class SQLReader {
  private Map<String, Map> sqlQueries = new HashMap<String, Map>();

  private SQLReader() {
    try {
      final File sqlYmlDir = new File("dir_with_yml_files");
      Collection<File> ymlFiles = FileUtils.listFiles(sqlYmlDir, new String[]{"yml"}, false);
      for (File f : ymlFiles) {
        final String fileName = FilenameUtils.getBaseName(f.getName());
        Map ymlQueries = (Map)new Yaml().load(new FileReader(f));
        sqlQueries.put(fileName, ymlQueries);
      }
    }
    catch (FileNotFoundException ex) {
      System.out.println("File not found!!!");
    }
  }
}

Dans l'exemple ci-dessus, une carte de cartes est créée, mappant chaque fichier YAML sur une carte contenant des noms/chaînes de requête.

6
siphiuel

La bibliothèque ElSql fournit cette fonctionnalité.

ElSql consiste en un petit fichier jar (six classes publiques) qui permet de charger un fichier SQL externe (elsql). Le fichier utilise un format simple pour fournir éventuellement un comportement légèrement supérieur au simple chargement d'un fichier:

-- an example comment
@NAME(SelectBlogs)
  @PAGING(:paging_offset,:paging_fetch)
    SELECT @INCLUDE(CommonFields)
    FROM blogs
    WHERE id = :id
      @AND(:date)
        date > :date
      @AND(:active)
        active = :active
    ORDER BY title, author
@NAME(CommonFields)
  title, author, content

// Java code:
bundle.getSql("SelectBlogs", searchArgs);

Le fichier est divisé en @NAME blocs auxquels on peut se référer à partir du code. Chaque bloc est défini par une indentation importante des espaces blancs. @PAGING insérera le code nécessaire pour la pagination tel que FETCH/OFFSET. @AND ne sera affiché que si la variable spécifiée existe (ce qui aide à construire des recherches dynamiques). La DSL gère également LIKE vs = pour les caractères génériques dans les recherches. Le but des balises DSL facultatives est de fournir les principes de base communs qui se produisent souvent lorsque vous essayez de créer du SQL dynamique d'une manière neutre pour la base de données.

Plus d'informations sur le blog ou guide de l'utilisateur .

5
JodaStephen

Vous pouvez également utiliser la classe QueryLoader dans Apache Commons DbUtils , qui lira le sql à partir d'un fichier de propriétés. Cependant, vous devrez utiliser DbUtils qui sert en quelque sorte le même but que le JDBCTemplate.

5
Ken Liu

Vous pouvez utiliser Spring et avoir vos instructions sql stockées dans votre fichier beans qui sont injectées lorsque vous obtenez la classe de votre fabrique de beans. Cette classe peut également utiliser une instance de SimpleJDBCTemplate qui peut être configurée via le fichier bean pour simplifier votre code.

3
user199218

C'est simple et fiable de faire en utilisant des classes de Spring. Prenez vos fichiers SQL et enregistrez-les dans un emplacement sur votre chemin de classe. Cela peut être dans un fichier JAR qui ne contient que du SQL si vous le souhaitez. Ensuite, utilisez Spring's ClassPathResource pour charger le fichier dans un flux et utilisez Apache IOUtils pour le convertir en chaîne. Vous pouvez ensuite exécuter le SQL en utilisant SimpleJdbcTemplate, ou le code DB de votre choix.

Je vous suggère de créer une classe utilitaire qui prend une simple classe Java avec des champs String publics qui correspondent aux noms de fichiers SQL suivant une convention de votre choix. Ensuite, utilisez la réflexion conjointement avec le ClassPathResource classe pour rechercher les fichiers SQL conformes à votre convention de dénomination et les affecter aux champs String. Ensuite, faites simplement référence aux champs de classe lorsque vous avez besoin de SQL. C'est simple, fonctionne très bien et atteint l'objectif que vous veulent. Il utilise également des classes et des techniques bien portées. Rien d'extraordinaire. Je l'ai fait il y a quelques années. Fonctionne très bien. Trop paresseux pour aller chercher le code. Vous n'aurez pas le temps de le découvrir vous-même.

2
marcus none

Vous pouvez utiliser les fonctionnalités de localisation pour ce faire. Vous utilisez ensuite le nom de la base de données comme paramètres régionaux pour obtenir la version "oraclish" de "insert-foo-in-bar" au lieu de la version anglaise ou française.

Les traductions sont généralement stockées dans des fichiers de propriétés, et il existe de bons outils pour localiser les applications en permettant de modifier ces fichiers de propriétés.

Je vous encourage fortement à utiliser les procédures stockées. Ce genre de chose est exactement ce à quoi ils servent.

1
Dave

dynamic-query est un bon framework open source pour ceux qui veulent quelque chose entre JDBC et ORM.

1 SQL simple. - Il enregistre SQL brut dans des fichiers externes. pas de balises redondantes, prend en charge les commentaires.

/* It also supports comment.
This code is in an external file 'sample.sql', Not inisde Java code.*/
listUsers : select * from user_table
where user_id= $$;  /* $$ will automatically catch a parameter userId */


2 SQL extensible. -Il prend en charge les paramètres, y compris d'autres fichiers et sous-requête.

listUsers:
select
    id, amount, created
    @checkEmail{ ,email } 
from user_table
where amount > $amt and balance < $amt
    @checkDate { and created = $$ }
    @checkEmail{ and email in (
        select email from vip_list ) } ;        
/* Above query can be four queries like below.
1. listUsers
2. listUsers.checkDate 
3. listUsers.checkEmail
4. listUsers.checkDate.checkEmail 
*/



-- It can include other files like below
& ../hr/additional hr.sql ; 
& ../fi/additional fi.sql ;


Exemple de code Java utilisant ci-dessus. définition des valeurs sur db.

QueryUtil qu = qm.createQueryUtil("selectAll");
try {
    qu.setConnection(conn);

    // with native jdbc
    qu.setString("alpha");
    qu.setDouble(10.1);
    qu.executeQuery();

    // or with bean
    qu.executeQuery(new User("alpha", 10.1));

    // or with map
    Map<String, Object> map=new HashMap<String, Object>();
    map.put("userName", "alpha");
    map.put("amt", 10.1);
    qu.executeQuery(map);

    // or with array
    qu.executeQueryParameters("alpha", 10.1);

Exemple de code Java utilisant ci-dessus. obtenir des valeurs de db.

    while (qu.next()) // == qu.rs.next()
    {
        // native jdbc
        String usreName = qu.getString("user_name"); 
        double amt = qu.getDouble("amt");

        // or bean
        User user = new User();
        qu.updateBean(user);

        // or array
        Object[] values = qu.populateArray();
    }
} catch (Exception e) {
    e.printStackTrace();
} finally {
    qu.closeJust();
}
1
user3408022

Vous pouvez utiliser velocity pour avoir des modèles sql "scriptables" que vous pouvez utiliser pour travailler avec les fichiers de manière flexible. Vous avez des instructions primitives comme des conditionnelles et des boucles pour construire vos commandes sql.

Mais je suggère fortement d'utiliser des instructions préparées et/ou des procédures stockées. Construire votre SQL comme vous le planifiez vous rendra vulnérable à l'injection SQL, le serveur de base de données ne pourra pas mettre en cache les requêtes SQL (ce qui entraînera de mauvaises performances).

BTW: Vous pouvez également stocker la définition des instructions préparées dans des fichiers. Ce n'est pas la meilleure solution mais assez proche d'elle et vous bénéficiez de la protection et des performances d'injection SQL.

Lorsque votre schéma SQL n'est pas construit pour fonctionner avec des instructions préparées ou des procédures stockées, vous souhaiterez peut-être repenser votre schéma. Peut-être qu'il doit être refactorisé.

0