diff --git a/ebean-api/src/main/java/io/ebean/Transaction.java b/ebean-api/src/main/java/io/ebean/Transaction.java index 4f7c148189..adaadf00c1 100644 --- a/ebean-api/src/main/java/io/ebean/Transaction.java +++ b/ebean-api/src/main/java/io/ebean/Transaction.java @@ -260,6 +260,12 @@ static Transaction current() { */ void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties); + /** + * If set to false (default is true) generated propertes are only set, if it is the version property or have a null value. + * This may be useful in backup & restore scenarios, if you want set WhenCreated/WhenModified. + */ + void setOverwriteGeneratedProperties(boolean overwriteGeneratedProperties); + /** * Set if the L2 cache should be skipped for "find by id" and "find by natural key" queries. *

diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java index 0e5df22511..2f1605d8d3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransaction.java @@ -103,6 +103,11 @@ public interface SpiTransaction extends Transaction { */ Boolean isUpdateAllLoadedProperties(); + /** + * Returns true, if generated properties are overwritten (default) or are only set, if they are null. + */ + boolean isOverwriteGeneratedProperties(); + /** * Return the batchSize specifically set for this transaction or 0. *

diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java index 9c2057a4a5..7b79a3f5c4 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiTransactionProxy.java @@ -248,6 +248,16 @@ public void setUpdateAllLoadedProperties(boolean updateAllLoaded) { transaction.setUpdateAllLoadedProperties(updateAllLoaded); } + @Override + public void setOverwriteGeneratedProperties(boolean overwriteGeneratedProperties) { + transaction.setOverwriteGeneratedProperties(overwriteGeneratedProperties); + } + + @Override + public boolean isOverwriteGeneratedProperties() { + return transaction.isOverwriteGeneratedProperties(); + } + @Override public Boolean isUpdateAllLoadedProperties() { return transaction.isUpdateAllLoadedProperties(); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java index d6145f5144..bd47b70b33 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/core/PersistRequestBean.java @@ -274,24 +274,30 @@ private void onUpdateGeneratedProperties() { } else { // @WhenModified set without invoking interception Object oldVal = prop.getValue(entityBean); - Object value = generatedProperty.getUpdateValue(prop, entityBean, now()); - prop.setValueChanged(entityBean, value); - intercept.setOldValue(prop.propertyIndex(), oldVal); + if (transaction == null || transaction.isOverwriteGeneratedProperties() || oldVal == null) { // version handled above + Object value = generatedProperty.getUpdateValue(prop, entityBean, now()); + prop.setValueChanged(entityBean, value); + intercept.setOldValue(prop.propertyIndex(), oldVal); + } } } } private void onFailedUpdateUndoGeneratedProperties() { for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) { - Object oldVal = intercept.origValue(prop.propertyIndex()); - prop.setValue(entityBean, oldVal); + if (transaction == null || transaction.isOverwriteGeneratedProperties() || prop.isVersion()) { + Object oldVal = intercept.origValue(prop.propertyIndex()); + prop.setValue(entityBean, oldVal); + } } } private void onInsertGeneratedProperties() { for (BeanProperty prop : beanDescriptor.propertiesGenInsert()) { - Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now()); - prop.setValueChanged(entityBean, value); + if (transaction == null || transaction.isOverwriteGeneratedProperties() || prop.isVersion() || prop.getValue(entityBean) == null) { + Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now()); + prop.setValueChanged(entityBean, value); + } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java index 1353a217db..5573fc6d79 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/ImplicitReadOnlyTransaction.java @@ -272,6 +272,15 @@ public void setReadOnly(boolean readOnly) { public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) { } + @Override + public void setOverwriteGeneratedProperties(boolean overwriteGeneratedProperties) { + } + + @Override + public boolean isOverwriteGeneratedProperties() { + return true; + } + @Override public Boolean isUpdateAllLoadedProperties() { return null; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java index e1b3b773ef..10c482dec9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/JdbcTransaction.java @@ -53,6 +53,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes { private boolean queryOnly = true; private boolean localReadOnly; private Boolean updateAllLoadedProperties; + private boolean overwriteGeneratedProperties = true; private boolean oldBatchMode; private boolean batchMode; private boolean batchOnCascadeMode; @@ -448,6 +449,16 @@ public final Boolean isUpdateAllLoadedProperties() { return updateAllLoadedProperties; } + @Override + public void setOverwriteGeneratedProperties(boolean overwriteGeneratedProperties) { + this.overwriteGeneratedProperties = overwriteGeneratedProperties; + } + + @Override + public boolean isOverwriteGeneratedProperties() { + return overwriteGeneratedProperties; + } + @Override public final void setBatchMode(boolean batchMode) { this.batchMode = batchMode; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java index c18633a79a..e63e50206f 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/transaction/NoTransaction.java @@ -230,6 +230,15 @@ public void setPersistCascade(boolean persistCascade) { public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) { } + @Override + public void setOverwriteGeneratedProperties(boolean overwriteGeneratedProperties) { + } + + @Override + public boolean isOverwriteGeneratedProperties() { + return true; + } + @Override public void setSkipCache(boolean skipCache) { } diff --git a/ebean-test/src/test/java/org/tests/generated/TestGeneratedProperties.java b/ebean-test/src/test/java/org/tests/generated/TestGeneratedProperties.java index ced209f8bc..ee97cc1467 100644 --- a/ebean-test/src/test/java/org/tests/generated/TestGeneratedProperties.java +++ b/ebean-test/src/test/java/org/tests/generated/TestGeneratedProperties.java @@ -1,10 +1,13 @@ package org.tests.generated; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; +import io.ebean.Transaction; +import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.Test; import org.tests.model.EGenProps; +import java.time.Instant; + import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -47,4 +50,42 @@ public void test_insert() { DB.delete(bean); } + + @Test + public void test_update_no_overwrite() { + EGenProps bean = new EGenProps(); + bean.setName("updating"); + DB.save(bean); + + bean = DB.find(EGenProps.class, bean.getId()); + bean.setInstantCreated(Instant.parse("2022-01-01T00:00:00Z")); + bean.setInstantUpdated(Instant.parse("2022-01-02T00:00:00Z")); + try (Transaction txn = DB.beginTransaction()) { + txn.setOverwriteGeneratedProperties(false); + DB.save(bean); + txn.commit(); + } + + bean = DB.find(EGenProps.class, bean.getId()); + assertThat(bean.getInstantCreated()).isEqualTo(Instant.parse("2022-01-01T00:00:00Z")); + assertThat(bean.getInstantUpdated()).isEqualTo(Instant.parse("2022-01-02T00:00:00Z")); + } + + @Test + public void test_insert_no_overwrite() { + EGenProps bean = new EGenProps(); + try (Transaction txn = DB.beginTransaction()) { + txn.setOverwriteGeneratedProperties(false); + bean.setName("inserting"); + bean.setInstantCreated(Instant.parse("2022-01-01T00:00:00Z")); + bean.setInstantUpdated(Instant.parse("2022-01-02T00:00:00Z")); + DB.save(bean); + txn.commit(); + } + + + bean = DB.find(EGenProps.class, bean.getId()); + assertThat(bean.getInstantCreated()).isEqualTo(Instant.parse("2022-01-01T00:00:00Z")); + assertThat(bean.getInstantUpdated()).isEqualTo(Instant.parse("2022-01-02T00:00:00Z")); + } }