Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions ebean-api/src/main/java/io/ebean/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,27 @@ static Transaction current() {
*/
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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,16 @@ public void setUpdateAllLoadedProperties(boolean updateAllLoaded) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,13 +262,13 @@ private void onUpdateGeneratedProperties() {
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());
Expand All @@ -280,17 +280,22 @@ private void onUpdateGeneratedProperties() {

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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,15 @@ public Boolean isUpdateAllLoadedProperties() {
return null;
}

@Override
public void setGeneratedPropertiesEnabled(boolean enable) {
}

@Override
public boolean isGeneratedPropertiesEnabled() {
return true;
}

@Override
public void setBatchMode(boolean batchMode) {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,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;
Expand Down Expand Up @@ -439,6 +440,16 @@ public final Boolean isUpdateAllLoadedProperties() {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,15 @@ public void setPersistCascade(boolean persistCascade) {
public void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties) {
}

@Override
public void setGeneratedPropertiesEnabled(boolean enable) {
}

@Override
public boolean isGeneratedPropertiesEnabled() {
return true;
}

@Override
public void setSkipCache(boolean skipCache) {
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -47,4 +50,70 @@ public void test_insert() {

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);
}
}
Loading