12

現在のプロジェクトは (さまざまな理由で) Hibernate を使用しておらず、Spring の SimpleJdbc サポートを使用してすべての DB 操作を実行しています。すべての CRUD 操作を抽象化するユーティリティ クラスがありますが、複雑な操作はカスタム SQL クエリを使用して実行されます。

現在、クエリはサービス クラス自体の内部に文字列定数として格納され、SimpleJdbcTemplate によって実行されるユーティリティに供給されます。私たちは、可読性と保守性のバランスを取らなければならない行き詰まりに陥っています。クラス自体の内部の SQL コードは、それを使用するコードと共に常駐するため、より保守しやすくなります。一方、これらのクエリを外部ファイル (フラットまたは XML) に格納すると、エスケープされた Java 文字列構文と比較して、SQL 自体が読みやすくなります。

誰かが同様の問題に遭遇しましたか? 良いバランスとは?カスタム SQL をプロジェクトのどこに保管しますか?

サンプルクエリは次のとおりです。

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

これは、フラットな SQL ファイルでは次のようになります。

--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)
4

16 に答える 16

9

私は、SQL を Java クラス内の文字列として、および実行時にロードされる個別のファイルとして格納しました。私は2つの理由から後者を大いに好みました。まず、コードが大幅に読みやすくなります。次に、別のファイルに保存すると、SQL を分離してテストする方が簡単です。それに加えて、クエリが別々のファイルにあるときは、SQL が得意な人に手伝ってもらう方が簡単でした。

于 2009-05-07T15:24:53.460 に答える
5

現在、同じ理由で、私はこれにも遭遇しました-春のjdbcに基づくプロジェクト。私の経験では、SQL自体にロジックを含めることは素晴らしいことではありませんが、実際にはこれに適した場所はなく、アプリケーションコードを配置することは、データベースに実行させるよりも遅く、必ずしも明確であるとは限りません.

これで私が見た最大の落とし穴は、複数のバリエーションで、SQL がプロジェクト全体に増殖し始めるところです。「FOOからA、B、Cを取得」。「Foo から A、B、C、E を取得する」など。この種の急増は、プロジェクトが特定のクリティカル マスに達したときに特に発生する可能性があります。10 個のクエリでは問題のようには見えないかもしれませんが、プロジェクト全体に 500 個のクエリが散らばっていると、既に何かを行っているかどうかを判断するのがはるかに難しくなります。 . 基本的な CRUD 操作を抽象化することで、ゲームの先を行くことができます。

私の知る限り、最善の解決策は、コード化された SQL と厳密に一貫性を保つことです - コメント、テスト、および一貫した場所で。私たちのプロジェクトには、コメントされていない 50 行の SQL クエリがあります。彼らはどういう意味ですか?知るか?

外部ファイルのクエリに関しては、これが何を買うのかわかりません-あなたはまだSQLに依存しており、SQLをクラスから除外するという(疑わしい)美的改善を除いて、あなたのクラスはそれでもSQLに依存しています。たとえば、通常はリソースを分離して、置換リソースをプラグインする柔軟性を確保しますが、置換SQLクエリをプラグインすることはできませんでした。これは、クラスのセマンティックな意味を変更したり、全て。したがって、それは幻想的なコードのクリーンさです。

于 2009-05-07T15:03:17.333 に答える
2

かなり根本的な解決策は、Groovy を使用してクエリを指定することです。Groovy は、複数行の文字列と文字列補間 (面白いことに GStrings として知られています) を言語レベルでサポートしています。

たとえば、Groovy を使用すると、上記で指定したクエリは単純に次のようになります。

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) """

このクラスは、Java で定義されているかのように、Java コードからアクセスできます。

String query = QueryFactory.FIND_ALL_BY_CHEAPEST_AND_PRODUCT_IDS;

SQLクエリの見栄えを良くするためだけにGroovyをクラスパスに追加するのは、ちょっとした「大ハンマー」の解決策であることは認めますが、Springを使用している場合は、すでにクラスパスにGroovyを使用している可能性がかなりあります.

また、コードを改善するために (Java の代わりに) Groovy を使用できるプロジェクトの他の多くの場所がある可能性があります (特に、Groovy が Spring によって所有されている現在)。例としては、テスト ケースの作成、Java Bean の Groovy Bean への置き換えなどがあります。

于 2009-05-07T15:15:55.010 に答える
1

ストアド プロシージャを使用します。Oracle Fine Grain Access を使用しているため、これは私たちにとって良いことです。これにより、関連する手順へのアクセスを制限することにより、ユーザーが特定のレポートまたは検索結果を表示することを制限できます。また、パフォーマンスが少し向上します。

于 2009-05-07T16:04:22.647 に答える
0

各クエリを個別のテキストファイルに外部化したことを除いて、現在のアプローチを使用したプロジェクトがありました。各ファイルは、SpringのResourceLoaderフレームワークを使用して(1回)読み込まれ、アプリケーションは次のようなインターフェイスを介して機能しました。

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

これの明確な利点は、エスケープされていない形式のSQLを使用すると、デバッグが容易になることです。ファイルをクエリツールに読み込むだけです。中程度の複雑さのクエリがいくつかあると、テストとデバッグ(特にチューニングのため)中にコードへの/からの脱出を処理することは非常に貴重でした。

また、いくつかの異なるデータベースをサポートする必要があったため、プラットフォームをより簡単に交換できました。

于 2009-05-07T15:08:58.037 に答える
0

あなたが説明した問題に基づいて、ここではそれをコードに保持し、すべての/n文字を取り除く以外に実際の選択肢はありません。これは、読みやすさに影響を与えるとあなたが言った唯一のことであり、それらは絶対に不要です。

コードに含まれていることに他の問題がない限り、問題は簡単に解決されます。

于 2009-05-07T15:10:15.180 に答える
0

私の経験から、SQL ステートメントをコード内に残しておく方が、それらを分離しない方が保守性が高くなります (注釈と構成ファイルのように) が、今、チーム メンバーとそれについて議論しています。

于 2009-05-07T17:32:26.163 に答える
0

クエリを外部ファイルに保存し、必要に応じてアプリケーションにそれを読み取らせると、巨大なセキュリティ ホールが生じると想像できます。

悪の首謀者がそのファイルにアクセスしてクエリを変更した場合はどうなりますか?

たとえば、変更

 select a from A_TABLE;

 drop table A_TABLE;

また

 update T_ACCOUNT set amount = 1000000

さらに、Java コードと SQL クエリ ファイルの 2 つを管理する必要があるという複雑さが増します。

編集: はい、アプリを再コンパイルせずにクエリを変更できます。私はそこに大したことは見ていません。プロジェクトが大きすぎる場合は、sqlQueries のみを保持/作成するクラスを再コンパイルできます。さらに、ドキュメンテーションが貧弱であると、間違ったファイルを変更してしまう可能性があり、それが巨大なサイレント バグに発展する可能性があります。例外やエラー コードはスローされず、実行したことに気付いたときには手遅れになる可能性があります。

別のアプローチは、十分に文書化された、使用したい SQL クエリを返すメソッドを備えた、ある種の SQLQueryFactory を用意することです。

例えば

public String findCheapest (String tableName){

      //returns query.
}
于 2009-05-07T14:57:13.587 に答える
0

ハード コーディング クエリの代わりにストアド プロシージャを使用しないのはなぜですか? ストアド プロシージャは保守性を向上させ、SQL インタージェクション攻撃などに対するセキュリティを強化します。

于 2009-05-07T14:58:08.657 に答える
0

クラスではおそらく最高です-クエリが十分に長く、エスケープが問題になる場合は、おそらくストアドプロシージャを調べるか、クエリを簡素化することをお勧めします。

于 2009-05-07T15:00:16.920 に答える
0

すでに Spring を使用しているため、SQL を Spring 構成ファイルに入れ、それを DAO クラスに DI してみませんか? これは、SQL 文字列を外部化する簡単な方法です。

HTH トム

于 2009-05-14T16:08:06.903 に答える
0

1 つのオプションはiBatisを使用することです。Hibernate のような本格的な ORM に比べてかなり軽量ですが、SQL クエリを .java ファイルの外部に格納する手段を提供します。

于 2009-05-07T15:03:54.460 に答える
0

私は外部オプションを好みます。私は複数のプロジェクトをサポートしていますが、SQL にわずかな変更を加えるたびにコンパイルして再デプロイする必要があるため、内部 SQL をサポートするのは非常に難しいと感じています。SQL を外部ファイルに格納することで、大小さまざまな変更をリスクを抑えて簡単に行うことができます。SQL を編集しているだけなら、クラスを壊すタイプミスをする可能性はありません。

Java の場合、.properties ファイルで SQL を表す Properties クラスを使用します。これにより、ファイルを複数回読み取るのではなく、クエリを再利用したい場合に SQL クエリを渡すことができます。

于 2009-09-28T15:29:32.337 に答える
0

すべての SQL を静的な最終文字列の束としてクラスに格納します。読みやすくするために、+ を使用して連結された数行に広げます。また、何かをエスケープする必要があるかどうかはわかりません.SQLでは「文字列」は一重引用符で囲まれています。

于 2009-05-07T15:05:47.123 に答える
0

クリップボードにアクセスし、Java 文字列リテラルを使用してプレーン テキストをエスケープ/エスケープ解除する小さなプログラムがあります。

「クイック起動」ツールバーのショートカットとして持っているので、私がする必要があるのは

Ctrl+C, Click jar, Ctrl+V

「コード」を SQL ツールに実行したい場合、またはその逆の場合。

だから私は通常、次のようなものを持っています:

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.

これは次のように変換されます:

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

一部のプロジェクトでは個別のファイルを作成できないか、または私がパラノイアであり、外部ファイルからの読み取り中に一部のビットが挿入/削除されると感じているためです (通常、目に見えない \n を作成します

select a,b,c 
from 

の中へ

select a,b,cfrom 

IntelliJ のアイデアは自動的に同じことを行いますが、単純なものからコードまでです。

ここに私が回復した古いバージョンがあります。少し壊れていて、? を処理できません。

誰かがそれを改善したら教えてください。

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("$"," \\\";");
        }
    }
}
于 2009-05-08T20:42:32.383 に答える
0

ここで必要なのは、SQL Java プリプロセッサであるSQLJです。悲しいことに、私はIBMOracleの実装を見たことがありますが、どうやらうまくいきませんでした。しかし、それらはかなり時代遅れです。

私があなたで、システムに大量のクエリがある場合、それらを別のファイルに保存し、実行時にロードします。

于 2009-05-07T15:36:53.837 に答える