この記事に書いてあること
- プロになるためのSpring入門のSpring JDBCを使用したデータベースアクセスに関する内容を読んだまとめ
- Spring JDBCを使ったコードの実装例
Spring JDBCとは?
- Spring JDBCは、Spring Frameworkが提供するデータベースアクセスの機能
- Java標準のJDBCを、より簡単に使用できる仕組みを提供している
(※どのデータベースアクセスの仕組みが使われたとしても、内部ではJava標準のJDBCが使われている。)
- Java標準のJDBCを直接使用する場合に必要となる冗長な処理を変わりに行うだけであるため、処理速度はほぼ変わらない
実装比較
Java標準のJDBCを使った実装例
@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を使った実装例
@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メソッドを使う
実装例:
String firstName = jdbcTemplate.queryForObject(
"SELECT first_name FROM customer WHERE customer_id=?", String.class, 1);
- 複数パラメータの指定方法
実装例:
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メソッドを使う
実装例:
Map<String, Object> map = jdbcTemplate.queryForMap(
"SELECT * FROM customer WHERE customer_id=?", 1);
取得結果:
customer_idが1のレコードの情報が入ったMap型オブジェクト
- 複数のレコードをMapで取得する場合は、queryForListを使う
実装例:
List<Map<String, Object>> maps = jdbcTemplate.queryForList(
"SELECT * FROM customer");
取得結果:
レコード毎の情報が入ったMapオブジェクトが全レコード分格納されたListオブジェクト
レコードをEntityオブジェクトに変換して取得する場合
Entityオブジェクト実装例:
CustomerDataクラス
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を使う
実装例:
CustomerData customerData = jdbcTemplate.queryForObject(
"SELECT * FROM customer WHERE customer_id = 1",
new DataClassRowMapper<>(CustomerData.class));
※DataClassRowMapperオブジェクトは、レコードの値を、コンストラクタで指定したクラスのオブジェクトに自動変換してくれるSpringのクラス
取得結果:
customer_idが1のレコードの情報が、カラム毎に対応するフィールドに入ったCustomerDataオブジェクト
- 複数のレコードをEntity(例: CustomerData)オブジェクトに変換して取得する場合は、queryを使う
実装例:
List<CustomerData> customerData = jdbcTemplate.query(
"SELECT * FROM customer",
new DataClassRowMapper<>(CustomerData.class));
取得結果:
レコード毎の情報が入ったCustomerDataオブジェクトが全レコード分格納されたListオブジェクト
更新系の処理
主なメソッド
メソッド名 | 用途 |
---|---|
update | 更新系のSQL(INSERT, DELETE, UPDATE)を実行する際に利用する |
INSERTする場合
実装例:
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型で返される
(※この場合は挿入されるレコードが1件と分かっているため戻り値を使用していない)
- キーの重複でレコードの挿入が出来なかった場合は例外がスローされる
UPDATEする場合
実装例:
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する場合
実装例:
int count = jdbcTemplate.update(
"DELETE FROM customer WHERE customer_id=?", 1);
- updateメソッドの戻り値は、DELETE文で変更されたレコードの数がint型で返される
- 0件だった場合は戻り値で「0」が返される
例外処理については以下の記事にまとめましたのでご参照ください。