Der sauberste Weg, eine SQL-Zeichenfolge in Java zu erstellen

107

Ich möchte eine SQL-Zeichenfolge erstellen, um Datenbankmanipulationen durchzuführen (Aktualisierungen, Löschungen, Einfügungen, Auswahlen usw.) - anstelle der schrecklichen String-Concat-Methode, bei der Millionen von "+" und Anführungszeichen verwendet werden, die bestenfalls unlesbar ist muss ein besserer Weg sein.

Ich habe überlegt, MessageFormat zu verwenden - aber es sollte für Benutzernachrichten verwendet werden, obwohl ich denke, dass es einen vernünftigen Job machen würde -, aber ich denke, es sollte etwas geben, das stärker auf SQL-Operationen in den Java-SQL-Bibliotheken ausgerichtet ist.

Wäre Groovy gut?

Vidar
quelle

Antworten:

76

Erwägen Sie zunächst die Verwendung von Abfrageparametern in vorbereiteten Anweisungen:

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

Die andere Möglichkeit besteht darin, alle Abfragen in der Eigenschaftendatei zu speichern. Beispielsweise kann in einer Datei queries.properties die obige Abfrage platziert werden:

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

Dann mit Hilfe einer einfachen Utility-Klasse:

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

}

Sie können Ihre Abfragen wie folgt verwenden:

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

Dies ist eine ziemlich einfache Lösung, funktioniert aber gut.

Piotr Kochański
quelle
1
Ich bevorzuge die Verwendung eines sauberen SQL-Builders wie diesem: mentabean.soliveirajr.com
TraderJoeChicago
2
Darf ich vorschlagen, dass Sie das InputStreamInnere der if (props == null)Anweisung einfügen, damit Sie sie nicht instanziieren, wenn sie nicht benötigt wird.
SyntaxRules
64

Verwenden Sie für beliebiges SQL jOOQ . jOOQ zur Zeit unterstützt SELECT, INSERT, UPDATE, DELETE, TRUNCATE, und MERGE. Sie können SQL folgendermaßen erstellen:

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

Anstatt die SQL-Zeichenfolge abzurufen, können Sie sie auch einfach mit jOOQ ausführen. Sehen

http://www.jooq.org

(Haftungsausschluss: Ich arbeite für das Unternehmen hinter jOOQ)

Lukas Eder
quelle
Wäre dies nicht in vielen Fällen eine schlechte Lösung, da Sie nicht zulassen können, dass die Datenbank die Anweisung zuvor mit unterschiedlichen Werten für "5", "8" usw. analysiert? Ich denke, mit jooq auszuführen würde es lösen?
Vegard
@Vegard: Sie haben die volle Kontrolle darüber, wie jOOQ Bindungswerte in seiner SQL-Ausgabe rendern soll: jooq.org/doc/3.1/manual/sql-building/bind-values . Mit anderen Worten, Sie können wählen, ob Renderwerte gerendert "?"oder inline gebunden werden sollen.
Lukas Eder
Ja, aber in Bezug auf saubere Methoden zum Erstellen von SQL wäre dies in meinen Augen ein bisschen chaotischer Code, wenn Sie JOOQ nicht zum Ausführen verwenden. In diesem Beispiel setzen Sie A auf 1, B auf 2 usw., aber Sie müssen es noch einmal tun, wenn Sie ausführen, wenn Sie nicht mit JOOQ ausführen.
Vegard
1
@Vegard: Nichts hindert Sie daran, eine Variable an die jOOQ-API zu übergeben und die SQL-Anweisung neu zu erstellen. Sie können Bindungswerte auch in ihrer Reihenfolge mit jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues ​​() extrahieren oder Bindungswerte mit jooq.org/javadoc/latest/org/jooq nach Namen benennen /Query.html#getParams () . Meine Antwort enthält nur ein sehr vereinfachtes Beispiel ... Ich bin mir nicht sicher, ob dies auf Ihre Bedenken reagiert?
Lukas Eder
2
Es ist eine kostspielige Lösung.
Sortierer
15

Eine Technologie, die Sie in Betracht ziehen sollten, ist SQLJ - eine Möglichkeit, SQL-Anweisungen direkt in Java einzubetten. Als einfaches Beispiel könnte in einer Datei mit dem Namen TestQueries.sqlj Folgendes enthalten sein:

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

Es gibt einen zusätzlichen Vorkompilierungsschritt, bei dem Ihre .sqlj-Dateien in reines Java übersetzt werden - kurz gesagt, es wird nach den speziellen Blöcken gesucht, die durch abgegrenzt sind

#sql
{
    ...
}

und verwandelt sie in JDBC-Aufrufe. Die Verwendung von SQLJ bietet mehrere wichtige Vorteile:

  • abstrahiert die JDBC-Schicht vollständig - Programmierer müssen nur an Java und SQL denken
  • Der Übersetzer kann dazu gebracht werden, Ihre Abfragen beim Kompilieren auf Syntax usw. anhand der Datenbank zu überprüfen
  • Möglichkeit, Java-Variablen in Abfragen mit dem Präfix ":" direkt zu binden

Für die meisten großen Datenbankanbieter gibt es Implementierungen des Übersetzers, sodass Sie in der Lage sein sollten, alles, was Sie benötigen, leicht zu finden.

Ashley Mercer
quelle
Dieser ist laut Wikipedia jetzt veraltet.
Zeus
1
Zum Zeitpunkt des Schreibens (Januar 2016) wird SQLJ auf Wikipedia ohne Verweise als "veraltet" bezeichnet. Wurde es offiziell aufgegeben? Wenn ja, werde ich oben in dieser Antwort eine Warnung einfügen.
Ashley Mercer
Hinweis: Die Technologie wird beispielsweise in der neuesten Version von Oracle, 12c, weiterhin unterstützt . Ich gebe zu, dass es nicht der modernste Standard ist, aber es funktioniert immer noch und hat einige Vorteile (wie die Überprüfung der Abfragen zur Kompilierungszeit für die Datenbank), die in anderen Systemen nicht verfügbar sind.
Ashley Mercer
12

Ich frage mich, ob Sie nach etwas wie Squiggle suchen . Ebenfalls sehr nützlich ist jDBI . Es wird Ihnen jedoch bei den Fragen nicht helfen.

tcurdt
quelle
9

Ich würde mir Spring JDBC ansehen . Ich benutze es immer dann, wenn ich SQLs programmgesteuert ausführen muss. Beispiel:

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

Es ist wirklich großartig für jede Art von SQL-Ausführung, insbesondere für Abfragen. Es hilft Ihnen dabei, Ergebnismengen Objekten zuzuordnen, ohne die Komplexität eines vollständigen ORM zu erhöhen.

Bent André Solheim
quelle
Wie kann ich eine wirklich ausgeführte SQL-Abfrage erhalten? Ich möchte es protokollieren.
Kodmanyagha
5

Ich neige dazu, die benannten JDBC-Parameter von Spring zu verwenden, damit ich eine Standardzeichenfolge wie "select * from blah where colX = ': someValue'" schreiben kann. Ich finde das ziemlich lesbar.

Eine Alternative wäre, die Zeichenfolge in einer separaten SQL-Datei anzugeben und den Inhalt mithilfe einer Dienstprogrammmethode einzulesen.

Ein Blick auf Squill lohnt sich auch: https://squill.dev.java.net/docs/tutorial.html

GaryF
quelle
Ich nehme an, Sie meinen, Sie verwenden BeanPropertySqlParameterSource? Ich stimme Ihnen fast zu, die Klasse, die ich gerade erwähnt habe, ist cool, wenn Sie ausschließlich Beans verwenden, aber ansonsten würde ich empfehlen, benutzerdefinierten ParameterizedRowMapper zum Erstellen von Objekten zu verwenden.
Esko
Nicht ganz. Sie können eine beliebige SqlParameterSource mit benannten JDBC-Parametern verwenden. Es entsprach meinen Anforderungen, eine MapSqlParameterSource anstelle der Bohnensorte zu verwenden. In jedem Fall ist es eine gute Lösung. Die RowMappers befassen sich jedoch mit der anderen Seite des SQL-Puzzles: Ergebnismengen in Objekte verwandeln.
GaryF
4

Ich stimme den Empfehlungen für die Verwendung eines ORM wie Hibernate zu. Es gibt jedoch sicherlich Situationen, in denen dies nicht funktioniert. Daher werde ich diese Gelegenheit nutzen, um einige Dinge anzukündigen , die ich mitgeschrieben habe: SqlBuilder ist eine Java-Bibliothek zum dynamischen Erstellen von SQL-Anweisungen im "Builder" -Stil. Es ist ziemlich leistungsfähig und ziemlich flexibel.

James
quelle
4

Ich habe an einer Java-Servlet-Anwendung gearbeitet, die sehr dynamische SQL-Anweisungen für Ad-hoc-Berichtszwecke erstellen muss. Die Grundfunktion der App besteht darin, eine Reihe benannter HTTP-Anforderungsparameter in eine vorcodierte Abfrage einzuspeisen und eine gut formatierte Ausgabetabelle zu generieren. Ich habe Spring MVC und das Abhängigkeitsinjektionsframework verwendet, um alle meine SQL-Abfragen in XML-Dateien zu speichern und zusammen mit den Tabellenformatierungsinformationen in die Berichtsanwendung zu laden. Schließlich wurden die Berichtsanforderungen komplizierter als die Funktionen der vorhandenen Parameterzuordnungs-Frameworks, und ich musste meine eigenen schreiben. Es war eine interessante Übung in der Entwicklung und erzeugte ein Framework für die Parameterzuordnung, das viel robuster ist als alles andere, was ich finden konnte.

Die neuen Parameterzuordnungen sahen folgendermaßen aus:

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

Das Schöne an dem resultierenden Framework war, dass es HTTP-Anforderungsparameter mit ordnungsgemäßer Typprüfung und Grenzwertprüfung direkt in der Abfrage verarbeiten konnte. Für die Eingabevalidierung sind keine zusätzlichen Zuordnungen erforderlich. In der obigen Beispielabfrage wurde der Parameter serverId überprüft, um sicherzustellen, dass er in eine Ganzzahl umgewandelt werden kann und im Bereich von 0 bis 50 liegt. Der Parameter appId wird als Array von Ganzzahlen mit einer Längenbeschränkung von 50 verarbeitet. Wenn das Feld showOwner angezeigt wirdIst vorhanden und auf "true" gesetzt, werden die SQL-Bits in den Anführungszeichen zur generierten Abfrage für die optionalen Feldzuordnungen hinzugefügt. Feld Es stehen mehrere weitere Parametertypzuordnungen zur Verfügung, einschließlich optionaler SQL-Segmente mit weiteren Parameterzuordnungen. Es ermöglicht eine so komplexe Abfragezuordnung, wie der Entwickler sie sich vorstellen kann. Es gibt sogar Steuerelemente in der Berichtskonfiguration, um zu bestimmen, ob eine bestimmte Abfrage die endgültigen Zuordnungen über ein PreparedStatement aufweist oder einfach als vorgefertigte Abfrage ausgeführt wird.

Für die Beispiel-HTTP-Anforderungswerte:

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

Es würde das folgende SQL erzeugen:

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

Ich denke wirklich, dass Spring oder Hibernate oder eines dieser Frameworks einen robusteren Zuordnungsmechanismus bieten sollte, der Typen überprüft, komplexe Datentypen wie Arrays und andere solche Funktionen ermöglicht. Ich habe meine Engine nur für meine Zwecke geschrieben, sie ist für die allgemeine Veröffentlichung nicht ganz gelesen. Momentan funktioniert es nur mit Oracle-Abfragen und der gesamte Code gehört einem großen Unternehmen. Eines Tages werde ich vielleicht meine Ideen aufgreifen und ein neues Open-Source-Framework aufbauen, aber ich hoffe, dass einer der bestehenden Big Player die Herausforderung annehmen wird.

Natalia
quelle
3

Warum möchten Sie die gesamte SQL von Hand generieren? Haben Sie sich ein ORM wie Hibernate angesehen? Abhängig von Ihrem Projekt wird es wahrscheinlich mindestens 95% Ihrer Anforderungen erfüllen, sauberer als Raw-SQL. Wenn Sie die letzte Leistung benötigen, können Sie das erstellen SQL-Abfragen, die von Hand optimiert werden müssen.

Jared
quelle
3

Sie können sich auch MyBatis ( www.mybatis.org ) ansehen . Es hilft Ihnen, SQL-Anweisungen außerhalb Ihres Java-Codes zu schreiben und die SQL-Ergebnisse unter anderem Ihren Java-Objekten zuzuordnen.

Joshua
quelle
3

Google bietet eine Bibliothek namens Room Persitence Library, die eine sehr saubere Methode zum Schreiben von SQL für Android Apps bietet, im Grunde eine Abstraktionsschicht über der zugrunde liegenden SQLite-Datenbank . Bellow ist ein Shortcode-Snippet von der offiziellen Website:

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

Weitere Beispiele und eine bessere Dokumentation finden Sie in den offiziellen Dokumenten der Bibliothek.

Es gibt auch eine namens MentaBean, die ein Java-ORM ist . Es hat nette Funktionen und scheint eine ziemlich einfache Art zu sein, SQL zu schreiben.

CasualCoder3
quelle
Gemäß der Raumdokumentation : Room provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite. Es ist also keine generische ORM-Bibliothek für RDBMS. Es ist hauptsächlich für Android Apps gedacht.
RafiAlhamd
2

Lesen Sie eine XML-Datei.

Sie können es aus einer XML-Datei lesen. Es ist einfach zu warten und zu bearbeiten. Es gibt Standard-STaX-, DOM- und SAX-Parser, mit denen nur wenige Codezeilen in Java erstellt werden können.

Mach mehr mit Attributen

Sie können einige semantische Informationen mit Attributen auf dem Tag haben, um mehr mit SQL zu tun. Dies kann der Methodenname oder der Abfragetyp sein oder alles, was Ihnen hilft, weniger zu codieren.

Wartung

Sie können die XML-Datei außerhalb des Glases platzieren und problemlos warten. Gleiche Vorteile wie eine Eigenschaftendatei.

Umwandlung

XML ist erweiterbar und leicht in andere Formate konvertierbar.

Anwendungsfall

Metamug verwendet XML, um REST-Ressourcendateien mit SQL zu konfigurieren.

Sorter
quelle
Sie können yaml oder json verwenden, wenn Sie sie mögen. Sie sind besser als in einem normalen Eigenschaften Datei speichern
Sorter
Die Frage ist, wie SQL erstellt wird. Wenn Sie zum Erstellen von SQL XML, Parser, Validierung usw. verwenden müssen, ist dies überlastet. Die meisten frühen Versuche, bei denen XML zum Erstellen von SQL verwendet wurde, werden zugunsten von Annotation abgelehnt. Die akzeptierte Antwort von Piotr Kochański ist einfach und elegant und auf den Punkt gebracht - löst das Problem und ist wartbar. HINWEIS: Es gibt KEINE alternative Möglichkeit, ein besseres SQL in einer anderen Sprache zu verwalten.
RafiAlhamd
Ich habe meinen vorherigen Kommentar gelöscht I don't see a reason to make use of XML. , da ich ihn nicht bearbeiten konnte.
RafiAlhamd
1

Wenn Sie die SQL-Zeichenfolgen in eine Eigenschaftendatei einfügen und diese dann lesen, können Sie die SQL-Zeichenfolgen in einer Nur-Text-Datei behalten.

Das löst die Probleme mit dem SQL-Typ nicht, erleichtert aber das Kopieren und Einfügen von TOAD oder sqlplus erheblich.

Eberesche
quelle
0

Wie erhalten Sie eine Verkettung von Zeichenfolgen, abgesehen von langen SQL-Zeichenfolgen in PreparedStatements (die Sie problemlos in einer Textdatei bereitstellen und trotzdem als Ressource laden können), die Sie über mehrere Zeilen verteilen?

Sie erstellen SQL-Zeichenfolgen nicht direkt, oder? Das ist das größte Nein-Nein in der Programmierung. Bitte verwenden Sie PreparedStatements und geben Sie die Daten als Parameter an. Dies verringert die Wahrscheinlichkeit von SQL Injection erheblich.

JeeBee
quelle
Aber wenn Sie eine Webseite nicht der Öffentlichkeit zugänglich machen - ist SQL Injection ein relevantes Problem?
Vidar
4
SQL Injection ist immer relevant, da es sowohl versehentlich als auch absichtlich passieren kann.
Sleske
1
@Vidar - Möglicherweise stellen Sie die Webseite jetzt nicht der Öffentlichkeit zur Verfügung , aber selbst Code, der "immer" intern ist, wird häufig irgendwann später extern angezeigt. Und es ist sowohl schneller als auch sicherer, es gleich beim ersten Mal richtig zu machen, als die gesamte Codebasis später auf Probleme prüfen zu müssen ...
Andrzej Doyle
4
Sogar ein PreparedStatement muss aus einem String erstellt werden, oder?
Stewart
Ja, aber es ist sicher, ein PreparedStatement aus einem String zu erstellen, solange Sie ein sicheres PreparedStatement erstellen. Sie sollten wahrscheinlich eine PreparedStatementBuilder-Klasse schreiben, um sie zu generieren und das Durcheinander von verketteten Dingen zu verbergen.
JeeBee