diff --git a/src/main/java/com/avaje/ebean/Transaction.java b/src/main/java/com/avaje/ebean/Transaction.java index 1f281090d..f670ab563 100644 --- a/src/main/java/com/avaje/ebean/Transaction.java +++ b/src/main/java/com/avaje/ebean/Transaction.java @@ -97,6 +97,17 @@ public interface Transaction extends Closeable { */ void setPersistCascade(boolean persistCascade); + /** + * Set to true when you want all loaded properties to be included in the update + * (rather than just the changed properties). + *

+ * You might set this when using JDBC batch in order to get multiple updates + * with slightly different sets of changed properties into the same statement + * and hence better JDBC batch performance. + *

+ */ + void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties); + /** * Turn on or off statement batching. Statement batching can be transparent * for drivers and databases that support getGeneratedKeys. Otherwise you may diff --git a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java index bbed76666..22a9ce6c6 100644 --- a/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java +++ b/src/main/java/com/avaje/ebean/bean/EntityBeanIntercept.java @@ -608,7 +608,7 @@ public final class EntityBeanIntercept implements Serializable { } else if (embeddedDirty != null && embeddedDirty[i]) { // an embedded property has been changed - recurse EntityBean embeddedBean = (EntityBean)owner._ebean_getField(i); - embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i)+"."); + embeddedBean._ebean_getIntercept().addDirtyPropertyValues(dirtyValues, getProperty(i) + "."); } } } @@ -638,6 +638,20 @@ public final class EntityBeanIntercept implements Serializable { return hash; } + /** + * Return a loaded property hash. + */ + public int getLoadedPropertyHash() { + int hash = 37; + int len = getPropertyLength(); + for (int i = 0; i < len; i++) { + if (isLoadedProperty(i)) { + hash = hash * 31 + (i+1); + } + } + return hash; + } + /** * Return the set of property names for changed properties. */ diff --git a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java index 3b0b85b20..79f59fc7e 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/ScopedTransaction.java @@ -152,6 +152,16 @@ public class ScopedTransaction implements SpiTransaction { transaction.setPersistCascade(persistCascade); } + @Override + public void setUpdateAllLoadedProperties(boolean updateAllLoaded) { + transaction.setUpdateAllLoadedProperties(updateAllLoaded); + } + + @Override + public boolean isUpdateAllLoadedProperties() { + return transaction.isUpdateAllLoadedProperties(); + } + @Override public void setBatchMode(boolean useBatch) { transaction.setBatchMode(useBatch); diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java index f0d41f192..3f3dc9e93 100644 --- a/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/api/SpiTransaction.java @@ -99,6 +99,11 @@ public interface SpiTransaction extends Transaction { */ String getId(); + /** + * Return true if this transaction has updateAllLoadedProperties set. + */ + boolean isUpdateAllLoadedProperties(); + /** * Return the batchSize specifically set for this transaction or 0. *

diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java index 7fc55361e..55f6ad25c 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/PersistRequestBean.java @@ -641,10 +641,14 @@ public final class PersistRequestBean extends PersistRequest implements BeanP } /** - * Test if the property value has changed and if so include it in the update. + * Return true if the property should be included in the update. */ public boolean isAddToUpdate(BeanProperty prop) { - return intercept.isDirtyProperty(prop.getPropertyIndex()); + if (transaction.isUpdateAllLoadedProperties()) { + return intercept.isLoadedProperty(prop.getPropertyIndex()); + } else { + return intercept.isDirtyProperty(prop.getPropertyIndex()); + } } public List getDerivedRelationships() { diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java index afadc9090..7a1d9dec0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/UpdateMeta.java @@ -110,12 +110,17 @@ public final class UpdateMeta { private SpiUpdatePlan getDynamicUpdatePlan(ConcurrencyMode mode, PersistRequestBean persistRequest) { - // we can use a cached UpdatePlan for the changed properties - + EntityBeanIntercept ebi = persistRequest.getEntityBeanIntercept(); - int hash = ebi.getDirtyPropertyHash(); - + + int hash; + if (persistRequest.getTransaction().isUpdateAllLoadedProperties()) { + hash = ebi.getLoadedPropertyHash(); + } else { + hash = ebi.getDirtyPropertyHash(); + } + BeanDescriptor beanDescriptor = persistRequest.getBeanDescriptor(); BeanProperty versionProperty = beanDescriptor.getVersionProperty(); diff --git a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java index ff63c2830..ce3b61646 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/src/main/java/com/avaje/ebeaninternal/server/transaction/JdbcTransaction.java @@ -91,6 +91,8 @@ public class JdbcTransaction implements SpiTransaction { protected boolean localReadOnly; + protected boolean updateAllLoadedProperties; + protected PersistBatch oldBatchMode; protected PersistBatch batchMode; @@ -393,6 +395,15 @@ public class JdbcTransaction implements SpiTransaction { } } + @Override + public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) { + this.updateAllLoadedProperties = updateAllLoadedProperties; + } + + public boolean isUpdateAllLoadedProperties() { + return updateAllLoadedProperties; + } + @Override public void setBatchMode(boolean batchMode) { if (!isActive()) { diff --git a/src/test/java/com/avaje/tests/model/basic/EBasicVer.java b/src/test/java/com/avaje/tests/model/basic/EBasicVer.java index c67f970cd..2cb9273ae 100644 --- a/src/test/java/com/avaje/tests/model/basic/EBasicVer.java +++ b/src/test/java/com/avaje/tests/model/basic/EBasicVer.java @@ -8,49 +8,59 @@ import javax.persistence.Table; import javax.persistence.Version; @Entity -@Table(name="e_basicver") +@Table(name = "e_basicver") public class EBasicVer { - - @Id - Integer id; - - String name; - - String description; - - @Version - Timestamp lastUpdate; - - public Integer getId() { - return id; - } - public void setId(Integer id) { - this.id = id; - } + @Id + Integer id; - public String getName() { - return name; - } + String name; - public void setName(String name) { - this.name = name; - } + String description; - public String getDescription() { - return description; - } + String other; - public void setDescription(String description) { - this.description = description; - } + @Version + Timestamp lastUpdate; - public Timestamp getLastUpdate() { - return lastUpdate; - } + public Integer getId() { + return id; + } - public void setLastUpdate(Timestamp lastUpdate) { - this.lastUpdate = lastUpdate; - } + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getOther() { + return other; + } + + public void setOther(String other) { + this.other = other; + } + + public Timestamp getLastUpdate() { + return lastUpdate; + } + + public void setLastUpdate(Timestamp lastUpdate) { + this.lastUpdate = lastUpdate; + } } diff --git a/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java b/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java new file mode 100644 index 000000000..51776e643 --- /dev/null +++ b/src/test/java/com/avaje/tests/update/TestUpdateAllLoadedProperties.java @@ -0,0 +1,108 @@ +package com.avaje.tests.update; + +import com.avaje.ebean.BaseTestCase; +import com.avaje.ebean.Ebean; +import com.avaje.ebean.EbeanServer; +import com.avaje.ebean.Transaction; +import com.avaje.tests.model.basic.EBasicVer; +import org.avaje.ebeantest.LoggedSqlCollector; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class TestUpdateAllLoadedProperties extends BaseTestCase { + + @Test + public void test() { + + EBasicVer basic1 = new EBasicVer(); + basic1.setName("basic1"); + basic1.setDescription("aaa"); + Ebean.save(basic1); + + EBasicVer basic2 = new EBasicVer(); + basic1.setName("basic2"); + basic1.setDescription("bbb"); + Ebean.save(basic2); + + + EbeanServer server = Ebean.getDefaultServer(); + Transaction txn = server.beginTransaction(); + try { + + txn.setUpdateAllLoadedProperties(true); + + LoggedSqlCollector.start(); + + basic2.setDescription("bbb-mod"); + server.save(basic2, txn); + + basic1.setName("basic1-mod"); + basic1.setDescription("aaa-mod"); + server.save(basic1, txn); + + txn.commit(); + + List loggedSql = LoggedSqlCollector.stop(); + + assertEquals(2, loggedSql.size()); + // all properties in the bean + assertTrue(loggedSql.get(0), loggedSql.get(0).contains("update e_basicver set name=?, description=?, other=?, last_update=? where id=? and last_update=?; --bind(")); + assertTrue(loggedSql.get(1), loggedSql.get(1).contains("update e_basicver set name=?, description=?, other=?, last_update=? where id=? and last_update=?; --bind(")); + + } finally { + txn.end(); + } + + testPartiallyLoaded(basic1.getId(), basic2.getId()); + + } + + private void testPartiallyLoaded(Integer id1, Integer id2) { + + List ids = new ArrayList(); + ids.add(id1); + ids.add(id2); + + List beans = Ebean.find(EBasicVer.class) + .select("name, other") + .where().idIn(ids) + .order().asc("id") + .findList(); + + assertEquals(2, beans.size()); + + LoggedSqlCollector.start(); + + + Transaction txn = Ebean.beginTransaction(); + try { + txn.setUpdateAllLoadedProperties(true); + + EBasicVer basic1 = beans.get(0); + basic1.setName("jim"); + Ebean.save(basic1); + + EBasicVer basic2 = beans.get(1); + basic2.setName("john"); + basic2.setOther("otherDesc"); + Ebean.save(basic2); + + txn.commit(); + + } finally { + txn.end(); + } + + List loggedSql = LoggedSqlCollector.stop(); + + assertEquals(2, loggedSql.size()); + assertTrue(loggedSql.get(0), loggedSql.get(0).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind(")); + assertTrue(loggedSql.get(1), loggedSql.get(1).contains("update e_basicver set name=?, other=?, last_update=? where id=?; --bind(")); + + } +}