SQLのシーケンスを利用して、データをINSERTする時に主キーなどを自動で割り当てるといった時に、割り当てた値をEntityに格納したい場合の方法です。
前提
INSERT対象テーブル情報
postgresqlのチュートリアルテーブル群を使用しています。
テーブルデータ(Addressテーブル)

シーケンスデータ
1 2 3 4 5 6 7 | CREATE SEQUENCE public .address_address_id_seq INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1 NO CYCLE; |
実装
プロダクトコード
Address.java
INSERTするEntityです。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 | @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します。
アノテーションベースでの実装です。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | @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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | @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が一つ多く発行されるということですね。
コメントを書く