Javaの動的検索コードを新人エンジニア向けに解説|AND・OR条件とSQLインジェクション対策の基本

こんにちは。ゆうせいです。

今回は、Javaで「検索条件を自由に組み合わせてcarsテーブルを検索するコード」を、新人エンジニア向けに解説します。

今回のコードは、車情報を管理するcarsテーブルに対して、price、name、car_id、deleted_atなどの条件を指定し、ANDやORで組み合わせて検索できるようにしています。

たとえば、次のような検索ができます。

価格が200万円より高い
かつ
名前に「車」という文字が含まれる

SQLで書くと、だいたい次のようなイメージです。

SELECT * FROM cars
WHERE price > ?
AND name LIKE ?




このコードのポイントは、検索条件を固定で書くのではなく、ConditionというオブジェクトのリストからSQLを動的に作っているところです。

ただし、SQLを動的に作る場合は、SQLインジェクションに注意しなければいけません。

SQLインジェクションとは、ユーザーが入力した文字列を悪用して、想定外のSQLを実行させる攻撃です。

家の玄関でたとえるなら、本来は「名前を確認して入る」だけの場所なのに、悪意のある人が合鍵のような文字列を差し込んで、勝手に中へ入ろうとするイメージです。

今回のコードでは、フィールド名、比較演算子、結合演算子をチェックし、値はPreparedStatementのプレースホルダにバインドすることで、安全性を高めています。

このコードがやりたいこと

まず、このコード全体の目的を整理しましょう。

目的内容
複数条件で検索するprice、nameなどを組み合わせてcarsテーブルを検索する
ANDやORを使う条件同士をANDまたはORでつなげる
SQLを動的に作る条件リストに応じてWHERE句を作る
SQLインジェクションを防ぐ使えるフィールド名や演算子を制限し、値はPreparedStatementで渡す
検索結果をCarDtoに詰めるResultSetから値を取り出してCarDtoのリストとして返す

つまり、このコードは「自由度の高い検索機能」を作るためのコードです。

検索画面で、価格、名前、削除日時などを組み合わせて検索したいときに使う考え方です。

前提になるcarsテーブル

今回のコードでは、carsテーブルに次のようなカラムがある前提です。

カラム名意味
car_id車のID
name車の名前
price価格
deleted_at削除日時

deleted_atは、論理削除でよく使われるカラムです。

論理削除とは、データを実際にDELETEするのではなく、deleted_atに日時を入れて「削除済み扱い」にする方法です。

ゴミ箱に書類を入れるようなものです。

書類そのものはまだ存在していますが、普段の一覧では見せないようにします。

Conditionとは何か

このコードでは、Conditionというクラスが使われています。

conditions.add(new Condition("price", ">", 2000000, "AND"));
conditions.add(new Condition("name", "LIKE", "%車%", "AND"));




Conditionは、検索条件を1つ表すための入れ物だと考えてください。

項目意味
fieldpriceどのカラムを検索するか
comparator>どの比較演算子を使うか
value2000000比較する値
operatorAND次の条件とどうつなぐか

1つ目のConditionは、次の条件を表しています。

price > 2000000

2つ目のConditionは、次の条件を表しています。

name LIKE '%車%'

この2つをANDでつなぐと、次のような検索になります。

price > 2000000 AND name LIKE '%車%'

Conditionは、検索条件を部品化したものです。

レゴブロックでたとえるなら、1つ1つのConditionが小さなブロックです。

それらをANDやORでつなげて、検索SQLという大きな形を作っています。

isValidFieldメソッドの役割

最初に、フィールド名をチェックするメソッドを見てみましょう。

private boolean isValidField(String field) {
    List<String> validFields = Arrays.asList("car_id", "name", "price", "deleted_at");
    return validFields.contains(field);
}




このメソッドは、指定されたフィールド名が使ってよいカラム名かどうかを確認しています。

使ってよいフィールドは、次の4つだけです。

許可されているフィールド
car_id
name
price
deleted_at

たとえば、fieldにpriceが渡された場合、validFieldsの中にpriceがあるのでtrueを返します。

一方で、fieldに次のような怪しい文字列が渡された場合はfalseになります。

price; DROP TABLE cars;

このような文字列をそのままSQLに結合すると危険です。

だから、使ってよいフィールド名をあらかじめ決めておき、それ以外は拒否しています。

このような考え方をホワイトリスト方式といいます。

ホワイトリスト方式とは、「許可したものだけ通す」考え方です。

学校の入場でたとえるなら、名簿に名前がある人だけ校内に入れる仕組みです。

名簿にない人は、たとえそれっぽい名前を名乗っても入れません。

isValidComparatorメソッドの役割

次に、比較演算子をチェックするメソッドです。

private boolean isValidComparator(String comparator) {
    List<String> validComparators = Arrays.asList("=", "!=", ">", "<", ">=", "<=", "LIKE");
    return validComparators.contains(comparator.toUpperCase());
}




比較演算子とは、値を比べるための記号です。

比較演算子意味
=等しいprice = 2000000
!=等しくないname != 'プリウス'
>より大きいprice > 2000000
<より小さいprice < 3000000
>=以上price >= 2000000
<=以下price <= 3000000
LIKE部分一致検索name LIKE '%車%'

このメソッドでは、比較演算子も許可リストに入っているものだけ使えるようにしています。

comparator.toUpperCase()としているので、likeと入力されてもLIKEとして判定できます。

LIKEは、文字列の一部が一致するかどうかを調べる演算子です。

たとえば、次の条件は、nameに「車」という文字が含まれるデータを探します。

name LIKE '%車%'

%はワイルドカードです。

ワイルドカードとは、「何文字でもよい」という意味の記号です。

トランプのジョーカーのようなものです。

ジョーカーは何のカードにもなれるように、%は任意の文字列に対応します。

isValidOperatorメソッドの役割

次に、結合演算子をチェックするメソッドです。

private boolean isValidOperator(String operator) {
    List<String> validOperators = Arrays.asList("AND", "OR");
    return validOperators.contains(operator.toUpperCase());
}




結合演算子とは、複数の条件をつなぐための言葉です。

結合演算子意味
AND両方の条件を満たすprice > 2000000 AND name LIKE '%車%'
ORどちらかの条件を満たすprice > 2000000 OR name LIKE '%車%'

ANDは「かつ」です。

ORは「または」です。

たとえば、テストで考えてみましょう。

「数学が80点以上 AND 英語が80点以上」なら、数学も英語も両方80点以上である必要があります。

「数学が80点以上 OR 英語が80点以上」なら、どちらか一方が80点以上なら条件を満たします。

このコードでは、ANDとOR以外の文字列を拒否しています。

なぜなら、結合演算子もSQLに直接文字列結合されるからです。

フィールド名や演算子は、PreparedStatementの?で置き換えられません。

そのため、必ずホワイトリストでチェックする必要があります。

searchCarListByConditionsメソッドの全体像

次に、メインとなるsearchCarListByConditionsメソッドを見ていきます。

public List<CarDto> searchCarListByConditions(List<Condition> conditions) {




このメソッドは、Conditionのリストを受け取り、検索結果としてCarDtoのリストを返します。

入力出力
List<Condition> conditionsList<CarDto> carList

List<Condition>は、検索条件の一覧です。

List<CarDto>は、検索結果の車一覧です。

DTOとは、Data Transfer Objectの略です。

DTOは、データを運ぶための入れ物です。

宅配便の箱でたとえると、DBから取り出したcar_id、name、price、deleted_atをCarDtoという箱に詰めて、画面や呼び出し元へ渡します。

検索結果を入れるリストを作る

メソッドの最初では、検索結果を入れるためのリストを作っています。

List<CarDto> carList = new ArrayList<>();




このcarListに、検索で見つかった車情報を1件ずつ追加していきます。

買い物カゴでたとえるなら、検索結果として見つかった商品をカゴに入れていくイメージです。

条件がない場合は全件返す

次の部分では、条件がない場合の処理をしています。

if (conditions == null || conditions.isEmpty()) {
    return getCarList(); // 条件なしの場合は全件返す
}




conditionsがnullの場合、または空の場合は、条件なしと判断しています。

状態意味処理
conditions == null条件リスト自体がない全件取得する
conditions.isEmpty()条件リストはあるが中身が空全件取得する

この場合はgetCarList()を呼び出して、carsテーブルの全件を返しています。

検索画面で、何も条件を入れずに検索ボタンを押したら全件表示するような動きです。

ただし、実務では全件取得に注意が必要です。

データが100件なら問題ありません。

でも、100万件あるテーブルで全件取得すると、処理が重くなります。

その場合はLIMITを付けたり、ページングを入れたりする必要があります。

フィールド名・比較演算子・結合演算子をチェックする

次に、すべての条件をチェックしています。

for (Condition cond : conditions) {
    if (!isValidField(cond.getField())) {
        throw new IllegalArgumentException(
                "無効なフィールド名です: " + cond.getField());
    }
    if (!isValidComparator(cond.getComparator())) {
        throw new IllegalArgumentException(
                "無効な比較演算子です: " + cond.getComparator());
    }
    if (!isValidOperator(cond.getOperator())) {
        throw new IllegalArgumentException(
                "無効な結合演算子です: " + cond.getOperator());
    }
}




ここでは、各Conditionについて次の3つを確認しています。

チェック対象チェック内容
field許可されたカラム名か
comparator許可された比較演算子か
operator許可された結合演算子か

もし不正な値があれば、IllegalArgumentExceptionを投げています。

IllegalArgumentExceptionは、「引数が不正です」という意味の例外です。

たとえば、priceやname以外の怪しいフィールド名が渡されたら、そこで処理を止めます。

このチェックはとても大切です。

なぜなら、このあとSQLを文字列として組み立てるからです。

危険な文字列をSQLに混ぜないように、入口で止めているわけです。

遊園地の入口で、危険物を持ち込んでいないかチェックするようなものですね。

SQLを動的に作る

次に、SQLを作っている部分です。

String sql = "SELECT * FROM cars WHERE ";

for (int i = 0; i < conditions.size(); i++) {
    Condition cond = conditions.get(i);
    sql += cond.getField() + " " + cond.getComparator() + " ?";

    if (i < conditions.size() - 1) {
        sql += " " + cond.getOperator() + " ";
    }
}




まず、SQLの先頭を作っています。

SELECT * FROM cars WHERE 




その後、conditionsの数だけループして、WHERE句の条件を追加しています。

たとえば、mainメソッドでは次の条件を作っています。

conditions.add(new Condition("price", ">", 2000000, "AND"));
conditions.add(new Condition("name", "LIKE", "%車%", "AND"));




この条件から作られるSQLは、次のようになります。

SELECT * FROM cars WHERE price > ? AND name LIKE ?




ここで大切なのは、値そのものはSQL文字列に直接結合していない点です。

2000000や%車%は、SQL文字列に直接入れていません。

代わりに?を入れています。

?はプレースホルダと呼ばれます。

プレースホルダとは、あとから安全に値を入れるための場所です。

空欄付きの申込書でたとえると、SQLが申込書で、?が空欄です。

あとからPreparedStatementを使って、空欄に安全に値を入れます。

なぜフィールド名と演算子は文字列結合しているのか

ここで、新人エンジニアが疑問に思いやすい点があります。

「SQLインジェクション対策なら、フィールド名や演算子も?にすればよいのでは?」

実は、PreparedStatementの?で置き換えられるのは、基本的に値です。

カラム名や比較演算子は、?で置き換える対象ではありません。

たとえば、次のような書き方は意図通りには使えません。

SELECT * FROM cars WHERE ? > ?




1つ目の?にpriceを入れても、DBはそれをカラム名としてではなく、値として扱います。

そのため、カラム名や演算子を動的に変えたい場合は、文字列としてSQLを組み立てる必要があります。

だからこそ、isValidField、isValidComparator、isValidOperatorで厳しくチェックしているのです。

まとめると、こうです。

SQLの部品?で渡せるか安全対策
渡せるPreparedStatementでバインドする
カラム名基本的に渡せないホワイトリストでチェックする
比較演算子基本的に渡せないホワイトリストでチェックする
ANDやOR基本的に渡せないホワイトリストでチェックする

この考え方は、動的SQLを書くうえでとても重要です。

PreparedStatementを作る

次に、DB接続とPreparedStatementを作っています。

try (Connection con = getConnection();
     PreparedStatement ps = con.prepareStatement(sql.toString())) {




Connectionは、JavaアプリとDBをつなぐ接続です。

電話でたとえるなら、JavaからDBへ電話をかけている状態です。

PreparedStatementは、SQLを安全に実行するためのオブジェクトです。

PreparedStatementを使うと、?に値を安全にセットできます。

また、このコードではtry-with-resourcesを使っています。

try-with-resourcesとは、処理が終わったら自動でcloseしてくれるJavaの仕組みです。

DB接続やPreparedStatementを閉じ忘れると、リソース不足の原因になります。

蛇口を開けっぱなしにすると水が無駄になるように、DB接続を閉じ忘れるとDBやアプリに負担がかかります。

try-with-resourcesを使うと、蛇口を自動で閉めてくれるような安心感があります。

なお、sqlはStringなので、sql.toString()は書いても動きますが、なくても問題ありません。

PreparedStatement ps = con.prepareStatement(sql)

このように書いても十分です。

条件の値をプレースホルダにバインドする

次に、?に値をセットしています。

for (int i = 0; i < conditions.size(); i++) {
    Object value = conditions.get(i).getValue();
    if (value instanceof Integer) {
        ps.setInt(i + 1, (Integer) value);
    } else {
        ps.setString(i + 1, value.toString());
    }
}




PreparedStatementでは、?の番号は1から始まります。

JavaのListのindexは0から始まります。

そのため、i + 1を使っています。

iPreparedStatementの番号
012000000
12%車%

mainメソッドの条件なら、次のように値がセットされます。

ps.setInt(1, 2000000);
ps.setString(2, "%車%");




完成イメージは次のSQLです。

SELECT * FROM cars WHERE price > 2000000 AND name LIKE '%車%'

ただし、実際には値が安全にバインドされるため、文字列結合より安全です。

ここがSQLインジェクション対策の大事な部分です。

value instanceof Integerの意味

次の部分を見てください。

if (value instanceof Integer) {
    ps.setInt(i + 1, (Integer) value);
} else {
    ps.setString(i + 1, value.toString());
}




instanceofは、「このオブジェクトが指定した型かどうか」を調べる演算子です。

ここでは、valueがInteger型ならsetIntを使っています。

Integer以外なら、文字列としてsetStringを使っています。

valueの型使うメソッド
IntegersetInt2000000
それ以外setString%車%

priceのような数値項目ではIntegerを使います。

nameのような文字列項目ではStringを使います。

ただし、この実装ではIntegerとString以外の型に対する細かい対応はありません。

たとえば、deleted_atを日付として扱うなら、LocalDateTimeやTimestampへの対応を検討したほうがよいです。

また、valueがnullの場合、value.toString()でNullPointerExceptionになる可能性があります。

実務ではnullチェックも考えましょう。

SQLを実行してResultSetを受け取る

次に、SQLを実行しています。

try (ResultSet rs = ps.executeQuery()) {




executeQueryは、SELECT文を実行するときに使うメソッドです。

実行結果はResultSetとして返ってきます。

ResultSetは、DBから返ってきた表のようなものです。

car_idnamepricedeleted_at
1普通車2500000null
2軽自動車2100000null

ResultSetは、この表を1行ずつ読み取るためのものです。

ResultSetからCarDtoを作る

検索結果を1行ずつ読み取り、CarDtoに詰めています。

while (rs.next()) {
    CarDto car = new CarDto();
    car.setCarId(rs.getInt("car_id"));
    car.setName(rs.getString("name"));
    car.setPrice(rs.getInt("price"));
    car.setDeletedAt(rs.getString("deleted_at"));
    carList.add(car);
}




rs.next()は、次の行があるかどうかを確認し、あればその行へ進みます。

1行ごとにCarDtoを作り、DBの値をセットしています。

DBのカラムCarDtoのフィールド
car_idcarId
namename
priceprice
deleted_atdeletedAt

最後に、carList.add(car)でリストに追加しています。

これにより、検索結果がList<CarDto>として呼び出し元に返せるようになります。

SQLExceptionの処理

DB処理では、SQLExceptionが発生する可能性があります。

} catch (SQLException e) {
    e.printStackTrace();
}




SQLExceptionは、SQL実行やDB接続で問題が起きたときの例外です。

たとえば、次のような場合に発生します。

原因
SQL文が間違っているカラム名のミス、構文エラー
DBに接続できないURL、ユーザー名、パスワードの誤り
型が合わない文字列を数値カラムと比較している
テーブルが存在しないcarsテーブルが作成されていない

このコードではe.printStackTrace()でエラー内容をコンソールに表示しています。

学習段階ではわかりやすいです。

ただし、実務ではログ出力や例外の再スローを検討します。

エラーを握りつぶすと、呼び出し元が失敗に気づけない場合があるからです。

mainメソッドの動き

最後に、mainメソッドを見ていきます。

public static void main(String[] args) {
    CarsDao carsDao = new CarsDao();

    List<Condition> conditions = new ArrayList<>();
    conditions.add(new Condition("price", ">",    2000000, "AND"));
    conditions.add(new Condition("name",  "LIKE", "%車%",   "AND"));
    List<CarDto> result = carsDao.searchCarListByConditions(conditions);

    for (CarDto carDto : result) {
        System.out.println(carDto);
    }
}




まず、CarsDaoを作っています。

CarsDao carsDao = new CarsDao();

DAOとは、Data Access Objectの略です。

DBアクセスを担当するクラスです。

アプリとDBの間に立つ窓口のような存在です。

次に、検索条件リストを作ります。

List<Condition> conditions = new ArrayList<>();

そして、条件を2つ追加しています。

conditions.add(new Condition("price", ">", 2000000, "AND"));
conditions.add(new Condition("name", "LIKE", "%車%", "AND"));

この2つの条件は、次の意味です。

条件意味
price > 2000000価格が200万円より高い
name LIKE '%車%'名前に「車」が含まれる

この条件を使って検索しています。

List<CarDto> result = carsDao.searchCarListByConditions(conditions);

最後に、検索結果を1件ずつ表示しています。

for (CarDto carDto : result) {
    System.out.println(carDto);
}

このmainメソッドは、検索メソッドが正しく動くか確認するためのテスト実行のような役割です。

このコードの良いところ

このコードには、学習として良いポイントがたくさんあります。

良いところ理由
条件をリストで管理している検索条件を柔軟に増やせる
フィールド名をホワイトリストで制限している危険なカラム名の混入を防げる
比較演算子をチェックしているSQLに不正な演算子が入るのを防げる
ANDとORをチェックしている条件結合部分の安全性を高めている
値をPreparedStatementで渡しているSQLインジェクション対策になる
DTOに詰めて返しているDBの結果をJavaのオブジェクトとして扱いやすい

特に大事なのは、フィールド名や演算子をバリデーションしている点です。

動的SQLでは、ここをサボると危険です。

「値はPreparedStatementを使っているから全部安全」と考えるのは危険です。

フィールド名や演算子は文字列結合しているため、別途チェックが必要になります。

このコードで注意したいところ

次に、改善できる点も見ておきましょう。

注意点理由改善案
SELECT *を使っている不要なカラムまで取得する可能性があるSELECT car_id, name, price, deleted_atと明示する
Stringの+=でSQLを作っている条件が増えると読みにくくなるStringBuilderを使う
最後のConditionにもoperatorが必要最後のoperatorは実際には使われない最後だけoperator不要にする設計も考える
null値に弱いvalue.toString()でNullPointerExceptionになる可能性があるnullチェックやIS NULL対応を追加する
型チェックが簡易的IntegerとString以外の型に対応していない日付やLongなども考慮する
ANDとORの優先順位に注意括弧を使えないため複雑な条件に弱い括弧付き条件を扱う設計を検討する
論理削除条件が固定で入っていない削除済みデータも検索される可能性がある通常はdeleted_at IS NULLを追加する

特に、実務ではSELECT *を避けることが多いです。

String sql = "SELECT car_id, name, price, deleted_at FROM cars WHERE ";

このように、必要なカラムを明示したほうが安全です。

また、論理削除を使っている場合は、通常の検索では削除済みデータを除外することが多いです。

WHERE deleted_at IS NULL

この条件を固定で入れるか、検索条件として扱うかは、システムの仕様によって決めます。

StringBuilderを使ったほうがよい理由

このコードでは、SQLを+=で連結しています。

sql += cond.getField() + " " + cond.getComparator() + " ?";

学習用としてはわかりやすいです。

ただし、文字列を何度も連結する場合は、StringBuilderを使うことが多いです。

StringBuilderとは、文字列を効率よく組み立てるためのクラスです。

ブロックを1つずつ積み上げていく専用の作業台のようなものです。

StringBuilder sql = new StringBuilder();
sql.append("SELECT car_id, name, price, deleted_at FROM cars WHERE ");

for (int i = 0; i < conditions.size(); i++) {
    Condition cond = conditions.get(i);
    sql.append(cond.getField())
       .append(" ")
       .append(cond.getComparator())
       .append(" ?");

    if (i < conditions.size() - 1) {
        sql.append(" ")
           .append(cond.getOperator())
           .append(" ");
    }
}




このように書くと、SQLを組み立てている意図が少し明確になります。

ただし、新人エンジニアの学習段階では、まず+=で流れを理解してからStringBuilderに進むとよいです。

LIKE検索で注意すること

mainメソッドでは、LIKE検索の値として%車%を渡しています。

conditions.add(new Condition("name", "LIKE", "%車%", "AND"));

LIKE検索では、%を付ける位置によって検索の意味が変わります。

意味
車%「車」で始まる
%車「車」で終わる
%車%「車」を含む

今回の%車%は、「名前のどこかに車が含まれる」という意味です。

LIKE検索は便利ですが、大量データでは重くなる場合があります。

特に、先頭に%を付ける検索はインデックスが効きにくいことがあります。

小さいデータなら問題になりませんが、実務では性能にも注意しましょう。

ANDとORの優先順位に注意する

今回のコードでは、条件を順番につなげています。

price > ? AND name LIKE ?

単純な条件なら問題ありません。

ただし、ANDとORが混ざると注意が必要です。

price > ? OR name LIKE ? AND car_id = ?

SQLでは、ANDのほうがORより優先されます。

つまり、上のSQLは次のように解釈されます。

price > ? OR (name LIKE ? AND car_id = ?)

もし次のようにしたいなら、括弧が必要です。

(price > ? OR name LIKE ?) AND car_id = ?

今回のコードでは、括弧付き条件には対応していません。

そのため、複雑な条件を扱いたい場合は、Conditionの設計をさらに工夫する必要があります。

数学でも、掛け算が足し算より先に計算されますよね。

SQLにも似たような優先順位があります。

このコードを図ではなく流れで整理する

処理の流れを文章で整理すると、次のようになります。

順番処理
1Conditionのリストを受け取る
2条件がなければgetCarListで全件返す
3各Conditionのfield、comparator、operatorをチェックする
4チェック済みの条件からWHERE句を作る
5PreparedStatementを作る
6?に値をセットする
7SQLを実行する
8ResultSetからCarDtoを作る
9CarDtoのリストを返す

この流れを理解できると、動的検索の基本がかなり見えてきます。

新人エンジニアがこのコードから学ぶべきこと

このコードから学べることは、単にJavaの文法だけではありません。

DB検索機能を作るうえで大切な考え方が入っています。

学ぶべきこと内容
動的SQL条件に応じてSQLを組み立てる考え方
バリデーション入力値が安全か確認する考え方
ホワイトリスト許可した値だけ通す安全対策
PreparedStatement値を安全にSQLへ渡す仕組み
DTODBの検索結果をJavaオブジェクトに詰める考え方
DAODBアクセス処理を担当するクラスの役割
SQLインジェクション対策文字列結合の危険性と安全な実装の基本

特に重要なのは、動的SQLとSQLインジェクション対策をセットで学ぶことです。

動的SQLは便利です。

でも、便利な分だけ危険もあります。

包丁と同じです。

料理には欠かせませんが、扱い方を間違えると危ないですよね。

動的SQLも、バリデーションとPreparedStatementをセットで使うことが大切です。

まとめ

今回のコードは、Conditionのリストをもとに、carsテーブルを動的に検索するためのJavaコードです。

フィールド名、比較演算子、結合演算子をチェックし、検索値はPreparedStatementで安全にバインドしています。

ポイント内容
isValidField使ってよいカラム名か確認する
isValidComparator使ってよい比較演算子か確認する
isValidOperatorANDまたはORか確認する
searchCarListByConditions条件リストからSQLを作って検索する
PreparedStatement?に値を安全にセットする
ResultSetDBの検索結果を1行ずつ読み取る
CarDto検索結果をJavaのオブジェクトとして持つ

一言でまとめるなら、このコードは「安全に条件を組み合わせてcarsテーブルを検索するための動的SQLサンプル」です。

新人エンジニアは、まず次の3つをしっかり覚えてください。

覚えること理由
値はPreparedStatementで渡すSQLインジェクションを防ぐため
カラム名や演算子はホワイトリストでチェックする?で置き換えられないため
検索結果はDTOに詰めて返すDBの結果をJava側で扱いやすくするため

今後の学習では、まずPreparedStatement、ResultSet、DTO、DAOの基本を復習してください。

その後、動的SQL、SQLインジェクション対策、LIKE検索、ANDとORの優先順位、StringBuilder、論理削除のdeleted_at IS NULLを順番に学ぶとよいです。

まずは今回のコードを動かし、priceだけの検索、nameだけのLIKE検索、priceとnameのAND検索、OR検索を試してみましょう。実際にSQLがどう組み立てられるかを確認すると、動的検索の理解が一気に深まります!

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。