Frage

Unser aktuelles Projekt verwendet nicht Hibernate (aus verschiedenen Gründen) und wir sind mit Spring SimpleJdbc Unterstützung aller Operationen unserer DB auszuführen. Wir haben eine Utility-Klasse, die alle CRUD-Operationen, aber komplexe Operationen abstrahiert werden benutzerdefinierte SQL-Abfragen durchgeführt wird.

Zur Zeit sind unsere Anfragen als String-Konstanten in den Serviceklassen selbst gespeichert und werden zu einem Dienstprogramm gespeist durch die SimpleJdbcTemplate sein auszuführen. Wir sind in einer Sackgasse, wo die Lesbarkeit mit Wartbarkeit ausgeglichen werden muss. SQL-Code in der Klasse selbst ist besser wartbar, da es mit dem Code befindet, die es verwendet. Auf der anderen Seite, wenn wir speichern diese Abfragen in einer externen Datei (flach oder XML) die SQL selbst wäre besser lesbar als im Vergleich zu Java-String-Syntax entkam.

Hat jemand ein ähnliches Problem aufgetreten? Was ist eine gute Balance? Wo bewahren Sie Ihre benutzerdefinierte SQL in Ihrem Projekt?

Eine Beispielabfrage ist wie folgt:

private static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
"    FROM PRODUCT_SKU T \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.ID) as minimum_id_for_price \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID, S.SALE_PRICE \n" +
"    ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) \n" +
"    JOIN \n" +
"    ( \n" +
"        SELECT S.PRODUCT_ID, \n" +
"               MIN(S.SALE_PRICE) as minimum_price_for_product \n" +
"          FROM PRODUCT_SKU S \n" +
"         WHERE S.PRODUCT_ID IN (:productIds) \n" +
"      GROUP BY S.PRODUCT_ID \n" +
"    ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) \n" +
"WHERE T.PRODUCT_ID IN (:productIds)";

Dies ist, wie es in einer flachen SQL-Datei aussehen:

--namedQuery: FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS
FROM PRODUCT_SKU T 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.ID) as minimum_id_for_price 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
JOIN 
( 
    SELECT S.PRODUCT_ID, 
           MIN(S.SALE_PRICE) as minimum_price_for_product 
      FROM PRODUCT_SKU S 
     WHERE S.PRODUCT_ID IN (:productIds) 
  GROUP BY S.PRODUCT_ID 
) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
WHERE T.PRODUCT_ID IN (:productIds)
War es hilfreich?

Lösung

Ich habe SQL, da beide Strings innerhalb einer Java-Klasse gespeichert und als separate Dateien, die zur Laufzeit geladen wurden. Ich zog es stark letztere aus zwei Gründen. Erstens ist der Code besser lesbar mit großem Abstand. Zweitens ist es einfacher, die SQL isoliert zu testen, ob Sie es in einer separaten Datei speichern. Zusätzlich zu, dass war es einfacher, jemand zu SQL besser als ich, um mir mit meinen Anfragen zu helfen, wenn sie in separaten Dateien waren.

Andere Tipps

Ich habe auch in diesem Lauf, die derzeit aus dem gleichen Grund - ein Projekt, das auf Frühjahr jdbc. Meine Erfahrung ist, dass es zwar nicht toll, sich die Logik in der SQL zu haben, gibt es wirklich keinen besseren Ort für sie, und im Anwendungscode setzen ist langsamer als mit der db es tun und nicht unbedingt ein klarer.

Biggest Fällt ich gesehen habe, ist, wo die SQL ganzes Projekt zu wuchern beginnt, mit mehreren Variationen. "Get A, B, C von FOO". "Get A, B, C, E von Foo", etc.etc. Diese Art der Vermehrung ist besonders wahrscheinlich, da das Projekt eine bestimmte kritische Masse trifft - es ist nicht mit 10 Abfragen wie ein Problem zu sein scheint, aber wenn 500 Abfragen dort während des gesamten Projektes verstreut ist wird es viel schwieriger, herauszufinden, ob Sie schon etwas getan haben . die grundlegenden CRUD-Operationen abstrahieren bringt Sie weit vor dem Spiel hier.

Beste Lösung, AFAIK, ist streng im Einklang mit dem codierten SQL zu sein - kommentiert, getestet und in einem einheitlichen Ort. Unser Projekt hat 50-line uncommented SQL-Abfragen. Was meinen sie? Wer weiß?

Wie bei Abfragen in externen Dateien, sehe ich nicht, was das kauft - Sie sind immer noch genauso abhängig von der SQL, und mit Ausnahme der (fragwürdigen) ästhetischen Verbesserung des SQL-aus den Klassen zu halten, Ihre Klassen sind immer noch genauso abhängig von der sQL--.eg Sie in der Regel getrennte Ressourcen, um die Flexibilität Ressourcen Plug-in-Ersatzes zu bekommen, aber man konnte in Ersatz sQL-Abfragen nicht stecken wie die semantische Bedeutung der Klasse ändern würde oder haupt nicht funktionieren. Es ist also eine Illusion Code-Sauberkeit.

Eine ziemlich radikale Lösung wäre Groovy zu benutzen, um Ihre Anfragen zu spezifizieren. Groovy hat Sprach-Level-Support für mehrzeilige Strings und String-Interpolation (witziger als gstrings bekannt).

Zum Beispiel mit Groovy, der Abfrage, die Sie oben angegeben haben würde einfach sein:

class Queries
    private static final String PRODUCT_IDS_PARAM = ":productIds"

    public static final String FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS = 
    """    FROM PRODUCT_SKU T 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.ID) as minimum_id_for_price 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID, S.SALE_PRICE 
        ) FI ON (FI.PRODUCT_ID = T.PRODUCT_ID AND FI.minimum_id_for_price = T.ID) 
        JOIN 
        ( 
            SELECT S.PRODUCT_ID, 
                   MIN(S.SALE_PRICE) as minimum_price_for_product 
              FROM PRODUCT_SKU S 
             WHERE S.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) 
          GROUP BY S.PRODUCT_ID 
        ) FP ON (FP.PRODUCT_ID = T.PRODUCT_ID AND FP.minimum_price_for_product = T.sale_price) 
    WHERE T.PRODUCT_ID IN ($PRODUCT_IDS_PARAM) """

Sie können diese Klasse zugreifen Java-Code, so wie du wäre, als ob es in Java definiert wurden, z.

String query = QueryFactory.FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS;

Ich werde zugeben, dass Groovy mit denen Sie Ihre Classpath nur Ihre SQL-Abfragen schöner aussehen zu machen, ist ein wenig eine Lösung „Vorschlaghammer eine Nuss zu knacken“, aber wenn Sie Frühling verwenden, gibt es eine faire Chance bereits Groovy auf Ihrem classpath.

Außerdem gibt es wahrscheinlich eine Menge an anderen Orten in Ihrem Projekt, wo Sie Groovy (anstelle von Java) verwenden könnten, Ihren Code zu verbessern, (vor allem jetzt, dass Groovy ist von Frühling Besitz). Beispiele Testfälle gehören zu schreiben, oder ersetzen Java Beans mit Groovy Bohnen.

Wir verwenden gespeicherte Prozeduren. Das ist gut für uns, weil wir Oracle Fine Grain Access verwenden. Dies ermöglicht es uns, einen Benutzer daran zu hindern, einen bestimmten Bericht zu sehen oder zu den Suchergebnissen durch ihren Zugang zu dem entsprechenden Verfahren zu begrenzen. Es gibt uns auch ein wenig von einer Leistungssteigerung.

Warum Stored Procedures nicht statt harte Kodierung Abfragen verwenden? Stored Procs wird Wartbarkeit erhöhen und mehr Sicherheit für Dinge wie SQL Zwischenruf Angriffe bieten.

In der Klasse ist wahrscheinlich am besten - wenn die Abfragen sind lang genug, dass das Entkommen ist ein Problem, das Sie wahrscheinlich entweder suchen zu wollen gespeicherten Prozeduren oder die Abfrage zu vereinfachen

.

Eine Möglichkeit ist die Verwendung iBatis . Es ist ziemlich leicht im Vergleich zu einem ausgewachsenen ORM wie Hibernate, sondern ein Mittel, um Ihre SQL-Abfragen außerhalb Ihrer .java-Dateien zu speichern

Wir speichern alle unsere SQL in einer Klasse, die als ein Bündel von static final Saiten. Zur besseren Lesbarkeit wir es über ein paar Zeilen verteilt verketteten + verwenden. Außerdem bin ich nicht sicher, wenn Sie irgend etwas entkommen müssen -. „Strings“ werden in einfachen Anführungszeichen in SQL eingeschlossen

Wir hatten ein Projekt, bei dem wir den genauen Ansatz Sie sind, außer dass wir jede Abfrage zu einer separaten Textdatei externalisiert. Jede Datei wurde (einmal) mit Spring Resource Rahmen gelesen und die Anwendung funktioniert über eine Schnittstelle wie folgt:

public interface SqlResourceLoader {
    String loadSql(String resourcePath);
}

Ein klarer Vorteil mit dabei war, dass in einem nicht-Escape-Format für eine einfachere Fehlersuche erlaubt die SQL mit - nur die Datei in ein Abfrage-Tool lesen. Wenn Sie mehr als ein paar Abfragen moderater Komplexität haben, mit un Umgang / Flucht vom / zum Code beim Testen und Debuggen (insbesondere für Tuning) war es von unschätzbarem Wert.

Wir hatten auch ein paar verschiedene Datenbanken zu unterstützen, so dass es erlaubt, die Plattform noch einfacher für den Austausch.

auf der Frage Basierend wie Sie es erklären, gibt es keine wirkliche Wahl hier außer es im Code zu halten, und loszuwerden, die / n Zeichen überall. Dies ist die einzige Sache, die Sie als Beeinträchtigung der Lesbarkeit erwähnt und sie sind absolut überflüssig.

Es sei denn, Sie haben andere Probleme mit ihm in dem Code Ihr Problem ist leicht zu lösen ist.

Ich könnte mir vorstellen, dass eine Abfrage in einer externen Datei zu speichern, und hat dann die Anwendung, um sie lesen, wenn eine riesige Sicherheitslücke benötigt präsentiert.

Was passiert, wenn ein böser Drahtzieher Zugriff auf diese Datei hat und ändert Ihre Abfrage?

Zum Beispiel Änderungen

 select a from A_TABLE;

 drop table A_TABLE;

oder

 update T_ACCOUNT set amount = 1000000

Plus, fügt es die Komplexität von zwei Dinge mantain mit: Java-Code und SQL-Abfrage-Dateien.

EDIT: Ja, können Sie Ihre Fragen ändern, ohne Ihre Anwendung neu zu kompilieren. Ich sehe nicht die große Sache dort. Sie könnten Klassen neu kompilieren, die halten / erstellen sqlQueries nur, wenn das Projekt zu groß ist. Außerdem, wenn Dokumentation ist schlecht, vielleicht würden Sie die falsche Datei zu ändern am Ende, und das wird in einen riesigen stillen Fehler machen. Keine Ausnahme oder Fehlercodes ausgelöst werden, und wenn man bedenkt, was du getan hast, kann es zu spät sein.

Ein anderer Ansatz eine Art SQLQueryFactory zu haben wäre, gut dokumentiert und mit Methoden, die eine SQL-Abfrage zurückgeben, die Sie verwenden möchten.

Zum Beispiel

public String findCheapest (String tableName){

      //returns query.
}

Was Sie brauchen, ist hier SQLJ die eine SQL Java-Preprocessor. Leider anscheinend nahm es nie weg, obwohl ich einige IBM gesehen haben und Oracle Implementierungen. Aber sie sind ziemlich veraltet.

Wenn ich Sie wäre, und hatte viele, viele Abfragen auf dem System, würde ich sie in einer separaten Datei gespeichert und laden Sie sie in Laufzeit .

Aus meiner Erfahrung ist es besser, die SQL-Anweisungen in dem Code nicht verlassen sie trennt, macht die Dinge besser verwaltbar (wie Annotationen vs. Konfigurationsdateien), aber jetzt habe ich eine Diskussion mit einem Teammitglied über sie.

Ich habe ein kleines Programm, das Zugriff auf die Zwischenablage und Flucht / unscape Klartext mit Java Stringliterale.

Ich habe es als eine Verknüpfung auf der „Quick Start“ Werkzeugleiste so das einzige, was ich tun muß, ist

Ctrl+C, Click jar, Ctrl+V

Entweder, wenn ich möchte, dass mein "Code" in SQL-Tool, oder umgekehrt ausgeführt werden.

Also habe ich in der Regel etwas wie dieses:

String query = 
    "SELECT a.fieldOne, b.fieldTwo \n"+
    "FROM  TABLE_A a, TABLE b \n"+ 
    "... etc. etc. etc";


logger.info("Executing  " + query  );

PreparedStatement pstmt = connection.prepareStatement( query );
....etc.

Welche umgewandelt in wird:

    SELECT a.fieldOne, b.fieldTwo 
    FROM  TABLE_A a, TABLE b
    ... etc. etc. etc

Entweder, weil bei einigen Projekten kann ich nicht separate Dateien erstellen, oder weil ich Paranoiker bin und ich fühle mich einige Bits / gelöscht werden eingeführt, während aus der externen Datei zu lesen (in der Regel eines unsichtbaren \ n, die

machen
select a,b,c 
from 

in

select a,b,cfrom 

IntelliJ Idee macht das gleiche für Sie automatisch, sondern nur von Ebene zu Code.

Hier ist eine alte Version, die ich gewonnen. Es ist ein bisschen gebrochen und nicht verarbeitet?.

Lassen Sie mich wissen, wenn jemand es verbessert.

import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;

/**
 * Transforms a plain string from the clipboard into a Java 
 * String literal and viceversa.
 * @author <a href="http://stackoverflow.com/users/20654/oscar-reyes">Oscar Reyes</a>
 */
public class ClipUtil{

    public static void main( String [] args ) 
                                throws UnsupportedFlavorException,
                                                     IOException {

        // Get clipboard
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Clipboard clipboard = toolkit.getSystemClipboard();

        // get current content.
        Transferable transferable = clipboard.getContents( new Object() ); 
        String s = ( String ) transferable.getTransferData( 
                                                DataFlavor.stringFlavor );

        // process the content
        String result = process( s );

        // set the result
        StringSelection ss = new StringSelection( result );
        clipboard.setContents( ss, ss );

    }
    /**
     * Transforms the given string into a Java string literal 
     * if it represents plain text and viceversa. 
     */
    private static String process( String  s ){
        if( s.matches( "(?s)^\\s*\\\".*\\\"\\s*;$" ) ) {
            return    s.replaceAll("\\\\n\\\"\\s*[+]\n\\s*\\\"","\n")
                       .replaceAll("^\\s*\\\"","")
                       .replaceAll("\\\"\\s*;$","");
        }else{
            return     s.replaceAll("\n","\\\\n\\\" +\n \\\" ")
                        .replaceAll("^"," \\\"")
                        .replaceAll("$"," \\\";");
        }
    }
}

Da Sie bereits Frühling verwenden, warum nicht die SQL in der Frühjahr-Konfigurationsdatei setzen und DI in die DAO-Klasse? Das ist eine einfache Möglichkeit, die SQL-Zeichenfolge externalisieren.

HTH Tom

Ich ziehe die externe Option. Ich unterstütze mehrere Projekte und finden es viel schwieriger interne SQL zu unterstützen, weil Sie kompilieren müssen und erneut bereitstellen jedes Mal, wenn eine leichte Änderung der SQL machen wollen. die SQL in einer externen Datei ermöglicht es Ihnen, große und kleine Veränderungen leicht mit weniger Risiko zu machen. Wenn Sie nur die SQL-Bearbeitung, gibt es keine Chance, in einem Tippfehler von Putten, die die Klasse bricht.

Für Java, verwende ich die Eigenschaften Klasse, die die SQL in einer .properties-Datei repräsentiert, mit dem Sie die SQL-Abfragen um passieren lässt, wenn Sie die Abfragen erneut verwenden möchten, anstatt die Datei in mehrere Male gelesen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top