概要
データアクセスフレームワークであるSpring JDBCを使用する方法についてまとめた。
当記事では、JdbcTemplateのSELECT処理であるqueryForObjectメソッドの使い方について紹介する。
前提
以下の記事の続きとなる。
概要 データアクセスフレームワークであるSpring JDBCを使用して、H2DBにアクセスする方法についてまとめた。 当記事ではSpring JDBCの特徴と、H2DBにアクセスするための準備方法について紹介する。 […]
概要 データアクセスフレームワークであるSpring JDBCを使用する方法についてまとめた。 当記事ではDAOの作成と、DBアクセスの動確を行う方法について紹介する。 ※尚、DAOパターンとは何かについての説明は割愛する &n[…]
特徴
JdbcTemplate.queryForObjectメソッドは、SELECT文を実行してDBから値を取得するために使用する。
また、取得する値は「単一のカラム」もしくは「単一のオブジェクト」となる。
使い方
JdbcTemplate.queryForObjectメソッドは以下のように使用する。
SQL文字列,
パラメータ ※省略可,
戻り値の型
)
文字列のSQL文を指定する。
パラメータが存在しない場合は設定不要。
存在する場合、new Object[]{}にパラメータを格納して指定する。
取得したい単一の型を指定する。
以上がJdbcTemplate.queryForObjectメソッドの基本的な使い方となる。
使用例
実際にJdbcTemplate.queryForObjectメソッドの使用例について紹介する。
単一カラムの取得
JdbcTemplate.queryForObjectメソッドを実行して、単一カラムを取得する。
COUNT関数を用いて数値を取得
以下は生徒数を取得する例となる。
インターフェース
StudentDao.java
public interface StudentDao {
/** 生徒の合計数を取得 */
int getStudentCount();
}
実装
JdbcStudentDao.java
@Component
public class JdbcStudentDao implements StudentDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 生徒の合計数を取得する
*
* @return すべての生徒数
*/
@Override
public int getStudentCount() {
var sql = "SELECT COUNT(*) FROM student";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
}
戻り値で取得したい型を上記のように常に指定する必要がある。
使用結果
FooMain.java
// 生徒の合計数を取得する
int studentCount = studentDao.getStudentCount();
System.out.println(studentCount);
// ------------------------
// コンソール
// ------------------------
// 3
パラメータを指定して文字列を取得
生徒IDを指定して生徒名を取得する。
インターフェース
StudentDao.java
/** 生徒の名前を取得 */
String findNameById(int id);
実装
JdbcStudentDao.java
/**
* 生徒の名前を取得する
*
* @param id 生徒ID
* @return 条件に一致する生徒名
*/
@Override
public String findNameById(int id) {
var sql = "SELECT name FROM student WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[] { id }, String.class);
}
return jdbcTemplate.queryForObject(sql, new Object[] { id }, String.class);
パラメータを指定して動的にSQL文を作成したいとき、「?」のプレースホルダを用意する。
そして、バインドさせたいパラメータはnew Object[]{}に設定する。
プレースホルダを使用する主な利点は、SQLインジェクション攻撃などのセキュリティリスクを軽減できること、およびクエリの可読性と保守性を向上させることにある。
使用結果
FooMain.java
// 生徒名を取得する
String name = studentDao.findNameById(2);
System.out.println(name);
// ------------------------
// コンソール
// ------------------------
// 佐藤花子
◇パラメータを複数指定して数値を取得
とある試験日の閾値を指定した生徒数を取得する。
インターフェース
StudentDao.java
/** 国語と算数の点数の閾値およびテスト実施日で生徒数を取得 */
int countByScoresAndDate(int jaScore, int maScore, Date testDate);
実装
JdbcStudentDao.java
/**
* 国語と算数の点数の閾値およびテスト実施日で生徒数を取得する
*
* @param jaScore 国語の点数
* @param maScore 算数の点数
* @param testDate テスト実施日
* @return 条件に一致する生徒数
*/
@Override
public int countByScoresAndDate(int jaScore, int maScore, Date testDate) {
// SQL用のDate型に変換
var sqlDate = new java.sql.Date(testDate.getTime());
var sql = "SELECT COUNT(DISTINCT student_id) FROM test_point "
+ "WHERE japanese >= ? AND mathematics >= ? AND test_date = ?";
return jdbcTemplate.queryForObject(sql, new Object[] { jaScore, maScore, sqlDate }, Integer.class);
}
var sqlDate = new java.sql.Date(testDate.getTime());
日付形式(DATE型)のパラメータをSQL文として扱うには、java.sql.Dateに変換する必要がある。
java.util.Dateの日付では時刻情報も保持しているが、java.sql.DateはSQLのDate型に対応して日付のみを保持するという違いがある。
複数パラメータを指定したい場合、プレースホルダ「?」にバインドさせる順番でnew Object[]{}にパラメータを設定する。
使用結果
FooMain.java
// 2023/5/22実施のテストで国語76点以上かつ算数65点以上の生徒数を取得する
var testDate = new SimpleDateFormat("yyyy-MM-dd").parse("2023-05-22");
int resCount = studentDao.countByScoresAndDate(76, 65, testDate);
System.out.println(resCount);
// ------------------------
// コンソール
// ------------------------
// 2
単一オブジェクトの取得
JdbcTemplate.queryForObjectメソッドを実行して、単一オブジェクトを取得する。
パラメータを指定してオブジェクトを取得
生徒IDを指定して生徒オブジェクトを取得する。
インターフェース
StudentDao.java
/** 生徒情報を取得 */
Student findStudentById(int id);
実装
JdbcStudentDao.java
/** Studentクラスに変換するRowMapper */
private final RowMapper<Student> studentRowMapper = (rs, i) -> {
var student = new Student();
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setGrade(rs.getInt("grade"));
student.setClassName(rs.getString("class_name"));
student.setMemo(rs.getString("memo"));
return student;
};
/**
* 生徒情報を取得する
*
* @param id 生徒ID
* @return 条件に一致する生徒情報
*/
@Override
public Student findStudentById(int id) {
var sql = "SELECT id, grade, name, class_name, memo FROM student WHERE id = ?";
return jdbcTemplate.queryForObject(sql, new Object[] { id }, studentRowMapper);
}
RowMapperはSpring JDBCのJdbcTemplateで使用するインターフェース。
DBから取得したクエリ結果(ResultSet)をJavaオブジェクトにマッピングする役割をもつ。
上記はRowMapperインターフェースのmapRowメソッドをラムダ式で実装している。
引数のrsはクエリ結果、iは現在の行番号を意味する。
student.setId(rs.getInt(“id”));
student.setName(rs.getString(“name”));
student.setGrade(rs.getInt(“grade”));
student.setClassName(rs.getString(“class_name”));
student.setMemo(rs.getString(“memo”));
return student;
DBから取得したrs(ResultSet)をStudentオブジェクトにORマッピングしている。
戻り値の型をRowMapperとすることで、DBからの結果を指定したオブジェクトにバインドして返却できる。
使用結果
FooMain.java
// 生徒情報を取得する
Student studentInfo = studentDao.findStudentById(3);
System.out.println(studentInfo);
// ------------------------
// コンソール
// ------------------------
// Student(id=3, grade=3, name=鈴木一郎, className=3-C, memo=芸術に秀でる, tests=null)
RowMapperの補足
JdbcTemplate.queryForObjectメソッドの引数にRowMapperオブジェクトを渡すと、以下の流れでORマッピングを行う。
②ResultSetを引数に、RowMapperのmapRowメソッドを呼び出す
③mapRowメソッドの中で定義した処理(rsからJavaオブジェクトへの変換)が呼ばれる
④変換されたJavaオブジェクトがqueryForObjectメソッドの戻り値として返却される
BeanPropertyRowMapperを使用する場合
BeanPropertyRowMapperはSpring JDBCで提供されるRowMapperの実装の一つで、結果セットのカラム名とJavaオブジェクトのプロパティ名を自動でマッピングする機能を提供する。
便利だが、パフォーマンスを重視する場合はmapRowメソッドの実装が推奨される。
使用するタイミングとしては、プロトタイプ作成時や性能を重視しない且つ単純なDBとJavaオブジェクトのORマッピングが可能な場合などに限定される。
実装例
JdbcStudentDao.java
@Override
public Student findStudentUseBeanPropertyById(int id) {
var sql = "SELECT id, grade, name, class_name, memo FROM student WHERE id = ?";
var rowMapper = new BeanPropertyRowMapper<Student>(Student.class);
return jdbcTemplate.queryForObject(sql, new Object[] { id }, rowMapper);
}
BeanPropertyMapperに、マッピングしたい型を上記のように指定してオブジェクトを作成する。
あとはqueryForObjectメソッドに渡すだけで自動でORマッピングしてくれる。
テスト情報も含める場合
上記の単一オブジェクトを返却するケースだとstudentテーブルの情報しか返却していない。
生徒情報に紐づくテスト情報リストも含めたい場合、別の記事で紹介するqueryメソッドを使用する。
注意点
JdbcTemplate.queryForObjectメソッドを使用する際には注意が必要になる。
このメソッドは正確に1行の結果を取得できることを期待している。
そのため、メソッドの実行結果が0行または2行以上の場合は例外がスローされる。
・2行以上の場合: IncorrectResultSizeDataAccessExceptionがスロー
JdbcTemplate.queryForObjectメソッドを使用する場合、例外ハンドリングもあわせて検討すること。