SQLのシーケンスを利用して、データをINSERTする時に主キーなどを自動で割り当てるといった時に、割り当てた値をEntityに格納したい場合の方法です。
前提
INSERT対象テーブル情報
postgresqlのチュートリアルテーブル群を使用しています。
テーブルデータ(Addressテーブル)
シーケンスデータ
CREATE SEQUENCE public.address_address_id_seq
INCREMENT BY 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1
NO CYCLE;
実装
プロダクトコード
Address.java
INSERTするEntityです。
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Address {
int addressId;
String address;
String address2;
String district;
int cityId;
String postalCode;
String phone;
LocalDateTime lastUpdate;
}
AddressMapper.java
Mybatisを利用してAddressテーブルにデータをINSERTします。
アノテーションベースでの実装です。
@Mapper
public interface AddressMapper {
@Insert("INSERT INTO address (" +
"address_id," +
"address," +
"address2," +
"district," +
"city_id," +
"postal_code," +
"phone," +
"last_update" +
")" +
"VALUES (" +
"address_address_id_seq.NEXTVAL," +
"#{address.address}," +
"#{address.address2}," +
"#{address.district}," +
"#{address.cityId}," +
"#{address.postalCode}," +
"#{address.phone}," +
"#{address.lastUpdate}" +
")")
@Options(useGeneratedKeys = true, keyColumn = "address_id", keyProperty = "address.addressId")
void save(@Param("address") Address address);
}
主キーであるaddress_idを、シーケンスを使用して値を設定しています。
@Optionsで指定しているのが自動生成されたaddress_idをEntityのaddressに格納する設定です。
useGeneratedKeys・・・データベース側で自動生成されたキーを取得する。実際にはJDBCのgetGeneratedKeysメソッドを使用している。
keyColumn・・・テーブル内で自動生成が設定されている列名。カンマ区切りで複数指定対応。
keyProperty・・・keyColumnで指定された列名をここで指定したプロパティにセットする。カンマ区切りで複数指定対応。
テストコード
AddressMapperTests.java
private AddressMapper target;
@DisplayName("1件Insertし、シーケンスによって自動生成された値を検証する")
@Test
void test1Insert() {
//GIVEN
Address actualCase = Address.builder()
.address("613 Korolev Drive")
.district("Shibuya")
.cityId(1)
.phone("28303384290")
.lastUpdate(LocalDateTime.of(2015, 5, 2, 8, 50))
.build();
//WHEN
target.save(actualCase);
//THEN
assertThat(actualCase.getAddressId()).isEqualTo(1);
int expect = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM address", Integer.class);
assertThat(expect).isEqualTo(1);
}
INSERTするデータをセットしているactualCaseで、主キーであるaddressIdがセットされていません。(intなので初期値0が入ります。)
target.saveメソッドを実行後、addressIdを1でassertしています。
つまり、メソッド実行後にactualCaseの中に自動生成されたaddressIdが格納されたということです。
備考
@SelectKeyアノテーションを使う方法
@Optionsで取得する方法とは別に、@SelectKeyで自動生成する方法もある。
その場合はこのようになる。
AddressMapper.java
@Insert("INSERT INTO address (" +
"address_id," +
"address," +
"address2," +
"district," +
"city_id," +
"postal_code," +
"phone," +
"last_update" +
")" +
"VALUES (" +
"#{address.addressId}," +
"#{address.address}," +
"#{address.address2}," +
"#{address.district}," +
"#{address.cityId}," +
"#{address.postalCode}," +
"#{address.phone}," +
"#{address.lastUpdate}" +
")")
@SelectKey(statement = "SELECT address_address_id_seq.NEXTVAL",
keyProperty = "address.addressId",
before = true,
resultType = Integer.class)
void save(@Param("address") Address address);
@SelectKeyでシーケンスをSELECTして、事前にaddressIdに自動生成された値を格納させて、それを利用してINSERTしている。
なので、INSERTのVALUES句のaddress_idの部分も変わっている
- @Optionsの方法…SQL側で自動生成させ、SQLが実行された後で値をEntityに格納する。
- @SelectKeyの方法…INSERTより前にSELECTによってシーケンスから自動生成された値をEntityに格納し、そのデータをINSERTしている。
という感じ。
@SelectKeyだとSQLが一つ多く発行されるということですね。