【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.Driver
JdbcTemplate
宣言
データベース操作用に 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);
}
}
}