【Spring Boot】Spring JDBC
パッケージの読み込み
Spring JDBC を使用するために、以下を追加します。
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'また使用する DB に対応したドライバーを追加してください。
接続設定
application.yml(.properties)に、データベースの接続設定をします。 以下は MySQL の設定例です。
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb
username: hoge
password: fugafuga
driver-class-name: com.mysql.cj.jdbc.DriverJdbcTemplate
宣言
データベース操作用に DAO クラスを作成するとします。
Spring JDBC では、JdbcTemplateクラスを使用してデータベースを操作します。
DAO クラスでは以下のようにJdbcTemplateを宣言します。
@Repository
public class SampleDataDao {
@Autowired
private JdbcTemplate template;
}SQL の実行
まずは SELECT の例を見てみます。
public List<SampleData> findAll() {
//SQL定義
String sql = "select * from sample_data";
//SQL実行
SqlRowSet rs = template.queryForRowSet(sql);
//結果反映
List<SampleData> list = new ArrayList<>();
while(rs.next()) {
list.add(new SampleData(rs.getString("id"),
rs.getString("first_name"),
rs.getString("last_name"),
rs.getInt("age"));)
}
return list;
}SQL の実行はqueryForRowSet()によって行い、SqlRowSetとして結果を受け取ります。
SqlRowSetはイテレーターとなっており、next()によって次のレコードを参照します。
次のレコードが参照できない場合、つまり最終レコードの場合に実行すると戻り値としてfalseを返します。
これを利用してループ処理を行い、各レコードの処理を行います。
以下のように決められた 1 データを取得する場合は、queryForObject()を使用します。
intとlongには、queryForInt()、queryForLong()があります。
public int getCount() {
//SQL定義
String sql = "select count(*) from sample_data";
//SQL実行
int count = template.queryForObject(sql, Integer.class);
return count;
}INSERT, UPDATE, DELETE は、update()を使用します。
public void update() {
//SQL定義
String sql = "update sample_data set age = 21 where id = '000001'";
//SQL実行
template.update(sql);
}パラメーターの指定
JDBC では、?を使用することでプレースホルダーとしてパラメーターを設定します。
public SampleData findById(String id) {
//SQL定義
String sql = "select * from sample_data where id = ?";
//SQL実行
SqlRowSet rs = template.queryForRowSet(sql, id);
//結果反映
return new SampleData(rs.getString("id"), rs.getString("first_name"), rs.getString("last_name"), rs.getInt("age"));
}パラメーターは、queryForRowSet()やupdate()など実行メソッドの引数として設定します。
パラメーターが複数存在する場合は、対応する?の順に引数を増やします。
public void create(SampleData sampleData) {
//SQL定義
String sql = "insert into sample_data values (?, ?, ?, ?)";
//SQL実行
//プレースホルダーの順に引数を設定する
template.update(sql, sampleData.getId(), sampleData.getFirstName(), sampleData,getLastName(), sampleData.getAge());
}NamedParameterJdbcTemplate
宣言
宣言をJdbcTemplateからNamedParameterJdbcTemplateに変更します。
@Repository
public class SampleDataDao {
@Autowired
private NamedParameterJdbcTemplate template;
}SQL 定義
NamedParameterJdbcTemplateでは、SQL 文の中のパラメーターを?ではなく、:paramNameとパラメーター名を使用します。
public void create(SampleData sampleData) {
//SQL定義
String sql = "insert into sample_data values (:id, :firstName, :lastName, :age)";
//...
}Map によるパラメーター指定
SQL 文中のパラメーターへの紐付けは、キーをパラメーター名とするMapインスタンスを作成して行います。
あとは作成したMapインスタンスを実行用のメソッドの引数として指定するだけです。
public void create(SampleData sampleData) {
//SQL定義
String sql = "insert into sample_data values (:id, :firstName, :lastName, :age)";
//パラメーター定義
Map<String, Object> params = new HashMap<>();
params.put("id", sampleData.getString());
params.put("firstName", sampleData.getString());
params.put("lastName", sampleData.getLastName());
params.put("age", sampleData.getAge());
//SQL実行
template.update(sql, params);
}クラスによるパラメーター指定
今回のsampleDataのように、既にパラメーターの情報を持つインスタンスがある場合、Mapを生成する必要はありません。
以下のようにBeanPropertySqlParameterSourceを使用して対応するインスタンスを直接指定することができます。
public void create(SampleData sampleData) {
//SQL定義
String sql = "insert into sample_data values (:id, :firstName, :lastName, :age)";
//パラメーター定義
SqlParameterSource params = new BeanPropertySqlParameterSource(sampleData);
//SQL実行
template.update(sql, params);
}これにより、パラメーター名と同名のフィールドから値が設定されます。
パラメーターがない場合の指定
NamedParameterJdbcTemplateでは、必ずパラメーター用のインスタンスを設定する必要があります。
パラメーターが必要ない場合は、EmptySqlParameterSource.INSTANCEを設定します。
public List<SampleData> findAll() {
//SQL定義
String sql = "select * from sample_data";
//SQL実行
SqlRowSet rs = template.queryForRowSet(sql, EmptySqlParameterSource.INSTANCE);
//結果反映(省略)
}トランザクション
@Transactinal
Spring JDBC では、メソッドに@Transactionalを付与することで、トランザクションの制御を自動で行ってくれます。
@Service
public class SampleDataService {
@Autowired
private SampleDao dao;
@Transactional
public void create(SampleData sampleData) {
//before process
dao.create(sampleDao);
//after process
}
}メソッドの処理開始時にトランザクションも開始され、終了時にコミットされます。
非検査例外(RuntimeExceptionおよびサブクラス)が発生した場合はロールバックされます。
検査例外が発生した場合はコミットされてしまいます。
検査例外でもロールバックしたい場合は以下のようにrollbackForプロパティを設定します。
@Transactional(rollbackFor = Exception.class)
public void create(SampleData sampleData) {
//...
}@Transactionalには以下のプロパティがあります。
| プロパティ | デフォルト | 説明 |
|---|---|---|
propagation | Propagation.REQUIRED | トランザクションの伝播タイプ(詳細は後述) |
isolation | Isolation.DEFAULT | トランザクションの分離レベル(詳細は後述) |
timeout | システムのデフォルト値 | トランザクションのタイムアウト(秒) |
readOnly | false | トランザクションが読み取り専用であることを設定 |
rollbackFor | ロールバックの条件である例外クラスを設定 | |
noRollbackFor | 例外の中でロールバックを行わないクラスを設定 |
伝播タイプ
トランザクションの伝搬タイプとは、現在のトランザクションの状態を基に、どのようなトランザクションを使用するかを表すものです。
@Transactional(propagation = Propagation.XXXXX)
public void methodA() {
//...
}@Transactional
public void methodB() {
//トランザクションありで実行される
serviceB.methodA();
}
public void methodC() {
//トランザクションなしで実行される
serviceB.methodA()
}プロパティ値として列挙型であるPropagationの値を指定します。
Propagation | トランザクションあり | トランザクションなし | 補足 |
|---|---|---|---|
REQUIRED | 引き継ぐ | 新規生成 | デフォルト値 |
SUPPORTS | 引き継ぐ | 非トランザクション | |
MANDATORY | 引き継ぐ | 例外スロー | |
REQUIRES_NEW | 新規生成 | 新規生成 | 既存のトランザクションは一時停止される |
NOT_SUPPORTED | 非トランザクション | 非トランザクション | 既存のトランザクションは一時停止される |
NEVER | 例外スロー | 非トランザクション | |
NESTED | ネスト | 新規生成 |
分離レベル
トランザクションには、ダーディリード(Dirty Read)、ノンリピータブルリード(Non-repeatable Read)、 ファントムリード(Phantom Read)という問題があります。
それぞれの詳細については割愛しますが、どの問題を許容するかを分離レベルとして設定します。
分離レベルは、列挙型のIsolationの値によって設定します。
以下表の「●」は、問題が発生することを表します。
Isolation | Dirty Read | Non-repeatable Read | Phantom Read |
|---|---|---|---|
READ_UNCOMMITED | ● | ● | ● |
READ_COMMITED | - | ● | ● |
REPEATABLE_READ | - | - | ● |
SERIALIZABLE | - | - | - |
PlatformTransactionManager
@Transactionalを使用せず、自身でトランザクション処理のタイミングを決めたい場合は、PlatformTransactionManagerを使用します。
@Service
public SampleDataService {
@Autowired
private PlatformTransactionManager trnManager;
@Autowired
private SampleDataDao dao
public void create(SampleData sampleData) {
//トランザクション設定
DefaultTransactionDefinition trnDefinition = new DefaultTransactionDefinition();
TransactionStatus trnStatus = trnManager.getTransaction(trnDefinition);
try {
dao.create();
//コミット
trnManager.commit(trnStatus);
} catch (RuntimeException e) {
//ロールバック
trnManager.rollback(trnStatus);
}
}
}