1. はじめに
今回はspring-jdbcのjdbcTemplateを利用して、spring bootの組み込みh2dbにDBアクセスする方法について説明したいと思います。といってもh2dbに関する話はほとんどなく、単純にjdbcTemplateの使い方になります。
組み込みh2dbの準備については「spring bootで組み込みh2dbを利用する方法」を参照ねがいます。
jdbcTemplateではなく他のORMapperでDBアクセスをする場合は以下の記事を参照ねがいます。
- MyBatisを利用する場合
- Sql2oを利用する場合
2. JdbcTemplateによるDBアクセスのポイント
JdbcTemplate
によるDBアクセスをする際のポイントについて以下に示します。
基本的なCRUDであれば、ここで示す内容で十分かと思います。
- パラメータ部分を
?
で定義するJdbcTemplate
と:パラメータ名
で定義するNamedParameterJdbcTemplate
がある- 可読性が高い
NamedParameterJdbcTemplate
の利用を推奨
- 可読性が高い
- DBアクセスするクラスはリポジトリと呼び、
@Repository
クラスアノテーションを付与する -
NamedParameterJdbcTemplate
を@Autowired
等でリポジトリクラスにインジェクションする - SQL文は文字列型として定数や変数として定義する
-
@Value
で外出しするのも可能 - パラメータ部分は
:パラメータ名
で定義する
-
- パラメータが多い場合は
BeanPropertySqlParameterSource
のコンストラクタを利用して定義するのが便利 - パラメータが少ない場合は
MapSqlParameterSource
のビルダーパターンを利用して定義するのが便利 -
INSERT
、DELETE
、UPDATE
のSQLはjdbcTemplate
のupdate
メソッドを利用する- 戻り値の
int
は更新結果の行数である
- 戻り値の
-
SELECT
のSQL文の場合、結果を特定のクラスにマッピングするためRowMapper
を利用する - 1行を取得する
SELECT
のSQLはjdbcTemplate
のqueryForObject
メソッドを利用する - 複数行を取得する
SELECT
のSQLはjdbcTemplate
のquery
メソッドを利用する
3. サンプル
3.1. テーブルの定義
spring bootではクラスパス直下のschema.sql
ファイルを起動時に自動で実行してくれます。
このファイルに今回利用するテーブルのCREATE TABLE
文を記述します。
h2dbのデータ型については以下の公式ガイドラインを参照ください。
http://www.h2database.com/html/datatypes.html
-- file_info table
CREATE TABLE IF NOT EXISTS file_info (
file_id varchar(100) PRIMARY KEY,
file_type varchar(100) NOT NULL,
file_name varchar(200) NOT NULL,
file_path varchar(200) NOT NULL,
content_type varchar(100) NOT NULL,
content_length bigint NOT NULL,
registered_date timestamp NOT NULL
);
3.2. マッピング対象のクラス
テーブルに対応するマッピング対象のクラスを定義します。
といっても普通のPOJOのクラスです。
package com.example.demo.domain.model;
import java.io.Serializable;
import java.sql.Timestamp;
public class FileInfo implements Serializable {
private static final long serialVersionUID = 1L;
private String contentType;
private long contentLength;
private String fileName;
private String fileType;
private String fileId;
private String filePath;
private Timestamp registeredDate;
// constructor,setter,getter omitted
}
3.3. リポジトリのクラス
今回の記事のメインとなるリポジトリのクラスを定義します。
ポイントは前述した「2. JdbcTemplateによるDBアクセスのポイント」の通りです。
実装上の注意としては検索結果が0件の場合、org.springframework.dao.EmptyResultDataAccessException
が発生するということです。今回は他のORMapperと同じく、0件の場合はnull
を返すようにしました。
なおDataAccessException
をDemoSystemException
にラップしているのは当方の都合です。jdbcTemplateの使い方とは直接的には関係ありません。
package com.example.demo.domain.repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.stereotype.Repository;
import com.example.demo.domain.common.DemoSystemException;
import com.example.demo.domain.model.FileInfo;
@Repository
public class FileInfoRepository {
private static final Logger LOGGER = LoggerFactory.getLogger(FileInfoRepository.class);
private static final String INSERT_SQL = "INSERT INTO file_info ("
+ "file_id, file_type, file_name, file_path, content_type, content_length, registered_date) values ("
+ ":fileId, :fileType, :fileName, :filePath, :contentType, :contentLength, :registeredDate)";
private static final String DELETE_BY_KEY_SQL = "DELETE FROM file_info WHERE file_id = :fileId";
private static final String UPDATE_BY_KEY_SQL = "UPDATE file_info SET file_type = :fileType, file_name = :fileName, "
+ "file_path = :filePath, content_type = :contentType, content_length = :contentLength, registered_date = :registeredDate "
+ "WHERE file_id = :fileId";
private static final String FIND_ONE_SQL = "SELECT file_id, file_type, file_name, file_path, content_type, content_length, registered_date "
+ "FROM file_info WHERE file_id = :fileId";
private static final String FIND_ALL_SQL = "SELECT file_id, file_type, file_name, file_path, content_type, content_length, registered_date "
+ "FROM file_info ORDER BY file_type, registered_date";
@Autowired
private NamedParameterJdbcTemplate jdbcTemplate;
public int insert(FileInfo fileInfo) {
try {
SqlParameterSource param = new BeanPropertySqlParameterSource(fileInfo);
return jdbcTemplate.update(INSERT_SQL, param);
} catch(DataAccessException e) {
LOGGER.error("DataAccessError : INSERT_SQL param:{}, error:{}", fileInfo, e);
throw new DemoSystemException("DataAccessError : FileInfoRepository INSERT_SQL", e);
}
}
public int deleteByKey(String fileId) {
try {
SqlParameterSource param = new MapSqlParameterSource().addValue("fileId", fileId);
return jdbcTemplate.update(DELETE_BY_KEY_SQL, param);
} catch(DataAccessException e) {
LOGGER.error("DataAccessError : DELETE_BY_KEY_SQL param:{}, error:{}", fileId, e);
throw new DemoSystemException("DataAccessError : FileInfoRepository DELETE_BY_KEY_SQL", e);
}
}
public int updateByKey(FileInfo fileInfo) {
try {
SqlParameterSource param = new BeanPropertySqlParameterSource(fileInfo);
return jdbcTemplate.update(UPDATE_BY_KEY_SQL, param);
} catch(DataAccessException e) {
LOGGER.error("DataAccessError : UPDATE_BY_KEY_SQL param:{}, error:{}", fileInfo, e);
throw new DemoSystemException("DataAccessError : FileInfoRepository UPDATE_BY_KEY_SQL", e);
}
}
public FileInfo fineOne(String fileId) {
try {
SqlParameterSource param = new MapSqlParameterSource().addValue("fileId", fileId);
return jdbcTemplate.queryForObject(FIND_ONE_SQL, param, fileInfoRowMapper());
} catch(EmptyResultDataAccessException e) {
return null;
} catch(DataAccessException e) {
LOGGER.error("DataAccessError : FIND_ONE_SQL param:{}, error:{}", fileId, e);
throw new DemoSystemException("DataAccessError : FileInfoRepository FIND_ONE_SQL", e);
}
}
public List<FileInfo> findAll() {
try {
return jdbcTemplate.query(FIND_ALL_SQL, fileInfoRowMapper());
} catch(EmptyResultDataAccessException e) {
return null;
} catch(DataAccessException e) {
LOGGER.error("DataAccessError : FIND_ALL_SQL error:{}", e);
throw new DemoSystemException("DataAccessError : FileInfoRepository FIND_ALL_SQL", e);
}
}
private RowMapper<FileInfo> fileInfoRowMapper() {
return new RowMapper<FileInfo>() {
@Override
public FileInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
FileInfo fileInfo = new FileInfo();
fileInfo.setContentLength(rs.getLong("content_length"));
fileInfo.setContentType(rs.getString("content_type"));
fileInfo.setFileId(rs.getString("file_id"));
fileInfo.setFileName(rs.getString("file_name"));
fileInfo.setFilePath(rs.getString("file_path"));
fileInfo.setFileType(rs.getString("file_type"));
fileInfo.setRegisteredDate(rs.getTimestamp("registered_date"));
return fileInfo;
}
};
}
}
4. さいごに
今回はspring-jdbcのjdbcTemplateを利用して、spring bootの組み込みh2dbにDBアクセスする方法について説明しました。
簡単なDBアクセスであればORMapperを導入しなくてもjdbcTemplateで十分かと思います。