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が一つ多く発行されるということですね。
コメントを書く