JavaでSQL文字列を作成する最もクリーンな方法


107

データベース操作(更新、削除、挿入、選択など)を行うSQL文字列を構築したい-何百万もの「+」と引用符を使用したひどい文字列連結メソッドではなく、せいぜい読めない-そこでより良い方法でなければなりません。

私はMessageFormatを使用することを考えましたが、それはユーザーメッセージに使用されることになっていますが、妥当な仕事をすると思いますが、Java sqlライブラリのSQLタイプの操作にもっと整合したものがあるはずです。

Groovyは良いでしょうか?

回答:


76

まず、準備されたステートメントでクエリパラメータを使用することを検討してください。

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

実行できるもう1つのことは、すべてのクエリをプロパティファイルに保存することです。たとえば、querys.propertiesファイルでは、上記のクエリを配置できます。

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

次に、簡単なユーティリティクラスを使用します。

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

}

次のようにクエリを使用できます。

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

これはかなり単純な解決策ですが、うまく機能します。


1
私はこのようなクリーンなSQLビルダーを使用することを好みます:mentabean.soliveirajr.com
TraderJoeChicago

2
不要なときにインスタンス化しないようInputStreamに、if (props == null)ステートメントの内側に置くことをお勧めします。
SyntaxRules 2013

64

任意のSQLの場合は、jOOQを使用します。jOOQは現在サポートしているSELECTINSERTUPDATEDELETETRUNCATE、とMERGE。次のようなSQLを作成できます。

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

SQL文字列を取得する代わりに、jOOQを使用して実行することもできます。見る

http://www.jooq.org

(免責事項:私はjOOQの背後にある会社で働いています)


「5」、「8」などの異なる値を使用して事前にdbmsにステートメントを解析させることができないので、これは多くの場合不十分な解決策ではありませんか?jooqで実行すれば解決できると思いますか?
Vegard 2013

@Vegard:jOOQがSQL出力でバインド値をレンダリングする方法を完全に制御できます:jooq.org/doc/3.1/manual/sql-building/bind-values。つまり、"?"バインド値をレンダリングするかインラインにするかを選択できます。
Lukas Eder 2013

はい、しかし、SQLを構築するためのクリーンな方法に関しては、JOOQを使用して実行していない場合、これは私の目にはやや面倒なコードになります。この例では、Aを1、Bを2などに設定していますが、JOOQで実行していない場合は、実行時にもう一度行う必要があります。
Vegard

1
@Vegard:変数をjOOQ APIに渡してSQLステートメントを再構築することを妨げるものは何もありません。また、jooq.org/javadoc/latest/org/jooq/Query.html#getBindValues()を使用してバインド値を順番に抽出するか、jooq.org/javadoc/latest/org/jooqを使用して名前でバインド値を名前で抽出できます。/Query.html#getParams()。私の回答には非常に単純な例が含まれています...しかし、これがあなたの懸念に対応するかどうかはわかりませんか?
Lukas Eder 2013

2
その高価なソリューション。
ソーター、2015年

15

考慮すべき1つのテクノロジーはSQLJです。これは、SQLステートメントをJavaに直接埋め込む方法です。簡単な例として、TestQueries.sqljと呼ばれるファイルに次のものがあるとします。

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

.sqljファイルを取得してピュアJavaに変換する追加のプリコンパイルステップがあります。つまり、次のように区切られた特別なブロックを探します。

#sql
{
    ...
}

それらをJDBC呼び出しに変換します。SQLJを使用することには、いくつかの重要な利点があります。

  • JDBCレイヤーを完全に抽象化します-プログラマーはJavaとSQLについて考えるだけで済みます
  • トランスレーターは、コンパイル時にデータベースに対してクエリの構文などをチェックできます
  • ":"接頭辞を使用してクエリ内のJava変数を直接バインドする機能

主要なデータベースベンダーのほとんどには、トランスレータの実装があるので、必要なものすべてを簡単に見つけることができます。


ウィキペディアによると、これは現在古くなっています。
Zeus

1
執筆時点(2016年1月)では、SQLJはWikipediaでは「古くなった」と呼ばれ、参照はありません。それは正式に放棄されましたか?その場合は、この回答の上部に警告を表示します。
アシュリーマーサー

NBこのテクノロジーは、たとえばOracle最新バージョン12cなどでも引き続きサポートされています。これは最新の標準ではないことを認めますが、それでも機能し、他のシステムでは利用できないいくつかの利点(DBに対するクエリのコンパイル時の検証など)があります。
アシュリーマーサー


9

Spring JDBCを見てみましょう。SQLをプログラムで実行する必要があるときはいつでも使用します。例:

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

これは、あらゆる種類のSQL実行、特にクエリに最適です。完全なORMの複雑さを追加することなく、結果セットをオブジェクトにマップするのに役立ちます。


実際に実行されたSQLクエリを取得するにはどうすればよいですか?ログに記録したい。
kodmanyagha

5

私はSpringの名前付きJDBCパラメータを使用する傾向があるため、「select * from blah where colX = ':someValue'」のような標準文字列を記述できます。それはかなり読みやすいと思います。

別の方法は、別の.sqlファイルで文字列を提供し、ユーティリティメソッドを使用して内容を読み取ることです。

ああ、Squillも一見の価値があります:https : //squill.dev.java.net/docs/tutorial.html


BeanPropertySqlParameterSourceを使用していると思いますか?私はあなたにほとんど同意します、私が今述べたクラスは厳密にBeanを使用する場合はクールですが、それ以外の場合はカスタムParameterizedRowMapperを使用してオブジェクトを構築することをお勧めします。
Esko

結構です。名前付きのJDBCパラメーターで任意のSqlParameterSourceを使用できます。Beanの種類ではなく、MapSqlParameterSourceを使用するという私のニーズに適していました。いずれにせよ、それは良い解決策です。ただし、RowMappersはSQLパズルの反対側、つまり結果セットをオブジェクトに変換することを扱います。
GaryF、2009年

4

次に、HibernateのようなORMを使用するための推奨事項について説明します。ただし、それでもうまくいかない状況は確かにあるので、この機会に、私が書いてきたいくつかのことを宣伝します。SqlBuilderは、「ビルダー」スタイルを使用してSQLステートメントを動的に構築するためのJavaライブラリです。それはかなり強力でかなり柔軟性があります。


4

アドホックレポートの目的で非常に動的なSQLステートメントを構築する必要があるJavaサーブレットアプリケーションに取り組んでいます。アプリの基本的な機能は、一連の名前付きHTTPリクエストパラメータを事前にコード化されたクエリにフィードし、適切にフォーマットされた出力のテーブルを生成することです。私は、Spring MVCと依存関係注入フレームワークを使用して、すべてのSQLクエリをXMLファイルに格納し、それらをテーブルの書式設定情報と共にレポートアプリケーションに読み込みました。最終的に、レポート要件は既存のパラメーターマッピングフレームワークの機能よりも複雑になり、自分で作成する必要がありました。これは、開発における興味深い演習であり、他の何よりもはるかに堅牢なパラメータマッピングのフレームワークを生み出しました。

新しいパラメータマッピングは次のようになりました。

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

結果のフレームワークの優れた点は、適切な型チェックと制限チェックを使用して、HTTP要求パラメーターを直接クエリに処理できることでした。入力の検証に追加のマッピングは必要ありません。上記のクエリ例では、serverIdという名前のパラメーター がチェックされ、整数にキャストでき、0〜50の範囲にあることが確認されます。パラメータappIdは、長さ制限が50の整数の配列として処理されます。フィールドshowOwnerが存在し、「true」に設定されている場合、引用符内のSQLのビットが、オプションのフィールドマッピング用に生成されたクエリに追加されます。フィールドさらにパラメーターマッピングを備えたSQLのオプションセグメントを含む、いくつかのパラメータータイプマッピングが利用可能です。開発者が思いつく限りの複雑なクエリマッピングが可能です。特定のクエリがPreparedStatementを介して最終的なマッピングを持つのか、それとも単に事前に作成されたクエリとして実行されるのかを決定するためのコントロールもレポート構成にあります。

サンプルのHttpリクエスト値の場合:

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

次の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

SpringやHibernate、またはそれらのフレームワークの1つは、型を検証し、配列などの複雑なデータ型を許可する、より堅牢なマッピングメカニズムを提供する必要があると私は本当に思います。私は自分の目的のためだけにエンジンを作成しましたが、一般的なリリースではまだ十分に読まれていません。現時点ではOracleクエリでのみ機能し、すべてのコードは大企業に属しています。いつか自分のアイデアを取り入れて新しいオープンソースフレームワークを構築するかもしれませんが、既存の大手企業の1人が挑戦してくれることを期待しています。


3

なぜすべてのSQLを手動で生成するのですか?HibernateのようなORMを見たことがありますか?プロジェクトによっては、必要なものの少なくとも95%を実行し、生のSQLよりもクリーンな方法で実行します。パフォーマンスの最後のビットを取得する必要がある場合は、手動で調整する必要があるSQLクエリ。



3

GoogleはRoom Persitence Libraryと呼ばれるライブラリを提供しています。これは、Androidアプリ用の SQL 非常に簡潔に記述する方法を提供します。基本的には、基礎となるSQLiteデータベースの抽象化レイヤーです。以下は公式ウェブサイトの短いコードスニペットです。

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

ライブラリの公式ドキュメントには、より多くの例とより良いドキュメントがあります。

Java ORMであるMentaBeanと呼ばれるものもあります。それは素晴らしい機能を備えており、SQLを書くのにかなり単純な方法のようです。


あたりとしてルームのドキュメントRoom provides an abstraction layer over SQLite to allow fluent database access while harnessing the full power of SQLite。したがって、これはRDBMSの汎用ORMライブラリではありません。主にAndroidアプリ向けです。
RafiAlhamd

2

XMLファイルを読み取ります。

XMLファイルから読み取ることができます。保守と操作が簡単です。標準のSTaX、DOM、SAXパーサーがあり、Javaで数行のコードにすることができます。

属性をさらに活用する

SQLをさらに活用するために、タグの属性を持ついくつかのセマンティック情報を持つことができます。これは、メソッド名やクエリの種類など、コーディングの手間を省くものであれば何でもかまいません。

維持

jarの外側にxmlを配置して、簡単に維持できます。プロパティファイルと同じ利点。

変換

XMLは拡張可能で、他の形式に簡単に変換できます。

使用事例

Metamugはxmlを使用して、SQLでRESTリソースファイルを構成します。


必要に応じて、yamlまたはjsonを使用できます。プレーンプロパティファイルに保存するよりも優れています
ソーター

問題は、SQLを構築する方法です。SQLを構築するために、XML、パーサー、検証などを使用する必要がある場合は、負担がかかります。XMLを使用してSQLを構築する初期の試みのほとんどは、Annotationを支持して却下されています。受け入れ答えによってピョートルKochańskiがあり、シンプルでエレガントかつポイントに-問題と保守性を解決します。注: 異なる言語でより良いSQLを維持するための代替方法はありません。
RafiAlhamd

I don't see a reason to make use of XML. 編集できなかったため、以前のコメントを削除しました。
RafiAlhamd

1

SQL文字列をプロパティファイルに入れてから読み込むと、SQL文字列をプレーンテキストファイルに保存できます。

SQLタイプの問題は解決しませんが、少なくともTOADまたはsqlplusからのコピーと貼り付けがはるかに簡単になります。


0

複数行にまたがるPreparedStatementsの長いSQL文字列(テキストファイルで簡単に提供してリソースとして読み込むことができる)を除いて、文字列の連結をどのように取得しますか?

SQL文字列を直接作成していませんか?それはプログラミングにおける最大のノーノーです。PreparedStatementsを使用して、データをパラメーターとして指定してください。SQLインジェクションの可能性を大幅に減らします。


しかし、Webページを公開していない場合、SQLインジェクションは関連する問題ですか?
Vidar

4
SQLインジェクションは、偶発的にも意図的にも発生する可能性があるため、常に適切です。
sleske 2009年

1
@Vidar- 現在、ウェブページを公開していない可能性がありますが、「常に」内部にあるコードでさえ、多くの場合、最終的にはある種の外部公開のあるポイントに到達します。そして、それは...後で問題のために全体のコードベースを監査する必要があるよりも、それを右の初めてのラウンドを行うために、両方の、より速く、より安全です
アンジェイ・ドイル

4
PreparedStatementも文字列から作成する必要がありますか?
スチュワート

はい。ただし、安全なPreparedStatementを作成する限り、文字列からPreparedStatementを作成しても安全です。おそらく、PreparedStatementBuilderクラスを作成してそれらを生成し、連結することの混乱を隠す必要があります。
JeeBee 2013年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.