diff --git a/src/main/java/io/ebean/config/dbplatform/DbIdentity.java b/src/main/java/io/ebean/config/dbplatform/DbIdentity.java index af1f4574c..c57626b3f 100644 --- a/src/main/java/io/ebean/config/dbplatform/DbIdentity.java +++ b/src/main/java/io/ebean/config/dbplatform/DbIdentity.java @@ -8,7 +8,9 @@ import java.util.regex.Pattern; */ public class DbIdentity { - private static final Pattern TABLE_REPLACE = Pattern.compile("{table}", Pattern.LITERAL); + private static final String TABLE_PLACEHOLDER = "{table}"; + + private static final Pattern TABLE_REPLACE = Pattern.compile(TABLE_PLACEHOLDER, Pattern.LITERAL); /** * Set if this DB supports sequences. Note some DB's support both Sequences @@ -53,7 +55,9 @@ public class DbIdentity { if (selectLastInsertedIdTemplate == null) { return null; } - + if (!selectLastInsertedIdTemplate.contains(TABLE_PLACEHOLDER)) { + return selectLastInsertedIdTemplate; + } return TABLE_REPLACE.matcher(selectLastInsertedIdTemplate).replaceAll(Matcher.quoteReplacement(table)); } diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index 44e5a32ad..acaa7c57d 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -1398,4 +1398,10 @@ public final class PersistRequestBean extends PersistRequest implements BeanP return orphanBean; } + /** + * Return the SQL used to fetch the last inserted id value. + */ + public String getSelectLastInsertedId() { + return beanDescriptor.getSelectLastInsertedId(publish); + } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index f114970f9..6f951ab87 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -182,6 +182,7 @@ public class BeanDescriptor implements BeanType, STreeType { * getGeneratedKeys is not supported. */ private final String selectLastInsertedId; + private final String selectLastInsertedIdDraft; private final boolean autoTunable; @@ -473,6 +474,7 @@ public class BeanDescriptor implements BeanType, STreeType { this.sequenceInitialValue = deploy.getSequenceInitialValue(); this.sequenceAllocationSize = deploy.getSequenceAllocationSize(); this.selectLastInsertedId = deploy.getSelectLastInsertedId(); + this.selectLastInsertedIdDraft = deploy.getSelectLastInsertedIdDraft(); this.concurrencyMode = deploy.getConcurrencyMode(); this.updateChangesOnly = deploy.isUpdateChangesOnly(); this.indexDefinitions = deploy.getIndexDefinitions(); @@ -3086,8 +3088,15 @@ public class BeanDescriptor implements BeanType, STreeType { * supported. *

*/ - public String getSelectLastInsertedId() { - return selectLastInsertedId; + public String getSelectLastInsertedId(boolean publish) { + return publish ? selectLastInsertedId : selectLastInsertedIdDraft; + } + + /** + * Return true if this bean uses a SQL select to fetch the last inserted id value. + */ + public boolean supportsSelectLastInsertedId() { + return selectLastInsertedId != null; } @Override diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java index 470df5ac8..1083235cc 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptorManager.java @@ -1387,9 +1387,10 @@ public class BeanDescriptorManager implements BeanDescriptorMap { } if (IdType.IDENTITY == desc.getIdType()) { - // used when getGeneratedKeys is not supported (SQL Server 2000) + // used when getGeneratedKeys is not supported (SQL Server 2000, SAP Hana) String selectLastInsertedId = dbIdentity.getSelectLastInsertedId(desc.getBaseTable()); - desc.setSelectLastInsertedId(selectLastInsertedId); + String selectLastInsertedIdDraft = (!desc.isDraftable()) ? selectLastInsertedId : dbIdentity.getSelectLastInsertedId(desc.getDraftTable()); + desc.setSelectLastInsertedId(selectLastInsertedId, selectLastInsertedIdDraft); return; } diff --git a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java index e6688c222..76a54220a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/meta/DeployBeanDescriptor.java @@ -126,6 +126,7 @@ public class DeployBeanDescriptor { * Used with Identity columns but no getGeneratedKeys support. */ private String selectLastInsertedId; + private String selectLastInsertedIdDraft; /** * The concurrency mode for beans of this type. @@ -839,11 +840,16 @@ public class DeployBeanDescriptor { return selectLastInsertedId; } + public String getSelectLastInsertedIdDraft() { + return selectLastInsertedIdDraft; + } + /** * Set the SQL used to return the last inserted Id. */ - public void setSelectLastInsertedId(String selectLastInsertedId) { + public void setSelectLastInsertedId(String selectLastInsertedId, String selectLastInsertedIdDraft) { this.selectLastInsertedId = selectLastInsertedId; + this.selectLastInsertedIdDraft = selectLastInsertedIdDraft; } /** diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java index 8e7246730..70e2917c8 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/InsertHandler.java @@ -39,7 +39,7 @@ public class InsertHandler extends DmlHandler { * A SQL Select used to fetch back the Id where generatedKeys is not * supported. */ - private String selectLastInsertedId; + private boolean useSelectLastInsertedId; /** * Create to handle the insert execution. @@ -79,7 +79,7 @@ public class InsertHandler extends DmlHandler { useGeneratedKeys = true; } else { // use a query to get the last inserted id - selectLastInsertedId = meta.getSelectLastInsertedId(); + useSelectLastInsertedId = meta.supportsSelectLastInsertedId(); } } @@ -117,8 +117,7 @@ public class InsertHandler extends DmlHandler { } /** - * Execute the insert in a normal non batch fashion. Additionally using - * getGeneratedKeys if required. + * Execute non batched insert additionally using getGeneratedKeys if required. */ @Override public int execute() throws SQLException, OptimisticLockException { @@ -127,7 +126,7 @@ public class InsertHandler extends DmlHandler { // get the auto-increment value back and set into the bean getGeneratedKeys(); - } else if (selectLastInsertedId != null) { + } else if (useSelectLastInsertedId) { // fetch back the Id using a query fetchGeneratedKeyUsingSelect(); } @@ -167,12 +166,10 @@ public class InsertHandler extends DmlHandler { */ private void fetchGeneratedKeyUsingSelect() throws SQLException { - Connection conn = transaction.getConnection(); - PreparedStatement stmt = null; ResultSet rset = null; try { - stmt = conn.prepareStatement(selectLastInsertedId); + stmt = transaction.getConnection().prepareStatement(persistRequest.getSelectLastInsertedId()); rset = stmt.executeQuery(); setGeneratedKey(rset); } finally { diff --git a/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java b/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java index ff11081aa..d38ae2952 100644 --- a/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java +++ b/src/main/java/io/ebeaninternal/server/persist/dml/InsertMeta.java @@ -38,7 +38,7 @@ public final class InsertMeta { /** * Used for DB that do not support getGeneratedKeys. */ - private final String selectLastInsertedId; + private final boolean supportsSelectLastInsertedId; private final Bindable shadowFKey; @@ -69,7 +69,7 @@ public final class InsertMeta { this.sqlNullId = null; this.sqlDraftNullId = null; this.supportsGetGeneratedKeys = false; - this.selectLastInsertedId = null; + this.supportsSelectLastInsertedId = false; } else { // insert sql for db identity or sequence insert @@ -77,11 +77,11 @@ public final class InsertMeta { if (id.getIdentityColumn() == null) { this.identityDbColumns = new String[]{}; this.supportsGetGeneratedKeys = false; - this.selectLastInsertedId = null; + this.supportsSelectLastInsertedId = false; } else { this.identityDbColumns = new String[]{id.getIdentityColumn()}; this.supportsGetGeneratedKeys = dbPlatform.getDbIdentity().isSupportsGetGeneratedKeys(); - this.selectLastInsertedId = desc.getSelectLastInsertedId(); + this.supportsSelectLastInsertedId = desc.supportsSelectLastInsertedId(); } this.sqlNullId = genSql(true, tableName, false); this.sqlDraftNullId = desc.isDraftable() ? genSql(true, draftTableName, true) : sqlNullId; @@ -116,15 +116,11 @@ public final class InsertMeta { } /** - * Returns sql that is used to fetch back the last inserted id. This will - * return null if it should not be used. - *

- * This is only for DB's that do not support getGeneratedKeys. For MS - * SQLServer 2000 this could return "SELECT (at)(at)IDENTITY as id". - *

+ * Return true if we should use a SQL query to return the generated key. + * This can not be used with JDBC batch mode. */ - public String getSelectLastInsertedId() { - return selectLastInsertedId; + public boolean supportsSelectLastInsertedId() { + return supportsSelectLastInsertedId; } /** diff --git a/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java b/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java new file mode 100644 index 000000000..857577a9c --- /dev/null +++ b/src/test/java/io/ebean/config/PlatformNoGeneratedKeysTest.java @@ -0,0 +1,103 @@ +package io.ebean.config; + +import io.ebean.EbeanServer; +import io.ebean.EbeanServerFactory; +import io.ebean.Transaction; +import io.ebean.annotation.Platform; +import io.ebean.config.dbplatform.DbIdentity; +import io.ebean.config.dbplatform.IdType; +import io.ebean.config.dbplatform.h2.H2Platform; +import org.junit.Test; +import org.tests.model.basic.EBasicVer; +import org.tests.model.draftable.BasicDraftableBean; + +import static org.assertj.core.api.StrictAssertions.assertThat; + +public class PlatformNoGeneratedKeysTest { + + static EbeanServer server = testH2Server(); + + @Test + public void insertBatch_expect_noIdValuesFetched() { + + EBasicVer b0 = new EBasicVer("a"); + EBasicVer b1 = new EBasicVer("b"); + EBasicVer b2 = new EBasicVer("c"); + + try (Transaction transaction = server.beginTransaction()) { + transaction.setBatchMode(true); + + server.save(b0); + server.save(b1); + server.save(b2); + + transaction.commit(); + } + + assertThat(b0.getId()).isNull(); + assertThat(b1.getId()).isNull(); + assertThat(b2.getId()).isNull(); + + } + + @Test + public void insertNoBatch_expect_selectIdentity() { + + EBasicVer b0 = new EBasicVer("one"); + server.save(b0); + + assertThat(b0.getId()).isNotNull(); + + + BasicDraftableBean d0 = new BasicDraftableBean("done"); + server.save(d0); + + assertThat(d0.getId()).isNotNull(); + + server.publish(BasicDraftableBean.class, d0.getId()); + + BasicDraftableBean one = server.find(BasicDraftableBean.class, d0.getId()); + + assertThat(one.getName()).isEqualTo("done"); + assertThat(one.isDraft()).isFalse(); + } + + private static EbeanServer testH2Server() { + + ServerConfig config = new ServerConfig(); + config.setName("h2_noGeneratedKeys"); + + OtherH2Platform platform = new OtherH2Platform(); + DbIdentity dbIdentity = platform.getDbIdentity(); + dbIdentity.setIdType(IdType.IDENTITY); + dbIdentity.setSupportsIdentity(true); + dbIdentity.setSupportsGetGeneratedKeys(false); + dbIdentity.setSupportsSequence(false); + dbIdentity.setSelectLastInsertedIdTemplate("select identity() --{table}"); + + config.setDatabasePlatform(platform); + config.getDataSourceConfig().setUsername("sa"); + config.getDataSourceConfig().setPassword(""); + config.getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;"); + config.getDataSourceConfig().setDriver("org.h2.Driver"); + + config.setDisableL2Cache(true); + config.setDefaultServer(false); + config.setRegister(false); + config.setDdlGenerate(true); + config.setDdlRun(true); + config.getClasses().add(EBasicVer.class); + config.getClasses().add(BasicDraftableBean.class); + + + return EbeanServerFactory.create(config); + } + + static class OtherH2Platform extends H2Platform { + + OtherH2Platform() { + super(); + this.platform = Platform.GENERIC; + } + } +} diff --git a/src/test/java/org/tests/model/draftable/BasicDraftableBean.java b/src/test/java/org/tests/model/draftable/BasicDraftableBean.java new file mode 100644 index 000000000..98b79cb68 --- /dev/null +++ b/src/test/java/org/tests/model/draftable/BasicDraftableBean.java @@ -0,0 +1,32 @@ +package org.tests.model.draftable; + +import io.ebean.annotation.Draft; +import io.ebean.annotation.Draftable; + +import javax.persistence.Entity; + +@Entity +@Draftable +public class BasicDraftableBean extends BaseDomain { + + private String name; + + @Draft + boolean draft; + + public BasicDraftableBean(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isDraft() { + return draft; + } +} diff --git a/src/test/resources/extra-ddl.xml b/src/test/resources/extra-ddl.xml index 8ca843ad6..bb786221b 100644 --- a/src/test/resources/extra-ddl.xml +++ b/src/test/resources/extra-ddl.xml @@ -4,7 +4,7 @@ drop view order_agg_vw if exists; - + create or replace view order_agg_vw as select d.order_id, sum(d.order_qty * d.unit_price) as order_total,