Spring JDBCを使用したデータベースアクセスの基本についてまとめ

## **この記事に書いてあること**

- [プロになるためのSpring入門](https://gihyo.jp/book/2023/978-4-297-13613-0)のSpring JDBCを使用したデータベースアクセスに関する内容を読んだまとめ
- Spring JDBCを使ったコードの実装例

## Spring JDBCとは？

- Spring JDBCは、Spring Frameworkが提供するデータベースアクセスの機能
- Java標準のJDBCを、より簡単に使用できる仕組みを提供している

（※どのデータベースアクセスの仕組みが使われたとしても、内部ではJava標準のJDBCが使われている。）

- Java標準のJDBCを直接使用する場合に必要となる冗長な処理を変わりに行うだけであるため、処理速度はほぼ変わらない

## 実装比較

### Java標準のJDBCを使った実装例

```java
@Repository
public class CustomerRepository {

    @Autowired
    private DataSource dataSource;

    public void insertCustomerInfo(CustomerData customerData) {
        String sql = "INSERT INTO customer (first_name, last_name, created_at, updated_at) VALUES (?,?,?,?)";
        try (Connection conn = dataSource.getConnection();
            PreparedStatement stmt = conn.prepareStatement(sql);) {
                stmt.setString(1, customerData.getFirstName());
                stmt.setString(2, customerData.getLastName());
                stmt.setTimestamp(3, Timestamp.valueOf(LocalDateTime.now()));
                stmt.setTimestamp(4, Timestamp.valueOf(LocalDateTime.now()));
                stmt.executeUpdate();
        } catch(SQLException e) {
            throw new SystemException("INSERTに失敗", e);
        }
    }
}

```

問題点：

- SQLExceptionのキャッチもしくはthrowsが無いとコンパイルが通らない
- アプリケーション固有の処理以外に決まりきった冗長な記述が多い
- Connectionクローズ忘れや、例外の握り潰しによる不適切な例外ハンドリングの危険がある

### Spring JDBCを使った実装例

```java
@Repository
public class MessageRepository {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void insertCustomerInfo(CustomerData customerData) {
        jdbcTemplate.update("INSERT INTO customer (first_name, last_name, created_at, updated_at) VALUES (?,?,?,?)",
            customerData.getFirstName(),
            customerData.getLastName(),
            Timestamp.valueOf(LocalDateTime.now()),
            Timestamp.valueOf(LocalDateTime.now()));
    }
}

```

解決：

- updateメソッドで以下の処理を行ってくれる
    - Connection, PreparedStatementオブジェクトの取得
    - SQLの実行
    - Connection, PreparedStatementオブジェクトのクローズ
    - 例外ハンドリング
    
    （SQLExceptionがスローされた際にエラーの原因毎の非チェック例外に変換してスローしてくれる）
    
- 冗長な記述がなくなり、アプリケーション固有の処理だけ記述すればよい

## 検索系の処理

主なメソッド

| メソッド名 | 用途 |
| --- | --- |
| queryForObject | 1レコードの1項目を取得する際に利用する |
| queryForMap | 1レコードの内容をMapとして取得する際に利用する |
| queryForList | 複数行のレコードを取得する際に利用する |
| query | 検索する際に利用する |

### 1カラム取得する場合

- 1レコードの1カラムだけ取得する場合は、**queryForObject**メソッドを使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529381/6a146294-3f7f-020e-58ca-7c4f8060bcd9.png)

実装例：

```java
String firstName = jdbcTemplate.queryForObject(
	"SELECT first_name FROM customer WHERE customer_id=?", String.class, 1);
```

- 複数パラメータの指定方法

実装例：

```java
String firstName = jdbcTemplate.queryForObject(
	"SELECT first_name FROM customer WHERE customer_id=? AND last_name=?", String.class, 1, "Doe");
```

取得結果：

String型の文字列 ”John”

### レコードをMapオブジェクトに変換して取得する場合

- 1レコードのデータをMapオブジェクトで取得する場合は、**queryForMap**メソッドを使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529381/a40b4a59-bbb8-4aa8-2024-65625b4faab8.png)

実装例：

```java
Map<String, Object> map = jdbcTemplate.queryForMap(
	"SELECT * FROM customer WHERE customer_id=?", 1);
```

取得結果：

customer_idが1のレコードの情報が入ったMap型オブジェクト

- 複数のレコードをMapで取得する場合は、**queryForList**を使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529381/0c6b5ce9-83d4-0b08-f463-411a6d6dc3a5.png)

実装例：

```java
List<Map<String, Object>> maps = jdbcTemplate.queryForList(
	"SELECT * FROM customer");
```

取得結果：

レコード毎の情報が入ったMapオブジェクトが全レコード分格納されたListオブジェクト

### レコードをEntityオブジェクトに変換して取得する場合

Entityオブジェクト実装例：

CustomerDataクラス

```java
   public class CustomerData {
    private final String firstName;
    private final String lastName;
    private final Timestamp createdAt;
    private final Timestamp updatedAt;

    public CustomerData(String firstName, String lastName, Timestamp createdAt, Timestamp updatedAt) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }
    
    ...Getterメソッド
```

※フィールド名は、Spring JDBCの機能を使って自動的にカラムと紐づけられる（スネークケース/キャメルケースでも可）

- 1レコードのデータをMapではなくEntity(例: CustomerData)オブジェクトに変換して取得する場合は、**queryForObject**を使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529381/f7380be9-40e7-003f-813b-c2fe0e1af805.png)

実装例：

```java
CustomerData customerData = jdbcTemplate.queryForObject(
             "SELECT * FROM customer WHERE customer_id = 1",
              new DataClassRowMapper<>(CustomerData.class));
```

※DataClassRowMapperオブジェクトは、レコードの値を、コンストラクタで指定したクラスのオブジェクトに自動変換してくれるSpringのクラス

取得結果：

customer_idが1のレコードの情報が、カラム毎に対応するフィールドに入ったCustomerDataオブジェクト

- 複数のレコードをEntity(例: CustomerData)オブジェクトに変換して取得する場合は、**query**を使う

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2529381/0c6b5ce9-83d4-0b08-f463-411a6d6dc3a5.png)

実装例：

```java
List<CustomerData> customerData = jdbcTemplate.query(
             "SELECT * FROM customer",
              new DataClassRowMapper<>(CustomerData.class));
```

取得結果：

レコード毎の情報が入ったCustomerDataオブジェクトが全レコード分格納されたListオブジェクト

## 更新系の処理

主なメソッド

| メソッド名 | 用途 |
| --- | --- |
| update | 更新系のSQL（INSERT, DELETE, UPDATE）を実行する際に利用する |

### INSERTする場合

実装例：

```java
jdbcTemplate.update(
    "INSERT INTO customer (first_name, last_name, created_at, updated_at) VALUES (?,?,?,?)",
    customerData.getFirstName(),
    customerData.getLastName(),
    Timestamp.valueOf(LocalDateTime.now()),
    Timestamp.valueOf(LocalDateTime.now()));
```

- updateの戻り値は、INSERTブンで挿入されたレコードの件数がint型で返される

（※この場合は挿入されるレコードが１件と分かっているため戻り値を使用していない）

- キーの重複でレコードの挿入が出来なかった場合は例外がスローされる

### UPDATEする場合

実装例：

```java
int count = jdbcTemplate.update(
    "UPDATE customer SET first_name=?, last_name=?, created_at=?, updated_at=? WHERE customer_id=?",
    customerData.getFirstName(),
    customerData.getLastName(),
    Timestamp.valueOf(LocalDateTime.now()),
    Timestamp.valueOf(LocalDateTime.now()),
    1);
```

- updateメソッドの戻り値は、UPDATE文で変更されたレコードの数がint型で返される
- 0件だった場合は戻り値で「0」が返される

### DELETEする場合

実装例：

```java
int count = jdbcTemplate.update(
"DELETE FROM customer WHERE customer_id=?", 1);
```

- updateメソッドの戻り値は、DELETE文で変更されたレコードの数がint型で返される
- 0件だった場合は戻り値で「0」が返される


# 


例外処理については以下の記事にまとめましたのでご参照ください。

https://qiita.com/atjjnnn/items/06faa237f09412bbf843