mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-25 03:31:07 +00:00
Add Transaction.setGeneratedPropertiesEnabled(boolean) (#3799)
Adds transaction-level control over whether Ebean auto-generates values for @WhenCreated, @WhenModified, @WhoCreated and @WhoModified properties.
Motivation
Backup/restore scenarios need to preserve the original audit timestamps and user values when re-inserting exported data. Without this, every save overwrites those fields with the current time/user.
Usage
try (Transaction txn = DB.beginTransaction()) {
txn.setGeneratedPropertiesEnabled(false);
bean.setWhenCreated(originalTimestamp);
bean.setWhenModified(originalTimestamp);
DB.save(bean);
txn.commit();
}
Behaviour
- Disabled (false): generated property values are only written if the property currently has a null value. Any non-null value set on the bean is preserved.
- @Version is unaffected: the version property always auto-increments regardless of this setting, preserving optimistic locking integrity.
- Default is true: all existing behaviour is unchanged.
Files changed
- Transaction — new setGeneratedPropertiesEnabled(boolean) with javadoc
- SpiTransaction — new isGeneratedPropertiesEnabled()
- SpiTransactionProxy — delegates both methods
- JdbcTransaction — field + implementation (default true)
- NoTransaction, ImplicitReadOnlyTransaction — no-op setter, true getter
- PersistRequestBean — onInsertGeneratedProperties, onUpdateGeneratedProperties, onFailedUpdateUndoGeneratedProperties all gate on isGeneratedPropertiesEnabled()
- TestGeneratedProperties — 3 new tests covering insert-preserves, insert-null-still-filled, and update-preserves
Supersedes
PR #2943 — same feature, renamed from setOverwriteGeneratedProperties to setGeneratedPropertiesEnabled for a clearer, positive-sense API.
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
This commit is contained in:
committed by
robin.bygrave
co-authored by
robin.bygrave
parent
59793ad3c3
commit
3bb7fbecb1
@@ -235,6 +235,27 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties);
|
||||
|
||||
/**
|
||||
* Set to false to disable auto-generation of {@code @WhenCreated}, {@code @WhenModified},
|
||||
* {@code @WhoCreated} and {@code @WhoModified} values for this transaction.
|
||||
* <p>
|
||||
* When disabled, Ebean will only set a generated property value if the property currently
|
||||
* has a null value (for inserts) or is a {@code @Version} property. Any value already set
|
||||
* on the bean is preserved.
|
||||
* <p>
|
||||
* This is useful in backup and restore scenarios where you need to retain the original
|
||||
* audit timestamps and user values rather than have them overwritten.
|
||||
* <pre>{@code
|
||||
* try (Transaction txn = DB.beginTransaction()) {
|
||||
* txn.setGeneratedPropertiesEnabled(false);
|
||||
* bean.setWhenCreated(originalTimestamp);
|
||||
* DB.save(bean);
|
||||
* txn.commit();
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
void setGeneratedPropertiesEnabled(boolean enable);
|
||||
|
||||
/**
|
||||
* Set if the L2 cache should be skipped for "find by id" and "find by natural key" queries.
|
||||
* <p>
|
||||
|
||||
@@ -103,6 +103,11 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
Boolean isUpdateAllLoadedProperties();
|
||||
|
||||
/**
|
||||
* Return true if generated properties ({@code @WhenCreated} etc.) are enabled for this transaction.
|
||||
*/
|
||||
boolean isGeneratedPropertiesEnabled();
|
||||
|
||||
/**
|
||||
* Return the batchSize specifically set for this transaction or 0.
|
||||
* <p>
|
||||
|
||||
@@ -216,6 +216,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
transaction.setUpdateAllLoadedProperties(updateAllLoaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratedPropertiesEnabled(boolean enable) {
|
||||
transaction.setGeneratedPropertiesEnabled(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneratedPropertiesEnabled() {
|
||||
return transaction.isGeneratedPropertiesEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isUpdateAllLoadedProperties() {
|
||||
return transaction.isUpdateAllLoadedProperties();
|
||||
|
||||
@@ -229,13 +229,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
GeneratedProperty generatedProperty = prop.generatedProperty();
|
||||
if (prop.isVersion()) {
|
||||
if (isLoadedProperty(prop)) {
|
||||
// @Version property must be loaded to be involved
|
||||
// @Version property must be loaded to be involved — always auto-incremented
|
||||
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
|
||||
Object oldVal = prop.getValue(entityBean);
|
||||
setVersionValue(value);
|
||||
intercept.setOldValue(prop.propertyIndex(), oldVal);
|
||||
}
|
||||
} else {
|
||||
} else if (transaction == null || transaction.isGeneratedPropertiesEnabled()) {
|
||||
// @WhenModified set without invoking interception
|
||||
Object oldVal = prop.getValue(entityBean);
|
||||
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
|
||||
@@ -247,17 +247,22 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
private void onFailedUpdateUndoGeneratedProperties() {
|
||||
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
|
||||
Object oldVal = intercept.origValue(prop.propertyIndex());
|
||||
if (oldVal != null) {
|
||||
prop.setValue(entityBean, oldVal);
|
||||
if (prop.isVersion() || transaction == null || transaction.isGeneratedPropertiesEnabled()) {
|
||||
// undo version always (it was always set); undo others only if they were set
|
||||
Object oldVal = intercept.origValue(prop.propertyIndex());
|
||||
if (oldVal != null) {
|
||||
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 (prop.isVersion() || transaction == null || transaction.isGeneratedPropertiesEnabled() || prop.getValue(entityBean) == null) {
|
||||
Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now());
|
||||
prop.setValueChanged(entityBean, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -255,6 +255,15 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratedPropertiesEnabled(boolean enable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneratedPropertiesEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBatchMode(boolean batchMode) {
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
private boolean queryOnly = true;
|
||||
private boolean localReadOnly;
|
||||
private Boolean updateAllLoadedProperties;
|
||||
private boolean generatedPropertiesEnabled = true;
|
||||
private boolean oldBatchMode;
|
||||
private boolean batchMode;
|
||||
private boolean batchOnCascadeMode;
|
||||
@@ -410,6 +411,16 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
|
||||
return updateAllLoadedProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setGeneratedPropertiesEnabled(boolean enable) {
|
||||
this.generatedPropertiesEnabled = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isGeneratedPropertiesEnabled() {
|
||||
return generatedPropertiesEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setBatchMode(boolean batchMode) {
|
||||
this.batchMode = batchMode;
|
||||
|
||||
@@ -210,6 +210,15 @@ final class NoTransaction implements SpiTransaction {
|
||||
public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratedPropertiesEnabled(boolean enable) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneratedPropertiesEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSkipCache(boolean skipCache) {
|
||||
}
|
||||
|
||||
@@ -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,70 @@ public class TestGeneratedProperties extends BaseTestCase {
|
||||
|
||||
DB.delete(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_insert_generatedPropertiesDisabled_preservesBeanValues() {
|
||||
Instant created = Instant.parse("2022-01-01T00:00:00Z");
|
||||
Instant updated = Instant.parse("2022-01-02T00:00:00Z");
|
||||
|
||||
EGenProps bean = new EGenProps();
|
||||
bean.setName("restore-insert");
|
||||
bean.setInstantCreated(created);
|
||||
bean.setInstantUpdated(updated);
|
||||
|
||||
try (Transaction txn = DB.beginTransaction()) {
|
||||
txn.setGeneratedPropertiesEnabled(false);
|
||||
DB.save(bean);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
bean = DB.find(EGenProps.class, bean.getId());
|
||||
assertThat(bean.getInstantCreated()).isEqualTo(created);
|
||||
assertThat(bean.getInstantUpdated()).isEqualTo(updated);
|
||||
DB.delete(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_insert_generatedPropertiesDisabled_nullValueStillFilled() {
|
||||
EGenProps bean = new EGenProps();
|
||||
bean.setName("restore-insert-null");
|
||||
// intentionally NOT setting instantCreated / instantUpdated
|
||||
|
||||
try (Transaction txn = DB.beginTransaction()) {
|
||||
txn.setGeneratedPropertiesEnabled(false);
|
||||
DB.save(bean);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
bean = DB.find(EGenProps.class, bean.getId());
|
||||
// null values must still be filled by the generator
|
||||
assertThat(bean.getInstantCreated()).isNotNull();
|
||||
assertThat(bean.getInstantUpdated()).isNotNull();
|
||||
DB.delete(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_update_generatedPropertiesDisabled_preservesBeanValues() {
|
||||
EGenProps bean = new EGenProps();
|
||||
bean.setName("restore-update");
|
||||
DB.save(bean);
|
||||
|
||||
Instant created = Instant.parse("2022-03-01T00:00:00Z");
|
||||
Instant updated = Instant.parse("2022-03-02T00:00:00Z");
|
||||
|
||||
bean = DB.find(EGenProps.class, bean.getId());
|
||||
bean.setInstantCreated(created);
|
||||
bean.setInstantUpdated(updated);
|
||||
|
||||
try (Transaction txn = DB.beginTransaction()) {
|
||||
txn.setGeneratedPropertiesEnabled(false);
|
||||
DB.save(bean);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
bean = DB.find(EGenProps.class, bean.getId());
|
||||
assertThat(bean.getInstantCreated()).isEqualTo(created);
|
||||
assertThat(bean.getInstantUpdated()).isEqualTo(updated);
|
||||
DB.delete(bean);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user