JDBCTemplateのプレースホルダでハマったお話

#### 開発環境
- Java 1.8.0
- TERASOLUNA Server Framework for Java (5.4.1.RELEASE)
 - Spring Framework 4.3.14.RELEASE

#### JDBCTemplateのおさらい
はじめに`JDBCTemplate`の使い方をざっと説明します。
※関係のないところは割愛しています。

##### XMLにJDBCのネームスペースを追加する

```xml:context.xml
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"

  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
   http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
   ">
```

##### (データソースの定義)
テストデータなど、実行時にテーブル定義やデータ投入をする場合に必要。
この例ではクラスパス下の`/database/schema.sql`に書かれた`H2`用のスクリプトを読み込んでいる。`database-name`は`URL`に設定したものを設定する。省略すると`id`に設定した文字列がされる。

```xml:context.xml
  <jdbc:embedded-database id="dataSource" type="H2" database-name="testdb">
    <jdbc:script location="classpath:/database/schema.sql" />
  </jdbc:embedded-database>
```

参考：`Logback`のデータベース設定例。この`url`タグの値と`database-name`を合わせる。

```xml:logback.xml
      <dataSource class="org.apache.commons.dbcp2.BasicDataSource">
        <driverClassName>org.h2.Driver</driverClassName>
        <url>jdbc:h2:mem:testdb</url>
        <username>sa</username>
        <password></password>
      </dataSource>
```

##### JDBCTemplateのビーン定義
コンストラクタに↑で定義したデータソースを設定するだけ

```xml:context.xml
  <bean class="org.springframework.jdbc.core.JdbcTemplate">
    <constructor-arg ref="dataSource" />
  </bean>
```

#### JDBCTemplateの使い方
##### コードで呼び出す
単純にインジェクトするだけ

```java.test.java
  @Inject
  JdbcTemplate jdbcTemplate;
```

##### SQLを実行して結果を取得
`queryForXXX`で`SQL`の結果を`List`や`Map`で受け取ることができる。
以下は`Logback`で出力したログのメッセージを取得する例。

```java:test.java
    List<String> logginEvents = jdbcTemplate.queryForList(
        "SELECT formatted_message FROM logging_event ORDER BY timestmp", String.class);
```

`?`を使うとプレースホルダになり値をバインドすることもできる。
以下は`MDC`または`Context`に含まれる、「context」というキーに紐づく値を取得する例。

```java:test.java
    List<String> loggingEventProperties = jdbcTemplate.queryForList(
        "SELECT mapped_value FROM logging_event_property WHERE mapped_key=?", String.class,
        "context");
```

#### JDBCTemplateのプレースホルダでハマったお話
- `SQL`をハードコーディングするとtypoしそうなので定数化したい:disappointed_relieved:
- そうだ！カラム名とかもプレースホルダにしたろ:smiley:
- じゃけん、すぐ書き換えましょうねぇ:stuck_out_tongue_closed_eyes:

```java:test.java

    private static final String MESSAGE_COLUM_NAME = "formatted_message";

    // 略

    List<String> logginEvents = jdbcTemplate.queryForList(
    "SELECT ? FROM logging_event ORDER BY timestmp", String.class, MESSAGE_COLUM_NAME);
```

実行結果……
![出力結果](https://qiita-image-store.s3.amazonaws.com/0/162142/f9e50c20-779e-37b4-f0b6-99ece58370a7.png)

なんで`formatted_message`:thinking:？

#### カラム名やテーブル名はバインドできない
`JDBCTemplate`のバインドは内部で`PreparedStatement`を呼び出していて、`PreparedStatement`のプレースホルダがパラメータのみ対応しているため、
カラム名やテーブル名はバインドできないらしい。

- [Using a prepared statement and variable bind Order By in Java with JDBC driver
](https://stackoverflow.com/questions/12430208/using-a-prepared-statement-and-variable-bind-order-by-in-java-with-jdbc-driver/12430474)
- [Cannot use a LIKE query in a JDBC PreparedStatement?](https://stackoverflow.com/questions/2857164/cannot-use-a-like-query-in-a-jdbc-preparedstatement)

ドキュメントを探したり、ブレイクポイントをかけてしばらく調べていましたが、カラム名に使ってはいけないという文言は見つけられませんでした。
しかし↑の例で`formatted_message`という文字列が返ってきたこと・`SQL`内で?をエスケープしなくてもバインドされることを考えると、`JDBC`ドライバのエスケープ処理が関係している気がします。

#### 結論
テーブル名やカラム名をバインドしたい時は、`String#format`などを使おう。

#### 投稿に至った経緯
久々に`JDBCTemplate`を使ったらハマったの記事にしました。