
Spring入門(JDBC Template編)
はじめに
仕事でSpringを用いたバックエンド開発を行うことになったため、Springについての学習をゼロから開始した。
学んだ内容を概念ごとに分割して記事を作成する。
本記事の対象読者はプログラミングやアプリケーション構築の基礎知識はあるが、Springは未経験の方。
JDBCとは
JDBCとはJavaDataBaseConnectivityの略で、Javaが標準で提供するDB関連の処理を提供する標準API。
DBの種類の違いを吸収してDBへの接続を行ったり、SQLの実行、結果の処理を行うことを可能にする。
JavaからのDBアクセスには様々な仕組みが作成されているが、いずれの場合も内部的にはJava標準のJDBCを使用している。
Spring JDBCとは
Spring JDBCはSpringが提供するJDBCのラッパークラス。
JDBCの冗長な記述をより簡潔にするためのAPIを提供する。
Springでは他ライブラリとの連携の柔軟に行えるため、必ずしもSpring JDBCを活用するわけではない。
DB操作の流れ
JNDI
Java Naming and Directory Interfaceの略。
Javaアプリが外部リソースと名前を紐づけることで、リソース名により外部リソースを利用可能にするためのインターフェース。
リソース情報をハードコードすることなく外部リソースとの接続を可能にする。
リソースはアプリサーバとは別のJNDIサーバに保存され、アプリから名前を受け取って関連づけられたリソースを返却する。
DB等の外部リソースに接続する際に使用される。
DataSource
データ格納元(DBなど)との接続を作成するファクトリをJabaで表現したもの。
DataSourceオブジェクトの作成時にDB名や通信プロトコル、ユーザ、パスワードなどの必要な設定を行うことで、ドライバ情報をハードコードすることなくデータソースにアクセス可能になる。
内部ではJNDIを介してデータソースにアクセスを行っている。
※ドライバ:ハードウェアやソフトウェアを利用するための仲介役。
システムの詳細を抽象化して接続や操作を簡易化する。
例:JDBCドライバはJavaアプリとDBMSのデータのやり取りを簡易化する
JDBC Template
DBにSQLを発行するメソッドを提供するSpringフレームワークのクラス。
JDBC Templateオブジェクト作成の引数としてDataSourceオブジェクトが必要であり、DataSourceオブジェクトの数だけJDBC Templateオブジェクトを用意してDIコンテナに格納して使い回す。

DB関連の呼称
テーブル:データを格納する領域全体
レコード:テーブルの一行分にあたる一つのデータセット
カラム:各レコードが持つデータの種類
フィールド:レコードを形成する一つ一つの要素

レコードの取得(SELECT)
取得したいデータの種類により使用するメソッドが異なる。
1レコードの1カラム:queryForObject
複数レコードの一カラム:queryForList
Map:queryForMap
1レコードのエンティティ:queryForObject
複数レコード(エンティティ):query
いずれの場合も
第一引数:SQL(パラメータ部分はJDBCの規定値である?とする)
第二引数:戻り値の型
第三以降の引数:?の具体値
エンティティ取得の型指定
エンティティを取得する際の方の指定には
new DataClassRowMapper<>(〇〇.class)を用いる 。
public Product selectById(String id) {
return jdbcTemplate.queryForObject("SELECT * FROM t_product WHERE id=?", new DataClassRowMapper<>(Product.class), id);
}
DataClassRowMapper:レコードの値をコンストラクタで指定したクラスのオブジェクトに自動変換するクラス。
カラムのスネークケース(_での区切り)とフィールドのキャメルケース(大文字での区切り)も自動で対応づけてくれる。
レコードの更新(INSERT,UPDATE,DELETE)
レコードの更新はINSERT,UPDATE,DELETEのすべての場合に共通でJdbcTemplateクラスのupdateメソッドを使用する。
例)insert処理
jdbcTemplate.update("INSERT INTO t_order values (?, ?, ?, ?, ?, ?, ?, ?)",
order.getId(),
order.getOrderDateTime(),
order.getBillingAmount(),
order.getCustomerName(),
order.getCustomerAddress(),
order.getCustomerPhone(),
order.getCustomerEmailAddress(),
order.getPaymentMethod().toString());
第一引数:実行したいSQL
第二引数:SQLの?の具体値
戻り値は変更を加えたレコード数のため、登録に失敗して戻り値が0の場合はエラーハンドリング処理を追加する必要がある。
JDBCTemplateのBean化
JdbcTemplateクラスはオブジェクトをBeanメソッドでDIコンテナに格納しておき、同じものを使い回す。
複数のDataSourceを使用する場合は、DataSourceの種類だけJDBCTemplateのBean定義を作成する。
JOINしたデータの取得
JOINとは
複数テーブルを外部キーを基にして結合すること。
以下のようなテーブルがあるとする。
Customers テーブル
| ID | Name | City |
|------------|--------------|-----------|
| 1 | Alice | Tokyo |
| 2 | Bob | Osaka |
| 3 | Carol | Aichi |
Orders テーブル
| ID | CustomerID | OrderDate |
|---------|------------|------------|
| 101 | 1 | 2023-07-15 |
| 102 | 2 | 2023-07-16 |
| 103 | 4 | 2023-07-17 |
OrderテーブルはCustomerテーブルの主キーを外部キーとして持っているため、以下のようなSQLで結合することができる。
SQL
SELECT
c.id AS c_id,
c.name AS c_name,
c.city AS c_city,
o.id AS o_id,
o.orderDate AS o_order
FROM Customers c
LEFT OUTER JOIN Orders o
ON c.id = o.customerid;
WHERE
c.id = '1'
ここで両テーブルではIDというカラム名が被っているため、テーブルの別名としてc,oを付与して、カラム名を「テーブルの別名」.「カラム名」の形で指定している。
※ LEFT OUTER JOINは左外部結合の処理。
1テーブル目の全レコードが含まれるデータが返却される。
2テーブル目に一致するカラムが存在しない場合はNULLを設定して
返却される。
外部結合には他にRIGHTとFULLが存在する。
一致するカラムがある場合のみデータを取得したい場合は
内部結合のINNER JOINを用いる。
上記のように「テーブルの別名」.「カラム名」で値を取得するとDataClassRowMapperでEntityとのマッピングをしようとするとフィールド名とカラム名が異なるためマッピングできなくなる。
RowMapperインタフェースの実装
DataClassRowMapperはRowMapperというインタフェースの具象クラスであり、自作でRowMapperの具象クラスを作成すれば複数Entityを含むようなマッピングも可能になる。
1対1の変換
以下のようなクラスのRowMapperの実装を考える。
Customer クラス
public class Customer {
private String id;
private String orderId;
private String name;
private Order order;
...Getter,Setterメソッド
}
Order クラス
public class Order {
private String id;
private String code;
private String Name;
...Getter,Setterメソッド
}
RowMapperインタフェースの実装は以下のように、RowMapperインタフェースのmapRowメソッドをOverrideする形で実装する。
static class CustomerRowMapper implements RowMapper<Customer> {
@Override
public Customer mapRow(ResultSet rs, int rowNum) throws SQLException {
Customer customer = new Customer();
customer.setId(rs.getString("c_id"));
customer.setOrderId(rs.getString("o_order_id"));
customer.setname(rs.getString("c_name"));
Order order = new Order();
order.setId(rs.getString("o_id"));
order.setCode(rs.getString("o_code"));
order.setName(rs.getString("o_name"));
customer.setOrder(order);
return customer;
}
}
Repositoryでマッピングを利用するには以下のように記述する。
public Customer selectById(String id) {
String sql = "..."
return jdbcTemplate.queryForObject(sql, new CustomerRowMapper(), id);
}
1対多の変換
RowMapperは1レコードに対する処理しか実装できないため、1レコードにEntityの配列を持つようなマッピングには使用できない。
この場合は ResultSetExtractorを使用する。
以下のようなクラスとSQLでの実装を考える。
Customer クラス
public class Customer {
private String id;
private String name;
private List<Order> orders;
...Getter,Setterメソッド
}
Order クラス
public class Order {
private String id;
private String code;
private String name;
private String customerId;
...Getter,Setterメソッド
}
SQL
SELECT
c.id AS c_id,
c.name AS c_name,
o.id AS o_id,
o.code AS o_code;
o.code AS o_name;
o.customer_id AS o_customer_id
FROM
customers c
LEFT OUTER JOIN orders o
ON c.id = o.customer_id;
WHERE
c.id = '1'
ResultSetExtractorの実装は以下のようになる。
static class CustomerResultSetExtractor implements ResultSetExtractor<Customer> {
@Override
public Customer extractData(ResultSet rs) throws SQLException, DataAccessException {
Customer customer = null;
while(rs.next()) {
if (customer == null) {
customer.setId(rs.getString("c_id"));
customer.setname(rs.getString("c_name"));
customer.setOrder(new ArrayList<>());
}
Order order = new Order();
order.setId(rs.getString("o_id"));
order.setCode(rs.getString("o_code"));
order.setName(rs.getString("o_name"));
customer.getOrder().add(order)
}
return customer;
}
}