SpringBootでJDBCを使ってDBとのやり取りを行う際、「O/Rマッパー」という機能使うことでコードの可読性が上がるみたいなことを学んだのでその備忘録。
O/Rマッパーを使わないコード。
・以下はUser情報を全件取得する場合を想定したコード。
// JdbcTemplateのインスタンス生成。SpringBootのJDBCを使う場合はこれを使う。
@Autowired
JdbcTemplate jdbc;
public List<User> selectMany() throws DataAccessException {
// M_USERテーブルのデータを全件取得(queryForList)
// map→keyとvalueの組み合わせ。それをListで受け取る。
List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user");
// 結果返却用の変数
List<User> userList = new ArrayList<>();
// 取得したデータを結果返却用のListに格納していく
for (Map<String, Object> map : getList) {
//Userインスタンスの生成
User user = new User();
// Userインスタンスに取得したデータをセットする
user.setUserId((String) map.get("user_id")); //ユーザーID
user.setPassword((String) map.get("password")); //パスワード
user.setUserName((String) map.get("user_name")); //ユーザー名
user.setBirthday((Date) map.get("birthday")); //誕生日
user.setAge((Integer) map.get("age")); //年齢
user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス
user.setRole((String) map.get("role")); //ロール
//結果返却用のListに追加
userList.add(user);
}
return userList;
}
こんな感じです。簡単に説明すると、queryForListメソッドでSQLを発行しレコードを取得する。DBから取得した際、Map型(こんな形→{USER_ID=test1 ...})で受け取るが、実際にユーザはたくさんいるのでそれを更にList型で受け取る。
そして取得したデータを拡張for文で1ユーザづつ取得してUserインスタンスを生成。
最後に作成したインスタンスをListに代入すればユーザ一覧の完成。
O/Rマッパーを使う。
・そもそもO/Rマッパーとは何なのか?
以下のURLに詳しく書いてあります。
https://thinkit.co.jp/free/article/0606/13/1
ざっくり言うと、DAOに直でSQL発行~インスタンスの生成までを書くのではなく、別のファイルに書いておく事で後々変更があった場合とかに柔軟に対応できるみたいな認識でいいと思います。
・では本題。何通りか使い方がある見たいのでそれぞれを見ていきます。
1,RowMapperを使う。
RowMapperをインポートしてUserインスタンスを生成する処理自体を分離させる。Mapperクラスは自分で作る。
・まずUserDAOを以下の様に変更。
import org.springframework.jdbc.core.RowMapper;
@Autowired
private JdbcTemplate jdbc;
//ユーザー全件取得
@Override
public List<User> selectMany() {
//M_USERテーブルのデータを全件取得するSQL
String sql = "SELECT * FROM m_user";
//RowMapperの生成
RowMapper<User> rowMapper = new UserRowMapper();
//SQL実行
return jdbc.query(sql, rowMapper);
}
この時点でDAOはかなりスッキリした。やった事しては以下になる。
・RowMapperをインポート。
・SQLを変数に代入しておく。
・UserRowMapperのインスタンスを生成(ファイルはこの後作成する)
・JdbcTemplateのqueryメソッドを使ってDBから情報を取得。
こんな感じです。ではMapperクラスを作成していきます。作成場所はDAOを同じ階層で問題ないかと思います。
package com.example.demo.login.domain.repository.jdbc;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import com.example.demo.login.domain.model.User;
public class UserRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
//戻り値用のUserインスタンスを生成
User user = new User();
//ResultSetの取得結果をUserインスタンスにセット
user.setUserId(rs.getString("user_id"));
user.setPassword(rs.getString("password"));
user.setUserName(rs.getString("user_name"));
user.setBirthday(rs.getDate("birthday"));
user.setAge(rs.getInt("age"));
user.setMarriage(rs.getBoolean("marriage"));
user.setRole(rs.getString("role"));
return user;
}
}
こんな感じです。Mapperクラスのコード自体は元々DAOにあったものとほとんど変わりはありませんが、拡張for文でListを作らなくても良いという点が違います。
詳しくは分かりませんが、mapRow(ResultSet rs, int rowNum)で行単位に処理を行って、結果を都度返しているからだと思います。
rs.getString("user_id")からわかるようにResultSetに1つのレコードが代入されているものと思います。
2,BeanPropertyRowMapperを使う。
こちらもUserインスタンスを生成する処理自体を分離させます。通常のRowMapperと違う点はMapperクラスを作らなくてもいい点です。コードを見ていきましょう。
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
public List<User> selectMany() {
//M_USERテーブルのデータを全件取得するSQL
String sql = "SELECT * FROM m_user";
//RowMapperの生成。UserRowMapperを使わなくてもBeanPropertyRowMapperにUser.classを指定すれば同じ事ができる。
RowMapper<User> rowMapper = new BeanPropertyRowMapper<User>(User.class);
//SQL実行
return jdbc.query(sql, rowMapper);
}
要点を以下にまとめます。
・Mapperクラスは作成不要。
・BeanPropertyRowMapperインスタンスを生成。この時、型と生成するインスタンスのクラスを指定する。(UserならUser.classみたいに)
・JdbcTemplateのqueryメソッドを使ってDBから情報を取得。
こんな感じです。
ここまで来るとコードがだいぶスッキリしますね。というよりもこれでUserのインスタンスが生成されるのが不思議に思えてくる。
3,ResultSetExtractorを使う。
ResultSetExtractorを使います。使い方自体は1のRowMapperみたいな感じで、ResultSetExtractorをimportしたMapperクラスを作ってそいつをnewしてJDBCのqueryメソッドを使用します。
public List<User> selectMany() {
//M_USERテーブルのデータを全件取得するSQL
String sql = "SELECT * FROM m_user";
//ResultSetExtractorの生成
UserResultSetExtractor extractor = new UserResultSetExtractor();
//SQL実行
return jdbc.query(sql, extractor);
}
import org.springframework.jdbc.core.ResultSetExtractor;
// ResultSetExtractor<List<T>>をimplementsする。
public class UserResultSetExtractor implements ResultSetExtractor<List<User>> {
@Override // extractDataメソッドをオーバーライドする。
public List<User> extractData(ResultSet rs) throws SQLException, DataAccessException {
//User格納用List
List<User> userList = new ArrayList<>();
//取得件数分のloop
while(rs.next()) {
//Listに追加するインスタンスを生成する
User user = new User();
//取得したレコードをUserインスタンスにセット
user.setUserId(rs.getString("user_id"));
user.setPassword(rs.getString("password"));
user.setUserName(rs.getString("user_name"));
user.setBirthday(rs.getDate("birthday"));
user.setAge(rs.getInt("age"));
user.setMarriage(rs.getBoolean("marriage"));
user.setRole(rs.getString("role"));
//ListにUserを追加
userList.add(user);
}
return userList;
}
}
要点をまとめると。
・ResultSetExtractorをimportする。
・ResultSetExtractor>をimplementsする。
・extractDataメソッドをオーバーライドする。
・ResultSetをloopしてレコード毎に処理を行う。
こんな感じです。
まとめ
以上、3つほどO/Rマッパーのコードを紹介しました。
まだ本番の開発をしたことがないので何とも言えませんが、それぞれに特徴や使うタイミングなどありそうです。
特にBeanPropertyRowMapperなんかはかなり簡単にSQL発行~インスタンス生成までが出来てしまうので有効活用できそうです。