たとえばoracleでは下記で連番を割り振れる。
CREATE TABLE sample_gen( id number GENERATED ALWAYS AS IDENTITY , value VARCHAR2(100) NOT NULL , CONSTRAINT sample_gen_pk PRIMARY KEY (id) );
import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.Data; @Data @Entity(name = "sample_gen") public class SampleGenEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) int id; @Column String value; }
これをJPA経由でinsertするとその連番の値も取得できる。sequence値を別途nextval
で取得してないのに連番が取れてしまう。旧来のoracleユーザからすると不思議だったので少し調べた。
@PersistenceContext EntityManager em; @Transactional public void execute() { SampleGenEntity e = new SampleGenEntity(); e.setValue("value"); em.persist(e); System.out.println(e);
Hibernate: insert into sample_gen (value,id) values (?,default) SampleGenEntity(id=23, value=value)
調べた事とか
環境
- Oracle Database 23ai Free Release 23.0.0.0.0
plugins { id 'java' id 'org.springframework.boot' version '3.4.2' id 'io.spring.dependency-management' version '1.1.7' } group = 'com.example' version = '0.0.1-SNAPSHOT' java { toolchain { languageVersion = JavaLanguageVersion.of(17) } } configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.oracle.database.jdbc:ojdbc11' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } tasks.named('test') { useJUnitPlatform() }
prepareStatementで自動生成列名を指定
Connection#prepareStatement(String sql, String[] columnNames))という自動生成列名を指定するメソッドが存在する。これを使用するとinsertと同時に連番の値も取得できる。おそらくは内部的にこのようなロジックになっているのだと思われる。
try (Connection c = ds.getConnection(); PreparedStatement ps = c .prepareStatement( "insert into sample_gen(id, value) values (default, ?)", new String[]{"id"});) { ps.setString(1, "asdfasdfa111"); ResultSet resultSet = ps.executeQuery(); resultSet.next(); System.out.println(resultSet.getInt(1)); }
JdbcTemplateも同様
JdbcTemplateの場合もほぼ同様。
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; JdbcTemplate jdbc = new JdbcTemplate(ds); KeyHolder keyHolder = new GeneratedKeyHolder(); jdbc.update(connection -> { PreparedStatement ps = connection .prepareStatement( "insert into sample_gen(id, value) values (default, ?)", new String[]{"id"}); ps.setString(1, "asdfasdsdff"); return ps; }, keyHolder); System.out.println(keyHolder.getKey()); }
参考;https://www.baeldung.com/spring-jdbc-autogenerated-keys
はまった点
The generated key type is not supported. Unable to cast [oracle.sql.ROWID] to [java.lang.Number].
Connection#prepareStatementにはautoGeneratedKeys
というint型を渡すメソッドもあり、たとえば.prepareStatement("insert ...", Statement.RETURN_GENERATED_KEYS);
のように使う。ただ、このケースでは使えないようだ。下記の通りROWID
を返そうとする。おそらくは、RDBMSだとかJDBCだとかバージョンだとかによって異なってくるのだろう(これ以上は調べてない)。
org.springframework.dao.DataRetrievalFailureException: The generated key type is not supported. Unable to cast [oracle.sql.ROWID] to [java.lang.Number]. at org.springframework.jdbc.support.GeneratedKeyHolder.getKeyAs(GeneratedKeyHolder.java:86) ~[spring-jdbc-6.2.2.jar:6.2.2] at org.springframework.jdbc.support.GeneratedKeyHolder.getKey(GeneratedKeyHolder.java:65) ~[spring-jdbc-6.2.2.jar:6.2.2]