From af030e3da409df5640d117a6a063c04df81c4f6c Mon Sep 17 00:00:00 2001 From: rob bygrave Date: Wed, 30 May 2018 03:31:32 +1200 Subject: [PATCH] #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement ENH: Add merge() API (using default merge options) to EbeanServer, Ebean and BeanRepository enhancement --- src/main/java/io/ebean/BeanRepository.java | 24 ++++++ src/main/java/io/ebean/Ebean.java | 9 ++ src/main/java/io/ebean/EbeanServer.java | 7 ++ .../java/io/ebean/MergeOptionsBuilder.java | 17 +++- src/main/java/io/ebean/SqlUpdate.java | 55 ++++++++++-- .../io/ebeaninternal/api/SpiEbeanServer.java | 11 +++ .../server/core/DefaultServer.java | 17 ++++ .../server/core/DefaultSqlUpdate.java | 51 ++++++++++- .../server/core/PersistRequestUpdateSql.java | 20 ++++- .../ebeaninternal/server/core/Persister.java | 11 +++ .../server/deploy/BeanDescriptor.java | 4 +- .../server/deploy/BeanPropertyAssocMany.java | 42 ++++++++- .../server/deploy/IntersectionBuilder.java | 85 +++++++++++++++++++ .../server/deploy/IntersectionRow.java | 14 +-- .../server/deploy/IntersectionTable.java | 34 ++++++++ .../server/deploy/id/ImportedId.java | 11 +++ .../server/deploy/id/ImportedIdEmbedded.java | 23 ++++- .../server/deploy/id/ImportedIdSimple.java | 15 ++++ .../server/persist/BatchControl.java | 8 ++ .../server/persist/BatchedPstmt.java | 10 ++- .../server/persist/BatchedPstmtHolder.java | 14 +++ .../server/persist/DefaultPersister.java | 19 ++++- .../ebeaninternal/api/TDSpiEbeanServer.java | 15 ++++ 23 files changed, 484 insertions(+), 32 deletions(-) create mode 100644 src/main/java/io/ebeaninternal/server/deploy/IntersectionBuilder.java create mode 100644 src/main/java/io/ebeaninternal/server/deploy/IntersectionTable.java diff --git a/src/main/java/io/ebean/BeanRepository.java b/src/main/java/io/ebean/BeanRepository.java index 3c588b65c..b926ea8a0 100644 --- a/src/main/java/io/ebean/BeanRepository.java +++ b/src/main/java/io/ebean/BeanRepository.java @@ -167,6 +167,30 @@ public abstract class BeanRepository extends BeanFinder { return db().deletePermanent(bean); } + /** + * Merge this entity using the default merge options. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see EbeanServer#merge(Object) + */ + public void merge(T bean) { + db().merge(bean); + } + + /** + * Merge this entity using the specified merge options. + *

+ * Ebean will detect if this is a new bean or a previously fetched bean and perform either an + * insert or an update based on that. + * + * @see EbeanServer#merge(Object, MergeOptions) + */ + public void merge(T bean, MergeOptions options) { + db().merge(bean, options); + } + /** * Refreshes this entity from the database. * diff --git a/src/main/java/io/ebean/Ebean.java b/src/main/java/io/ebean/Ebean.java index 97f895921..09d2d738b 100644 --- a/src/main/java/io/ebean/Ebean.java +++ b/src/main/java/io/ebean/Ebean.java @@ -638,6 +638,15 @@ public final class Ebean { serverMgr.getDefaultServer().updateAll(beans); } + /** + * Merge the bean using the default merge options. + * + * @param bean The bean to merge + */ + public static void merge(Object bean) { + serverMgr.getDefaultServer().merge(bean); + } + /** * Merge the bean using the given merge options. * diff --git a/src/main/java/io/ebean/EbeanServer.java b/src/main/java/io/ebean/EbeanServer.java index 1b9157c1f..23b974591 100644 --- a/src/main/java/io/ebean/EbeanServer.java +++ b/src/main/java/io/ebean/EbeanServer.java @@ -1628,6 +1628,13 @@ public interface EbeanServer { */ void updateAll(Collection beans, Transaction transaction) throws OptimisticLockException; + /** + * Merge the bean using the default merge options (no paths specified, default delete). + * + * @param bean The bean to merge + */ + void merge(Object bean); + /** * Merge the bean using the given merge options. * diff --git a/src/main/java/io/ebean/MergeOptionsBuilder.java b/src/main/java/io/ebean/MergeOptionsBuilder.java index 10365346c..7193c681c 100644 --- a/src/main/java/io/ebean/MergeOptionsBuilder.java +++ b/src/main/java/io/ebean/MergeOptionsBuilder.java @@ -8,11 +8,20 @@ import java.util.Set; */ public class MergeOptionsBuilder { + private static final MOptions DEFAULT_OPTIONS = new MOptions(); + private Set paths = new LinkedHashSet<>(); private boolean clientGeneratedIds; - private boolean deletePermanent = true; + private boolean deletePermanent; + + /** + * Return the default options. + */ + public static MergeOptions defaultOptions() { + return DEFAULT_OPTIONS; + } /** * Add a path that will included in the merge. @@ -57,6 +66,12 @@ public class MergeOptionsBuilder { private final boolean deletePermanent; private final Set paths; + private MOptions(){ + this.clientGeneratedIds = false; + this.paths = new LinkedHashSet<>(); + this.deletePermanent = false; + } + private MOptions(Set paths, boolean clientGeneratedIds, boolean deletePermanent) { this.paths = paths; this.clientGeneratedIds = clientGeneratedIds; diff --git a/src/main/java/io/ebean/SqlUpdate.java b/src/main/java/io/ebean/SqlUpdate.java index 87364334d..9884c095c 100644 --- a/src/main/java/io/ebean/SqlUpdate.java +++ b/src/main/java/io/ebean/SqlUpdate.java @@ -19,15 +19,17 @@ package io.ebean; *

*

{@code
  *
- * // example that uses 'named' parameters
- * String s = "UPDATE f_topic set post_count = :count where id = :id";
- * SqlUpdate update = Ebean.createSqlUpdate(s);
- * update.setParameter("id", 1);
- * update.setParameter("count", 50);
+ *   // example that uses 'named' parameters
  *
- * int modifiedCount = Ebean.execute(update);
+ *   String s = "UPDATE f_topic set post_count = :count where id = :id";
  *
- * String msg = "There were " + modifiedCount + " rows updated";
+ *   SqlUpdate update = Ebean.createSqlUpdate(s);
+ *   update.setParameter("id", 1);
+ *   update.setParameter("count", 50);
+ *
+ *   int modifiedCount = update.execute();
+ *
+ *   String msg = "There were " + modifiedCount + " rows updated";
  *
  * }
*

@@ -57,6 +59,33 @@ package io.ebean; * * txn.commit(); * } + * } + *

+ * An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like: + *

+ *
{@code
+ *
+ *   try (Transaction txn = Ebean.beginTransaction()) {
+ *
+ *     insert.setNextParameter(10000);
+ *     insert.setNextParameter("hello");
+ *     insert.setNextParameter("rob");
+ *     insert.addBatch();
+ *
+ *     insert.setNextParameter(10001);
+ *     insert.setNextParameter("goodbye");
+ *     insert.setNextParameter("rob");
+ *     insert.addBatch();
+ *
+ *     insert.setNextParameter(10002);
+ *     insert.setNextParameter("chow");
+ *     insert.setNextParameter("bob");
+ *     insert.addBatch();
+ *
+ *     int[] rows = insert.executeBatch();
+ *
+ *     txn.commit();
+ *   }
  *
  * }
* @@ -83,6 +112,18 @@ public interface SqlUpdate { */ int execute(); + /** + * Execute when addBatch() has been used to batch multiple bind executions. + * + * @return The row counts for each of the batched statements. + */ + int[] executeBatch(); + + /** + * Add the statement to batch processing to then later execute via executeBatch(). + */ + void addBatch(); + /** * Return the generated key value. */ diff --git a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java index 8232773fc..a4d887365 100644 --- a/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java +++ b/src/main/java/io/ebeaninternal/api/SpiEbeanServer.java @@ -268,4 +268,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL * Return true if a row for the bean type and id exists. */ boolean exists(Class beanType, Object beanId, Transaction transaction); + + /** + * Add to JDBC batch for later execution. + */ + void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction); + + /** + * Execute the batched statement. + */ + int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction); + } diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java index 1a904843b..c63638240 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultServer.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultServer.java @@ -13,6 +13,7 @@ import io.ebean.FutureIds; import io.ebean.FutureList; import io.ebean.FutureRowCount; import io.ebean.MergeOptions; +import io.ebean.MergeOptionsBuilder; import io.ebean.PagedList; import io.ebean.PersistenceContextScope; import io.ebean.ProfileLocation; @@ -65,6 +66,7 @@ import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiJsonContext; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.SpiQuery.Type; +import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.api.SpiTransactionManager; import io.ebeaninternal.api.TransactionEventTable; @@ -919,6 +921,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return new DefaultUpdateQuery<>(createQuery(beanType)); } + @Override + public void merge(Object bean) { + merge(bean, MergeOptionsBuilder.defaultOptions(), null); + } + @Override public void merge(Object bean, MergeOptions options) { merge(bean, options, null); @@ -1961,6 +1968,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer { return persister.executeSqlUpdate(updSql, t); } + @Override + public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) { + persister.addBatch(sqlUpdate, transaction); + } + + @Override + public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) { + return persister.executeBatch(sqlUpdate, transaction); + } + /** * Execute the updateSql. */ diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java b/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java index 92e40b48b..d9d4e6500 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java @@ -1,11 +1,12 @@ package io.ebeaninternal.server.core; import io.ebean.Ebean; -import io.ebean.EbeanServer; import io.ebean.SqlUpdate; import io.ebean.Update; import io.ebeaninternal.api.BindParams; +import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlUpdate; +import io.ebeaninternal.api.SpiTransaction; import java.io.Serializable; @@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { private static final long serialVersionUID = -6493829438421253102L; - private transient final EbeanServer server; + private transient final SpiEbeanServer server; /** * The parameters used to bind to the sql. @@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { private Object generatedKey; + /** + * Set when batching explicitly used. + */ + private boolean batched; + + /** + * Transaction used for addBatch() executeBatch() processing. + */ + private transient SpiTransaction transaction; + /** * Create with server sql and bindParams object. *

@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { * same time. *

*/ - public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) { + public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) { this.server = server; this.sql = sql; this.bindParams = bindParams; @@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { * Create with a specific server. This means you can use the * SqlUpdate.execute() method. */ - public DefaultSqlUpdate(EbeanServer server, String sql) { + public DefaultSqlUpdate(SpiEbeanServer server, String sql) { this(server, sql, new BindParams()); } @@ -111,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { @Override public int execute() { if (server != null) { + if (batched) { + server.executeBatch(this, transaction); + return -1; + } return server.execute(this); } else { // Hopefully this doesn't catch anyone out... @@ -118,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { } } + @Override + public int[] executeBatch() { + if (server == null) { + throw new IllegalStateException("No EbeanServer set?"); + } + if (!batched) { + throw new IllegalStateException("No prior addBatch() called?"); + } + return server.executeBatch(this, transaction); + } + + + @Override + public void addBatch() { + if (server == null) { + throw new IllegalStateException("No EbeanServer set?"); + } + if (transaction == null) { + transaction = server.currentServerTransaction(); + if (transaction == null) { + throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()"); + } + } + + batched = true; + server.addBatch(this, transaction); + } + @Override public Object getGeneratedKey() { return generatedKey; diff --git a/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java b/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java index 202eb5141..6aa17970c 100644 --- a/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java +++ b/src/main/java/io/ebeaninternal/server/core/PersistRequestUpdateSql.java @@ -1,6 +1,5 @@ package io.ebeaninternal.server.core; -import io.ebean.SqlUpdate; import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; @@ -27,15 +26,17 @@ public final class PersistRequestUpdateSql extends PersistRequest { private String description; + private boolean addBatch; + /** * Create. */ - public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate, + public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate, SpiTransaction t, PersistExecute persistExecute) { super(server, t, persistExecute, sqlUpdate.getLabel()); this.type = Type.UPDATESQL; - this.updateSql = (SpiSqlUpdate) sqlUpdate; + this.updateSql = sqlUpdate; updateSql.reset(); } @@ -44,11 +45,24 @@ public final class PersistRequestUpdateSql extends PersistRequest { profileBase(EVT_UPDATESQL, offset, (short)0, flushCount); } + /** + * Add this statement to JDBC batch for later execution. + */ + public int addBatch() { + this.addBatch = true; + return executeOrQueue(); + } + @Override public int executeNow() { return persistExecute.executeSqlUpdate(this); } + @Override + public boolean isBatchThisRequest() { + return addBatch || super.isBatchThisRequest(); + } + @Override public int executeOrQueue() { return executeStatement(); diff --git a/src/main/java/io/ebeaninternal/server/core/Persister.java b/src/main/java/io/ebeaninternal/server/core/Persister.java index 82e46b856..20cbfcce4 100644 --- a/src/main/java/io/ebeaninternal/server/core/Persister.java +++ b/src/main/java/io/ebeaninternal/server/core/Persister.java @@ -8,6 +8,7 @@ import io.ebean.Transaction; import io.ebean.Update; import io.ebean.bean.EntityBean; import io.ebean.meta.MetricVisitor; +import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; import io.ebeaninternal.server.deploy.BeanDescriptor; @@ -91,4 +92,14 @@ public interface Persister { * Visit the metrics. */ void visitMetrics(MetricVisitor visitor); + + /** + * Add the statement to JDBC batch for later execution via executeBatch. + */ + void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction); + + /** + * Execute the associated batched statement. + */ + int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java index 807fbf16b..ee277666b 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanDescriptor.java @@ -637,9 +637,9 @@ public class BeanDescriptor implements BeanType, STreeType { */ public void setEbeanServer(SpiEbeanServer ebeanServer) { this.ebeanServer = ebeanServer; - for (BeanPropertyAssocMany aPropertiesMany : propertiesMany) { + for (BeanPropertyAssocMany assocMany : propertiesMany) { // used for creating lazy loading lists etc - aPropertiesMany.setLoader(ebeanServer); + assocMany.setEbeanServer(ebeanServer); } } diff --git a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index f3bf893bc..687cc9d84 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/io/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -7,10 +7,10 @@ import io.ebean.Transaction; import io.ebean.bean.BeanCollection; import io.ebean.bean.BeanCollection.ModifyListenMode; import io.ebean.bean.BeanCollectionAdd; -import io.ebean.bean.BeanCollectionLoader; import io.ebean.bean.EntityBean; import io.ebean.bean.PersistenceContext; import io.ebean.text.PathProperties; +import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.api.SpiExpressionRequest; import io.ebeaninternal.api.SpiQuery; import io.ebeaninternal.api.json.SpiJsonReader; @@ -50,6 +50,8 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST private final String intersectionPublishTable; private final String intersectionDraftTable; + private IntersectionTable intersectionTable; + /** * For ManyToMany this is the Inverse join used to build reference queries. */ @@ -352,10 +354,30 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST * Set the lazy load server to help create reference collections (that lazy * load on demand). */ - public void setLoader(BeanCollectionLoader loader) { + public void setEbeanServer(SpiEbeanServer server) { if (help != null) { - help.setLoader(loader); + help.setLoader(server); } + if (manyToMany) { + intersectionTable = initIntersectionTable(); + } + } + + private IntersectionTable initIntersectionTable() { + + IntersectionBuilder row = new IntersectionBuilder(intersectionPublishTable, intersectionDraftTable); + for (ExportedProperty exportedProperty : exportedProperties) { + row.addColumn(exportedProperty.getForeignDbColumn()); + } + importedId.buildImport(row); + return row.build(); + } + + /** + * Return the intersection table helper. + */ + public IntersectionTable intersectionTable() { + return intersectionTable; } /** @@ -755,6 +777,20 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc implements ST return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable); } + /** + * Bind the two side of a many to many to the given SqlUpdate. + */ + public void intersectionBind(SqlUpdate sql, EntityBean parentBean, EntityBean other) { + if (embeddedExportedProperties) { + BeanProperty idProp = descriptor.getIdProperty(); + parentBean = (EntityBean) idProp.getValue(parentBean); + } + for (ExportedProperty exportedProperty : exportedProperties) { + sql.setNextParameter(exportedProperty.getValue(parentBean)); + } + importedId.bindImport(sql, other); + } + private void buildExport(IntersectionRow row, EntityBean parentBean) { if (embeddedExportedProperties) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/IntersectionBuilder.java b/src/main/java/io/ebeaninternal/server/deploy/IntersectionBuilder.java new file mode 100644 index 000000000..d5658bf14 --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/IntersectionBuilder.java @@ -0,0 +1,85 @@ +package io.ebeaninternal.server.deploy; + +import java.util.ArrayList; +import java.util.List; + +/** + * Helper to build IntersectionTable. + */ +public class IntersectionBuilder { + + private final String publishTable; + private final String draftTable; + + private final List columns = new ArrayList<>(); + + IntersectionBuilder(String publishTable, String draftTable) { + this.publishTable = publishTable; + this.draftTable = draftTable; + } + + public void addColumn(String column) { + columns.add(column); + } + + public IntersectionTable build() { + + String insertSql = insertSql(publishTable); + String deleteSql = deleteSql(publishTable); + + String draftInsertSql; + String draftDeleteSql; + if (publishTable.equals(draftTable)) { + draftInsertSql = insertSql; + draftDeleteSql = deleteSql; + } else { + draftInsertSql = insertSql(draftTable); + draftDeleteSql = deleteSql(draftTable); + } + + return new IntersectionTable(insertSql, deleteSql, draftInsertSql, draftDeleteSql); + } + + private String insertSql(String tableName) { + + StringBuilder sb = new StringBuilder(); + sb.append("insert into ").append(tableName).append(" ("); + + int count = 0; + for (String column : columns) { + if (count++ > 0) { + sb.append(", "); + } + sb.append(column); + } + sb.append(") values ("); + for (int i = 0; i < count; i++) { + if (i > 0) { + sb.append(", "); + } + sb.append("?"); + } + sb.append(")"); + + return sb.toString(); + } + + private String deleteSql(String tableName) { + + StringBuilder sb = new StringBuilder(); + sb.append("delete from ").append(tableName); + sb.append(" where "); + + int count = 0; + for (String column : columns) { + if (count++ > 0) { + sb.append(" and "); + } + sb.append(column); + sb.append(" = ?"); + } + + return sb.toString(); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/IntersectionRow.java b/src/main/java/io/ebeaninternal/server/deploy/IntersectionRow.java index a0d6f3beb..59c13838a 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/IntersectionRow.java +++ b/src/main/java/io/ebeaninternal/server/deploy/IntersectionRow.java @@ -1,8 +1,8 @@ package io.ebeaninternal.server.deploy; -import io.ebean.EbeanServer; import io.ebean.SqlUpdate; import io.ebeaninternal.api.BindParams; +import io.ebeaninternal.api.SpiEbeanServer; import io.ebeaninternal.server.core.DefaultSqlUpdate; import io.ebeaninternal.server.expression.DefaultExpressionRequest; import io.ebeaninternal.server.expression.IdInExpression; @@ -22,12 +22,12 @@ public class IntersectionRow { private List excludeIds; private BeanDescriptor excludeDescriptor; - public IntersectionRow(String tableName, BeanDescriptor targetDescriptor) { + IntersectionRow(String tableName, BeanDescriptor targetDescriptor) { this.tableName = tableName; this.targetDescriptor = targetDescriptor; } - public IntersectionRow(String tableName) { + IntersectionRow(String tableName) { this.tableName = tableName; this.targetDescriptor = null; } @@ -35,7 +35,7 @@ public class IntersectionRow { /** * Set Id's to exclude. This is for deleting non-attached detail Id's. */ - public void setExcludeIds(List excludeIds, BeanDescriptor excludeDescriptor) { + void setExcludeIds(List excludeIds, BeanDescriptor excludeDescriptor) { this.excludeIds = excludeIds; this.excludeDescriptor = excludeDescriptor; } @@ -44,7 +44,7 @@ public class IntersectionRow { values.put(key, value); } - public SqlUpdate createInsert(EbeanServer server) { + public SqlUpdate createInsert(SpiEbeanServer server) { BindParams bindParams = new BindParams(); @@ -72,7 +72,7 @@ public class IntersectionRow { return new DefaultSqlUpdate(server, sb.toString(), bindParams); } - public SqlUpdate createDelete(EbeanServer server, boolean softDelete) { + public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) { BindParams bindParams = new BindParams(); @@ -107,7 +107,7 @@ public class IntersectionRow { return new DefaultSqlUpdate(server, sb.toString(), bindParams); } - public SqlUpdate createDeleteChildren(EbeanServer server) { + public SqlUpdate createDeleteChildren(SpiEbeanServer server) { BindParams bindParams = new BindParams(); diff --git a/src/main/java/io/ebeaninternal/server/deploy/IntersectionTable.java b/src/main/java/io/ebeaninternal/server/deploy/IntersectionTable.java new file mode 100644 index 000000000..785c2be4c --- /dev/null +++ b/src/main/java/io/ebeaninternal/server/deploy/IntersectionTable.java @@ -0,0 +1,34 @@ +package io.ebeaninternal.server.deploy; + +import io.ebean.EbeanServer; +import io.ebean.SqlUpdate; + +public class IntersectionTable { + + private final String insertSql; + private final String deleteSql; + private final String draftInsertSql; + private final String draftDeleteSql; + + IntersectionTable(String insertSql, String deleteSql, String draftInsertSql, String draftDeleteSql) { + this.insertSql = insertSql; + this.deleteSql = deleteSql; + this.draftInsertSql = draftInsertSql; + this.draftDeleteSql = draftDeleteSql; + } + + /** + * Return a SqlUpdate for inserting into the intersection table. + */ + public SqlUpdate insert(EbeanServer server, boolean draft) { + return server.createSqlUpdate(draft ? draftInsertSql : insertSql); + } + + /** + * Return a SqlUpdate for deleting from the intersection table. + */ + public SqlUpdate delete(EbeanServer server, boolean draft) { + return server.createSqlUpdate(draft ? draftDeleteSql : deleteSql); + } + +} diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedId.java b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedId.java index 422b0c41f..24b7c1bfe 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedId.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedId.java @@ -4,6 +4,7 @@ import io.ebean.SqlUpdate; import io.ebean.bean.EntityBean; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.deploy.IntersectionBuilder; import io.ebeaninternal.server.deploy.IntersectionRow; import io.ebeaninternal.server.persist.dml.GenerateDmlRequest; import io.ebeaninternal.server.persist.dmlbind.BindableRequest; @@ -66,4 +67,14 @@ public interface ImportedId { * Return the set importedId clause. */ String importedIdClause(); + + /** + * Add DB columns to the intersection builder. + */ + void buildImport(IntersectionBuilder row); + + /** + * Bind values to the intersection SqlUpdate. + */ + void bindImport(SqlUpdate sql, EntityBean other); } diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java index 1a37c7f52..22c80dfc9 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdEmbedded.java @@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; import io.ebeaninternal.server.deploy.BeanPropertyAssocOne; import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.deploy.IntersectionBuilder; import io.ebeaninternal.server.deploy.IntersectionRow; import io.ebeaninternal.server.persist.dml.GenerateDmlRequest; import io.ebeaninternal.server.persist.dmlbind.BindableRequest; @@ -130,15 +131,31 @@ public class ImportedIdEmbedded implements ImportedId { EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other); if (embeddedId == null) { - String msg = "Foreign Key value null?"; - throw new PersistenceException(msg); + throw new PersistenceException("Foreign Key value null?"); } - for (ImportedIdSimple anImported : imported) { Object scalarValue = anImported.foreignProperty.getValue(embeddedId); row.put(anImported.localDbColumn, scalarValue); } + } + @Override + public void buildImport(IntersectionBuilder row) { + for (ImportedIdSimple importedScalar : imported) { + row.addColumn(importedScalar.localDbColumn); + } + } + + @Override + public void bindImport(SqlUpdate sql, EntityBean other) { + EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other); + if (embeddedId == null) { + throw new PersistenceException("Foreign Key value null?"); + } + for (ImportedIdSimple anImported : imported) { + Object scalarValue = anImported.foreignProperty.getValue(embeddedId); + sql.setNextParameter(scalarValue); + } } /** diff --git a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java index 803ff4b56..e84b07e49 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java +++ b/src/main/java/io/ebeaninternal/server/deploy/id/ImportedIdSimple.java @@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanFkeyProperty; import io.ebeaninternal.server.deploy.BeanProperty; import io.ebeaninternal.server.deploy.BeanPropertyAssoc; import io.ebeaninternal.server.deploy.DbSqlContext; +import io.ebeaninternal.server.deploy.IntersectionBuilder; import io.ebeaninternal.server.deploy.IntersectionRow; import io.ebeaninternal.server.persist.dml.GenerateDmlRequest; import io.ebeaninternal.server.persist.dmlbind.BindableRequest; @@ -98,6 +99,20 @@ public final class ImportedIdSimple implements ImportedId, Comparable