Il modo più pulito per creare una stringa SQL in Java


107

Voglio costruire una stringa SQL per manipolare il database (aggiornamenti, eliminazioni, inserimenti, selezioni, quel genere di cose) - invece del terribile metodo di concatenazione di stringhe usando milioni di "+" e virgolette che è illeggibile nella migliore delle ipotesi - lì deve essere un modo migliore.

Ho pensato di usare MessageFormat - ma dovrebbe essere usato per i messaggi degli utenti, anche se penso che farebbe un lavoro ragionevole - ma immagino che dovrebbe esserci qualcosa di più allineato alle operazioni di tipo SQL nelle librerie java sql.

Groovy andrebbe bene?

Risposte:


76

Prima di tutto considera l'utilizzo di parametri di query nelle istruzioni preparate:

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

L'altra cosa che si può fare è mantenere tutte le query nel file delle proprietà. Ad esempio in un file queries.properties è possibile inserire la query precedente:

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

Quindi con l'aiuto di una semplice classe di utilità:

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);
    }

}

potresti utilizzare le tue query come segue:

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

Questa è una soluzione piuttosto semplice, ma funziona bene.


1
Preferisco usare un generatore SQL pulito come questo: mentabean.soliveirajr.com
TraderJoeChicago

2
Posso suggerirti di inserire l' InputStreaminterno if (props == null)dell'istruzione in modo da non istanziarla quando non è necessaria.
SyntaxRules

64

Per SQL arbitrario, usa jOOQ . jOOQ attualmente supporta SELECT, INSERT, UPDATE, DELETE, TRUNCATE, e MERGE. Puoi creare SQL in questo modo:

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();

Invece di ottenere la stringa SQL, potresti anche semplicemente eseguirla, usando jOOQ. Vedere

http://www.jooq.org

(Disclaimer: lavoro per l'azienda dietro jOOQ)


in molti casi non sarebbe una soluzione scadente in quanto non è possibile lasciare che il dbms analizzi in anticipo l'istruzione con valori diversi per "5", "8", ecc.? Immagino che l'esecuzione con jooq lo risolverebbe?
Vegard

@Vegard: hai il pieno controllo su come jOOQ dovrebbe rendere i valori di bind nel suo output SQL: jooq.org/doc/3.1/manual/sql-building/bind-values . In altre parole, puoi scegliere se eseguire il rendering "?"o se associare i valori in linea.
Lukas Eder

sì, ma per quanto riguarda i modi puliti per costruire sql, questo sarebbe un codice un po 'disordinato ai miei occhi se non stai usando JOOQ per eseguire. in questo esempio imposti A a 1, B a 2 ecc., ma devi farlo ancora una volta quando esegui se non stai eseguendo con JOOQ.
Vegard

1
@Vegard: Niente ti impedisce di passare una variabile all'API jOOQ e ricostruire l'istruzione SQL. Inoltre, puoi estrarre i valori di bind nel loro ordine utilizzando jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() o denominare i valori di bind con i loro nomi utilizzando jooq.org/javadoc/latest/org/jooq /Query.html#getParams () . La mia risposta contiene solo un esempio molto semplicistico ... Non sono sicuro che questo risponda alle tue preoccupazioni, però?
Lukas Eder

2
È una soluzione costosa.
Sorter

15

Una tecnologia da considerare è SQLJ , un modo per incorporare istruzioni SQL direttamente in Java. Come semplice esempio, potresti avere quanto segue in un file chiamato TestQueries.sqlj:

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

C'è un ulteriore passaggio di precompilazione che prende i tuoi file .sqlj e li traduce in puro Java - in breve, cerca i blocchi speciali delimitati con

#sql
{
    ...
}

e li trasforma in chiamate JDBC. Esistono diversi vantaggi chiave nell'utilizzo di SQLJ:

  • astrae completamente il livello JDBC: i programmatori devono solo pensare a Java e SQL
  • il traduttore può essere incaricato di controllare le tue query per la sintassi ecc. rispetto al database in fase di compilazione
  • capacità di associare direttamente le variabili Java nelle query utilizzando il prefisso ":"

Esistono implementazioni del traduttore per la maggior parte dei principali fornitori di database, quindi dovresti essere in grado di trovare facilmente tutto ciò di cui hai bisogno.


Questo è obsoleto ora, come da wikipedia.
Zeus

1
Al momento della scrittura (gennaio 2016) SQLJ è indicato su Wikipedia come "obsoleto" senza alcun riferimento. È stato ufficialmente abbandonato? In tal caso, inserirò un avviso all'inizio di questa risposta.
Ashley Mercer

NB La tecnologia è ancora supportata, ad esempio, nell'ultima versione di Oracle, 12c . Ammetto che non è lo standard più moderno, ma funziona ancora e presenta alcuni vantaggi (come la verifica in fase di compilazione delle query rispetto al DB) che non sono disponibili in altri sistemi.
Ashley Mercer

12

Mi chiedo se cerchi qualcosa come Squiggle . Anche qualcosa di molto utile è jDBI . Tuttavia, non ti aiuterà con le domande.


9

Vorrei dare un'occhiata a Spring JDBC . Lo uso ogni volta che ho bisogno di eseguire SQL in modo programmatico. Esempio:

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

È davvero ottimo per qualsiasi tipo di esecuzione sql, in particolare per le query; ti aiuterà a mappare i set di risultati sugli oggetti, senza aggiungere la complessità di un ORM completo.


come posso ottenere una query SQL reale eseguita? Voglio registrarlo.
kodmanyagha

5

Tendo ad utilizzare i parametri JDBC denominati di Spring in modo da poter scrivere una stringa standard come "select * from blah where colX = ': someValue'"; Penso che sia abbastanza leggibile.

Un'alternativa potrebbe essere quella di fornire la stringa in un file .sql separato e leggere il contenuto utilizzando un metodo di utilità.

Oh, vale anche la pena dare un'occhiata a Squill: https://squill.dev.java.net/docs/tutorial.html


Presumo tu voglia dire che stai usando BeanPropertySqlParameterSource? Sono quasi d'accordo con te, la classe che ho appena menzionato è interessante quando si usano rigorosamente i bean, ma altrimenti consiglierei di utilizzare ParameterizedRowMapper personalizzato per costruire oggetti.
Esko

Non proprio. È possibile utilizzare qualsiasi SqlParameterSource con parametri JDBC denominati. Si adattava alle mie esigenze di utilizzare MapSqlParameterSource, piuttosto che la varietà di bean. Ad ogni modo, è una buona soluzione. I RowMapper, tuttavia, si occupano dell'altro lato del puzzle SQL: trasformare i set di risultati in oggetti.
GaryF

4

Secondo le raccomandazioni per l'utilizzo di un ORM come Hibernate. Tuttavia, ci sono certamente situazioni in cui ciò non funziona, quindi coglierò l' occasione per pubblicizzare alcune cose che ho aiutato a scrivere: SqlBuilder è una libreria java per la creazione dinamica di istruzioni sql utilizzando lo stile "builder". è abbastanza potente e abbastanza flessibile.


4

Ho lavorato su un'applicazione servlet Java che ha bisogno di costruire istruzioni SQL molto dinamiche per scopi di reporting ad hoc. La funzione di base dell'app è alimentare un gruppo di parametri di richiesta HTTP denominati in una query precodificata e generare una tabella di output ben formattata. Ho usato Spring MVC e il framework di inserimento delle dipendenze per archiviare tutte le mie query SQL in file XML e caricarle nell'applicazione di reportistica, insieme alle informazioni sulla formattazione della tabella. Alla fine, i requisiti di reporting sono diventati più complicati delle capacità dei framework di mappatura dei parametri esistenti e ho dovuto scriverne uno mio. È stato un interessante esercizio di sviluppo e ha prodotto un framework per la mappatura dei parametri molto più robusto di qualsiasi altra cosa che ho potuto trovare.

Le nuove mappature dei parametri sembravano tali:

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 bellezza del framework risultante era che poteva elaborare i parametri della richiesta HTTP direttamente nella query con il controllo del tipo e il controllo dei limiti appropriati. Nessun mapping aggiuntivo richiesto per la convalida dell'input. Nella query di esempio precedente, il parametro denominato serverId verrebbe controllato per assicurarsi che possa eseguire il cast su un numero intero e rientrare nell'intervallo 0-50. Il parametro appId verrebbe elaborato come un array di numeri interi, con un limite di lunghezza di 50. Se il campo showOwnerè presente e impostato su "true", i bit di SQL tra virgolette verranno aggiunti alla query generata per le mappature dei campi opzionali. campo Sono disponibili molte altre mappature del tipo di parametro, inclusi segmenti opzionali di SQL con ulteriori mappature dei parametri. Consente una mappatura delle query tanto complessa quanto lo sviluppatore può fornire. Ha anche controlli nella configurazione del report per determinare se una determinata query avrà le mappature finali tramite una PreparedStatement o semplicemente eseguita come query precostruita.

Per i valori di richiesta Http di esempio:

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

Produrrebbe il seguente SQL:

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

Penso davvero che Spring o Hibernate o uno di questi framework dovrebbero offrire un meccanismo di mappatura più robusto che verifica i tipi, consente tipi di dati complessi come array e altre funzionalità simili. Ho scritto il mio motore solo per i miei scopi, non è abbastanza letto per il rilascio generale. Al momento funziona solo con le query Oracle e tutto il codice appartiene a una grande azienda. Un giorno potrei prendere le mie idee e costruire un nuovo framework open source, ma spero che uno dei grandi attori esistenti accetterà la sfida.


3

Perché vuoi generare tutto lo sql a mano? Hai guardato un ORM come Hibernate A seconda del tuo progetto probabilmente farà almeno il 95% di ciò di cui hai bisogno, fallo in modo più pulito rispetto all'SQL grezzo, e se hai bisogno di ottenere le ultime prestazioni puoi creare il Query SQL che devono essere messe a punto manualmente.


3

Puoi anche dare un'occhiata a MyBatis ( www.mybatis.org ). Ti aiuta a scrivere istruzioni SQL al di fuori del tuo codice java e mappa i risultati sql nei tuoi oggetti java tra le altre cose.


3

Google fornisce una libreria chiamata Room Persitence Library che fornisce un modo molto pulito di scrivere SQL per app Android , fondamentalmente un livello di astrazione sul database SQLite sottostante . Di seguito è riportato uno snippet di codice breve dal sito Web ufficiale:

@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);
}

Ci sono più esempi e una migliore documentazione nei documenti ufficiali per la libreria.

Ce n'è anche uno chiamato MentaBean che è un ORM Java . Ha caratteristiche interessanti e sembra essere un modo piuttosto semplice di scrivere SQL.


Come per la documentazione di camera : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Quindi, non è una libreria ORM generica per RDBMS. È pensato principalmente per le app Android.
RafiAlhamd,

2

Leggi un file XML.

Puoi leggerlo da un file XML. È facile da mantenere e lavorare. Ci sono parser standard STaX, DOM, SAX disponibili là fuori per rendere poche righe di codice in java.

Fai di più con gli attributi

Puoi avere alcune informazioni semantiche con attributi sul tag per aiutarti a fare di più con l'SQL. Può essere il nome del metodo o il tipo di query o qualsiasi cosa che ti aiuti a programmare meno.

maintaince

Puoi mettere l'xml fuori dal barattolo e mantenerlo facilmente. Stessi vantaggi di un file delle proprietà.

Conversione

XML è estensibile e facilmente convertibile in altri formati.

Caso d'uso

Metamug usa xml per configurare i file di risorse REST con sql.


Puoi usare yaml o json se ti piacciono. Sono meglio che archiviarli in un semplice file delle proprietà
Sorter

La domanda è come COSTRUIRE SQL. Per creare SQL, se è necessario utilizzare XML, Parser, Validation, ecc. È sovraccarico. La maggior parte dei primi tentativi che coinvolgevano XML per costruire SQL sono stati respinti a favore di Annotation. La risposta accettata da Piotr Kochański è semplice ed elegante e al punto: risolve il problema e manutenibile. NOTA: NON esiste un modo alternativo per mantenere un SQL migliore in una lingua diversa.
RafiAlhamd,

Ho cancellato il mio commento precedente I don't see a reason to make use of XML. , perché non potevo modificarlo.
RafiAlhamd,

1

Se metti le stringhe SQL in un file delle proprietà e poi lo leggi, puoi conservare le stringhe SQL in un file di testo normale.

Ciò non risolve i problemi di tipo SQL, ma almeno rende molto più facile copiare e incollare da TOAD o sqlplus.


0

Come si ottiene la concatenazione di stringhe, a parte lunghe stringhe SQL in PreparedStatements (che potresti facilmente fornire in un file di testo e caricare comunque come risorsa) che interrompi su più righe?

Non stai creando direttamente stringhe SQL, vero? Questo è il più grande no-no nella programmazione. Utilizzare PreparedStatements e fornire i dati come parametri. Riduce notevolmente la possibilità di SQL Injection.


Ma se non stai esponendo una pagina web al pubblico, SQL Injection è un problema rilevante?
Vidar

4
SQL Injection è sempre rilevante, perché può accadere sia accidentalmente che intenzionalmente.
sleske

1
@Vidar - potresti non esporre la pagina web al pubblico ora , ma anche il codice che sarà "sempre" interno spesso finisce per ottenere una sorta di esposizione esterna in qualche punto più in là. Ed è sia più veloce che più sicuro farlo bene la prima volta che dover controllare l'intera base di codice per problemi in seguito ...
Andrzej Doyle

4
Anche un PreparedStatement deve essere creato da una stringa, no?
Stewart

Sì, ma è sicuro creare una PreparedStatement da una stringa, a condizione che venga creata una PreparedStatement sicura. Probabilmente dovresti scrivere una classe PreparedStatementBuilder per generarli, per nascondere il caos delle cose concatenate.
JeeBee
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.