Experimental Feature - Freeze and detach beans (and attach)

- Frozen beans as ReadOnly + Unmodifiable collections
- Add Transaction.freezeAndDetach() returning FrozenBeans
- Add Transaction.attach( FrozenBeans )

- InterceptReadOnly now supports lazy loading
- Query.setReadOnly(true) now uses InterceptReadOnly [when it used to ALSO require Query.setDisableLazyLoading(true)}
- Add EntityBeanIntercept.errorOnLazyLoad (such that lazy loading on frozen bean is an error)

- BeanList, BeanSet, BeanMap are now freezable
- ModifyAwareList Set and Map are now freezable
This commit is contained in:
Rob Bygrave
2024-01-30 20:40:18 +13:00
parent f34e7b6003
commit bc302b19db
28 changed files with 819 additions and 596 deletions
@@ -16,4 +16,9 @@ public interface ModifyAwareType {
*/
void setMarkedDirty(boolean markedDirty);
/**
* Return a unmodifiable version of this type.
*/
Object freeze();
}
@@ -2,6 +2,7 @@ package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.bean.FrozenBeans;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DocStoreConfig;
@@ -54,6 +55,24 @@ public interface Transaction extends AutoCloseable {
*/
int SERIALIZABLE = java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* Experimental Feature - Freeze the beans in the persistence context and detach them.
*/
@Deprecated(since = "Experimental Feature")
default FrozenBeans freezeAndDetach() {
throw new UnsupportedOperationException();
}
/**
* Experimental Feature - Attach frozen beans to the persistence context so that they can be
* used with ORM queries (with the persistence context acting like a cache).
*/
@Deprecated(since = "Experimental Feature")
default void attach(FrozenBeans frozenBeans) {
throw new UnsupportedOperationException();
}
/**
* Register a TransactionCallback with this transaction.
*/
@@ -35,6 +35,11 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
ALL
}
/**
* Return a unmodifiable collection of the underlying entities.
*/
Object freeze();
/**
* Set the disableLazyLoad state.
*/
@@ -552,4 +552,16 @@ public interface EntityBeanIntercept extends Serializable {
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
String mutableNext(int propertyIndex);
/**
* Set that lazy loading if invoked throws a PersistenceException.
*/
void errorOnLazyLoad(boolean lazyLoadAsError);
/**
* Freeze the intercept so that it is readOnly.
* Lazy loading and mutation of the bean is not allowed.
*/
void freeze();
}
@@ -0,0 +1,9 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Holds entity beans that are frozen.
*/
public interface FrozenBeans extends Serializable {
}
@@ -0,0 +1,343 @@
package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceException;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Base EntityBeanIntercept that supports partial loaded state and lazy loading.
*/
abstract class InterceptBase implements EntityBeanIntercept {
/**
* Flag for loaded property.
*/
static final byte FLAG_LOADED_PROP = 1;
/**
* The actual entity bean that 'owns' this intercept.
*/
protected final EntityBean owner;
protected final byte[] flags;
protected boolean readOnly;
protected boolean disableLazyLoad;
protected boolean errorOnLazyLoad;
private final ReentrantLock lock = new ReentrantLock();
private String ebeanServerName;
private transient BeanLoader beanLoader;
private transient PersistenceContext persistenceContext;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
protected boolean lazyLoadFailure;
protected boolean fullyLoadedBean;
protected int lazyLoadProperty = -1;
protected Object ownerId;
/**
* Create with a given entity.
*/
InterceptBase(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
InterceptBase() {
this.owner = null;
this.flags = null;
}
public void freeze() {
this.errorOnLazyLoad = true;
this.readOnly = true;
this.beanLoader = null;
this.persistenceContext = null;
}
@Override
public final EntityBean owner() {
return owner;
}
@Override
public final Object ownerId() {
return ownerId;
}
@Override
public final void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
@Override
public final void setBeanLoader(BeanLoader beanLoader) {
this.beanLoader = beanLoader;
this.ebeanServerName = beanLoader.name();
}
@Override
public final void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
this.beanLoader = beanLoader;
this.persistenceContext = ctx;
this.ebeanServerName = beanLoader.name();
}
@Override
public final PersistenceContext persistenceContext() {
return persistenceContext;
}
@Override
public final void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
@Override
public final boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public final void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public final boolean isPartial() {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
return false;
}
@Override
public final boolean hasIdOnly(int idIndex) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
return true;
}
@Override
public final boolean isReadOnly() {
return readOnly;
}
@Override
public final void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public final void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
@Override
public final boolean isLazyLoadFailure() {
return lazyLoadFailure;
}
@Override
public final boolean isDisableLazyLoad() {
return disableLazyLoad;
}
@Override
public final void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
}
@Override
public final void errorOnLazyLoad(boolean errorOnLazyLoad) {
this.errorOnLazyLoad = errorOnLazyLoad;
}
@Override
public final void setEmbeddedLoaded(Object embeddedBean) {
if (embeddedBean instanceof EntityBean) {
((EntityBean) embeddedBean)._ebean_getIntercept().setLoaded();
}
}
@Override
public final int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public final String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public final int propertyLength() {
return flags.length;
}
@Override
public final void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
@Override
public final void setPropertyUnloaded(int propertyIndex) {
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
@Override
public final void initialisedMany(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public final void setLoadedProperty(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public final void setLoadedPropertyAll() {
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@Override
public final boolean isLoadedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
@Override
public final Set<String> loadedPropertyNames() {
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(property(i));
}
}
return props;
}
@Override
public final StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
@Override
public final boolean[] loaded() {
final boolean[] ret = new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
@Override
public final int lazyLoadPropertyIndex() {
return lazyLoadProperty;
}
@Override
public final String lazyLoadProperty() {
return property(lazyLoadProperty);
}
@Override
public final void loadBean(int loadProperty) {
lock.lock();
try {
if (errorOnLazyLoad) {
throw new PersistenceException("Lazy loading not allowed on this bean");
}
if (beanLoader == null) {
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException(ebeanServerName == null ? "No registered default server" : "Database [" + ebeanServerName + "] is not registered");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
}
} finally {
lock.unlock();
}
final Lock lock = beanLoader.lock();
try {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
loadBeanInternal(loadProperty, beanLoader);
} finally {
lock.unlock();
}
}
@Override
public final void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
if (lazyLoadProperty == -1) {
lazyLoadProperty = loadProperty;
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
}
}
@@ -10,55 +10,110 @@ import java.util.Set;
* EntityBeanIntercept optimised for read only use.
* <p>
* For the read only use this intercept doesn't need to hold any state that is normally
* required for updates such as per property changed, loaded, dirty state, original values
* required for updates such as per property changed, dirty state, original values
* bean state etc.
*/
public class InterceptReadOnly implements EntityBeanIntercept {
private final EntityBean owner;
public final class InterceptReadOnly extends InterceptBase {
/**
* Create with a given entity.
*/
public InterceptReadOnly(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
}
@Override
public String toString() {
return "InterceptReadOnly{" + owner + '}';
public void preGetId() {
// do nothing
}
@Override
public EntityBean owner() {
return owner;
public void preGetter(int propertyIndex) {
if (disableLazyLoad) {
return;
}
if (!isLoadedProperty(propertyIndex)) {
loadBean(propertyIndex);
}
}
private void preSetter(int propertyIndex) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setLoadedProperty(propertyIndex);
}
@Override
public PersistenceContext persistenceContext() {
return null;
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
preSetter(propertyIndex);
}
@Override
public void setPersistenceContext(PersistenceContext persistenceContext) {
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
preSetter(propertyIndex);
}
// ------------------------------------------------------------------------------------
// The following method have "Do Nothing" behaviour as not interested in mutation state
// ------------------------------------------------------------------------------------
@Override
public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
}
@Override
public Object ownerId() {
return null;
}
@Override
public void setOwnerId(Object ownerId) {
}
@Override
public Object embeddedOwner() {
return null;
@@ -84,31 +139,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
}
@Override
public void setBeanLoader(BeanLoader beanLoader) {
}
@Override
public boolean isFullyLoadedBean() {
return false;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
}
@Override
public boolean isPartial() {
return false;
}
@Override
public boolean isDirty() {
return false;
@@ -134,11 +164,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return false;
}
@Override
public boolean hasIdOnly(int idIndex) {
return false;
}
@Override
public boolean isReference() {
return false;
@@ -159,16 +184,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return false;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean readOnly) {
}
@Override
public void setForceUpdate(boolean forceUpdate) {
@@ -199,31 +214,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void setLazyLoadFailure(Object ownerId) {
}
@Override
public boolean isLazyLoadFailure() {
return false;
}
@Override
public boolean isDisableLazyLoad() {
return false;
}
@Override
public void setDisableLazyLoad(boolean disableLazyLoad) {
}
@Override
public void setEmbeddedLoaded(Object embeddedBean) {
}
@Override
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
return false;
@@ -234,45 +224,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return null;
}
@Override
public int findProperty(String propertyName) {
return 0;
}
@Override
public String property(int propertyIndex) {
return null;
}
@Override
public int propertyLength() {
return 0;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
}
@Override
public void setLoadedProperty(int propertyIndex) {
}
@Override
public void setLoadedPropertyAll() {
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return false;
}
@Override
public boolean isChangedProperty(int propertyIndex) {
@@ -319,11 +270,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public Set<String> loadedPropertyNames() {
return Collections.emptySet();
}
@Override
public boolean[] dirtyProperties() {
return new boolean[0];
@@ -369,61 +315,11 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public StringBuilder loadedPropertyKey() {
return null;
}
@Override
public boolean[] loaded() {
return new boolean[0];
}
@Override
public int lazyLoadPropertyIndex() {
return 0;
}
@Override
public String lazyLoadProperty() {
return null;
}
@Override
public void loadBean(int loadProperty) {
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
}
@Override
public void initialisedMany(int propertyIndex) {
}
@Override
public void preGetterCallback(int propertyIndex) {
}
@Override
public void preGetId() {
}
@Override
public void preGetter(int propertyIndex) {
}
@Override
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
}
@Override
public void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
@@ -434,61 +330,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
}
@Override
public void setOldValue(int propertyIndex, Object oldValue) {
@@ -1,11 +1,7 @@
package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.ValuePair;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -13,8 +9,6 @@ import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This is the object added to every entity bean using byte code enhancement.
@@ -22,7 +16,7 @@ import java.util.concurrent.locks.ReentrantLock;
* This provides the mechanisms to support deferred fetching of reference beans
* and oldValues generation for concurrency checking.
*/
public final class InterceptReadWrite implements EntityBeanIntercept {
public final class InterceptReadWrite extends InterceptBase {
private static final long serialVersionUID = -3664031775464862649L;
@@ -30,10 +24,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private static final int STATE_REFERENCE = 1;
private static final int STATE_LOADED = 2;
/**
* Used when a bean is partially loaded.
*/
private static final byte FLAG_LOADED_PROP = 1;
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
@@ -47,43 +37,21 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
*/
private static final byte FLAG_MUTABLE_HASH_SET = 16;
private final ReentrantLock lock = new ReentrantLock();
private transient NodeUsageCollector nodeUsageCollector;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
private transient PreGetterCallback preGetterCallback;
private String ebeanServerName;
private boolean deletedFromCollection;
/**
* The actual entity bean that 'owns' this intercept.
*/
private final EntityBean owner;
private EntityBean embeddedOwner;
private int embeddedOwnerIndex;
/**
* One of NEW, REF, UPD.
* One of NEW, REF, LOADED.
*/
private int state;
private boolean deletedFromCollection;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading.
*/
private boolean disableLazyLoad;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
private boolean lazyLoadFailure;
private boolean fullyLoadedBean;
private boolean loadedFromCache;
private final byte[] flags;
private Object[] origValues;
private Exception[] loadErrors;
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
/**
@@ -101,49 +69,14 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* Create with a given entity.
*/
public InterceptReadWrite(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.flags = new byte[owner._ebean_getPropertyNames().length];
super(ownerBean);
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
public InterceptReadWrite() {
this.owner = null;
this.flags = null;
}
@Override
public String toString() {
return "InterceptReadWrite@" + hashCode() + "{state=" + state +
(dirty ? " dirty;" : "") +
(forceUpdate ? " forceUpdate;" : "") +
(readOnly ? " readOnly;" : "") +
(disableLazyLoad ? " disableLazyLoad;" : "") +
(lazyLoadFailure ? " lazyLoadFailure;" : "") +
(fullyLoadedBean ? " fullyLoadedBean;" : "") +
(loadedFromCache ? " loadedFromCache;" : "") +
", pc=" + System.identityHashCode(persistenceContext) +
", flags=" + Arrays.toString(flags) +
(lazyLoadProperty > -1 ? (", lazyLoadProperty=" + lazyLoadProperty) : "") +
", loader=" + beanLoader +
(ownerId != null ? (", ownerId=" + ownerId) : "") +
'}';
}
@Override
public EntityBean owner() {
return owner;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
}
@Override
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
super();
}
@Override
@@ -151,16 +84,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.nodeUsageCollector = usageCollector;
}
@Override
public Object ownerId() {
return ownerId;
}
@Override
public void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
@Override
public Object embeddedOwner() {
return embeddedOwner;
@@ -187,39 +110,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.embeddedOwnerIndex = embeddedOwnerIndex;
}
@Override
public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
this.beanLoader = beanLoader;
this.persistenceContext = ctx;
this.ebeanServerName = beanLoader.name();
}
@Override
public void setBeanLoader(BeanLoader beanLoader) {
this.beanLoader = beanLoader;
this.ebeanServerName = beanLoader.name();
}
@Override
public boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public boolean isPartial() {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
return false;
}
@Override
public boolean isDirty() {
if (dirty) {
@@ -257,18 +147,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return isNew() || isDirty();
}
@Override
public boolean hasIdOnly(int idIndex) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
return true;
}
@Override
public boolean isReference() {
return state == STATE_REFERENCE;
@@ -298,16 +176,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return loadedFromCache;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public void setForceUpdate(boolean forceUpdate) {
this.forceUpdate = forceUpdate;
@@ -356,34 +224,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.lazyLoadProperty = -1;
}
@Override
public void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
@Override
public boolean isLazyLoadFailure() {
return lazyLoadFailure;
}
@Override
public boolean isDisableLazyLoad() {
return disableLazyLoad;
}
@Override
public void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
}
@Override
public void setEmbeddedLoaded(Object embeddedBean) {
if (embeddedBean instanceof EntityBean) {
((EntityBean) embeddedBean)._ebean_getIntercept().setLoaded();
}
}
@Override
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
if (embeddedBean == null) {
@@ -411,65 +251,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return origValues[propertyIndex];
}
@Override
public int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public int propertyLength() {
return flags.length;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
@Override
public void setLoadedProperty(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public void setLoadedPropertyAll() {
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
@Override
public boolean isChangedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
@@ -530,20 +311,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
setDirty(true);
}
@Override
public Set<String> loadedPropertyNames() {
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(property(i));
}
}
return props;
}
@Override
public boolean[] dirtyProperties() {
final int len = propertyLength();
@@ -668,86 +435,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
@Override
public boolean[] loaded() {
final boolean[] ret = new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
@Override
public int lazyLoadPropertyIndex() {
return lazyLoadProperty;
}
@Override
public String lazyLoadProperty() {
return property(lazyLoadProperty);
}
@Override
public void loadBean(int loadProperty) {
lock.lock();
try {
if (beanLoader == null) {
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException(ebeanServerName == null ? "No registered default server" : "Database [" + ebeanServerName + "] is not registered");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
}
} finally {
lock.unlock();
}
final Lock lock = beanLoader.lock();
try {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
loadBeanInternal(loadProperty, beanLoader);
} finally {
lock.unlock();
}
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
if (lazyLoadProperty == -1) {
lazyLoadProperty = loadProperty;
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
}
/**
* Helper method to check if two objects are equal.
*/
@@ -808,11 +495,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public void initialisedMany(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public void preGetterCallback(int propertyIndex) {
PreGetterCallback preGetterCallback = this.preGetterCallback;
@@ -1076,4 +758,16 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
final MutableValueNext next = mutableNext[propertyIndex];
return next != null ? next.content() : null;
}
@Override
public void freeze() {
super.freeze();
// not allowed to be used for updates
this.dirty = false;
this.forceUpdate = false;
this.deletedFromCollection = false;
this.origValues = null;
this.nodeUsageCollector = null;
this.embeddedOwner = null;
}
}
@@ -44,6 +44,11 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
builder.addCollection(list);
}
@Override
public Object freeze() {
return list == null ? Collections.emptyList() : Collections.unmodifiableList(list);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
@@ -37,6 +37,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@Override
public Object freeze() {
return map == null ? Collections.emptyMap() : Collections.unmodifiableMap(map);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
@@ -3,10 +3,7 @@ package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.*;
/**
* Set capable of lazy loading and modification aware.
@@ -43,6 +40,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
builder.addCollection(set);
}
@Override
public Object freeze() {
return set == null ? Collections.emptySet() : Collections.unmodifiableSet(set);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
@@ -1,8 +1,11 @@
package io.ebeaninternal.api;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import java.util.List;
import java.util.Map;
/**
* SPI extension to PersistenceContext.
@@ -14,4 +17,14 @@ public interface SpiPersistenceContext extends PersistenceContext {
*/
List<Object> dirtyBeans(SpiBeanTypeManager manager);
/**
* Return all the entities from this persistence context.
*/
Map<Class<?>, List<EntityBean>> detach();
/**
* Attach all the cached entities into this persistence context.
*/
void attach(FrozenBeans cachedBeans);
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.FrozenBeans;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -223,6 +224,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.register(callback);
}
@Override
public FrozenBeans freezeAndDetach() {
return transaction.freezeAndDetach();
}
@Override
public void attach(FrozenBeans frozenBeans) {
transaction.attach(frozenBeans);
}
@Override
public boolean isReadOnly() {
return transaction.isReadOnly();
@@ -23,4 +23,8 @@ public final class ModifyAwareFlag implements ModifyAwareType, Serializable {
this.markedDirty = markedDirty;
}
@Override
public Object freeze() {
throw new UnsupportedOperationException();
}
}
@@ -3,12 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.*;
/**
* Modify aware wrapper of a list.
@@ -52,6 +47,11 @@ public final class ModifyAwareList<E> implements List<E>, ModifyAwareType, Seria
return list.hashCode();
}
@Override
public List<E> freeze() {
return Collections.unmodifiableList(list);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,11 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Map that is wraps an underlying map for the purpose of detecting changes.
@@ -51,6 +47,11 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
return map.hashCode();
}
@Override
public Map<K, V> freeze() {
return Collections.unmodifiableMap(map);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,10 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Wraps a Set for the purposes of detecting modifications.
@@ -33,6 +30,11 @@ public final class ModifyAwareSet<E> implements Set<E>, ModifyAwareType, Seriali
this.set = underlying;
}
@Override
public Set<E> freeze() {
return Collections.unmodifiableSet(set);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -687,6 +687,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
public void freeze(EntityBean entityBean) {
for (BeanProperty beanProperty : propertiesMutable) {
beanProperty.freeze(entityBean);
}
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.freeze(entityBean);
}
entityBean._ebean_getIntercept().freeze();
}
public void metricPersistBatch(PersistRequest.Type type, long startNanos, int size) {
iudMetrics.addBatch(type, startNanos, size);
}
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ModifyAwareType;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.MutableValueInfo;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -1498,4 +1496,15 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
desc.registerColumn(dbColumn, path);
}
}
/**
* Freeze mutable types (like DbArray).
*/
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof ModifyAwareType) {
ModifyAwareType bc = (ModifyAwareType) value;
setValue(entityBean, bc.freeze());
}
}
}
@@ -1133,4 +1133,13 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return false;
}
}
@Override
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof BeanCollection) {
BeanCollection<?> bc = (BeanCollection<?>) value;
setValue(entityBean, bc.freeze());
}
}
}
@@ -31,7 +31,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
private final boolean readOnlyNoIntercept;
private final boolean readOnly;
private final InheritInfo inheritInfo;
final String prefix;
private final Map<String, String> pathMap;
@@ -54,7 +54,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;
this.readOnlyNoIntercept = disableLazyLoad && node.readOnly;
this.readOnly = node.readOnly;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
@@ -162,7 +162,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
void initBeanType() throws SQLException {
localDesc = desc;
localBean = desc.createEntityBean2(readOnlyNoIntercept);
localBean = desc.createEntityBean2(readOnly);
localIdBinder = idBinder;
}
@@ -302,7 +302,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != loadingChildProperty) {
if (readOnlyNoIntercept) {
if (disableLazyLoad && readOnly) {
many.createEmptyReference(localBean);
} else {
// create a proxy for the many (deferred fetching)
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
final class DFrozenBeans implements FrozenBeans {
private static final long serialVersionUID = 1L;
private final Map<Class<?>, Map<Object, EntityBean>> beans;
DFrozenBeans(Map<Class<?>, Map<Object, EntityBean>> beans) {
this.beans = Collections.unmodifiableMap(beans);
}
Set<Map.Entry<Class<?>, Map<Object, EntityBean>>> entries() {
return beans.entrySet();
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiBeanType;
import io.ebeaninternal.api.SpiBeanTypeManager;
@@ -44,6 +45,33 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
public DefaultPersistenceContext() {
}
public void attach(FrozenBeans other) {
lock.lock();
try {
DFrozenBeans otherContext = (DFrozenBeans) other;
for (Map.Entry<Class<?>, Map<Object, EntityBean>> entry : otherContext.entries()) {
classContext(entry.getKey()).attach(entry.getValue());
}
} finally {
lock.unlock();
}
}
@Override
public Map<Class<?>, List<EntityBean>> detach() {
lock.lock();
try {
expungeStaleEntries();
Map<Class<?>, List<EntityBean>> result = new LinkedHashMap<>();
for (Map.Entry<Class<?>, ClassContext> entry : typeCache.entrySet()) {
result.put(entry.getKey(), entry.getValue().beans());
}
return result;
} finally {
lock.unlock();
}
}
@Override
public void beginIterate() {
lock.lock();
@@ -247,6 +275,23 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
return "size:" + map.size() + " (" + weakCount + " weak)";
}
List<EntityBean> beans() {
List<EntityBean> beans = new ArrayList<>(map.size());
for (Object value : map.values()) {
if (value instanceof BeanRef) {
value = ((BeanRef) value).get();
}
if (value != null) {
beans.add((EntityBean) value);
}
}
return beans;
}
void attach(Map<Object, EntityBean> value) {
map.putAll(value);
}
private Object get(Object id) {
Object ret = map.get(id);
if (ret instanceof BeanRef) {
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.FrozenBeans;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
@@ -130,6 +131,16 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
}
@Override
public FrozenBeans freezeAndDetach() {
return manager.freezeAndDetach(persistenceContext);
}
@Override
public void attach(FrozenBeans frozenBeans) {
persistenceContext.attach(frozenBeans);
}
@Override
public final void setLabel(String label) {
this.label = label;
@@ -6,6 +6,8 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
@@ -22,6 +24,7 @@ import io.ebeaninternal.api.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.profile.TimedProfileLocation;
import io.ebeaninternal.server.profile.TimedProfileLocationRegistry;
@@ -33,7 +36,9 @@ import jakarta.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -637,4 +642,21 @@ public class TransactionManager implements SpiTransactionManager {
}
}
public FrozenBeans freezeAndDetach(SpiPersistenceContext persistenceContext) {
Map<Class<?>, Map<Object, EntityBean>> allBeans = new HashMap<>();
for (Map.Entry<Class<?>, List<EntityBean>> entry : persistenceContext.detach().entrySet()) {
allBeans.put(entry.getKey(), freezeBeanMap(entry));
}
return new DFrozenBeans(allBeans);
}
private Map<Object, EntityBean> freezeBeanMap(Map.Entry<Class<?>, List<EntityBean>> entry) {
BeanDescriptor<?> descriptor = beanDescriptorManager.descriptor(entry.getKey());
Map<Object, EntityBean> beanMap = new HashMap<>();
for (EntityBean entityBean : entry.getValue()) {
descriptor.freeze(entityBean);
beanMap.put(descriptor.getId(entityBean), entityBean);
}
return beanMap;
}
}
@@ -1,6 +0,0 @@
package io.ebeaninternal.server.type;
public interface ModifyAwareType {
boolean isDirty();
}
@@ -35,6 +35,11 @@ public class PlainBeanDirtyAware implements ModifyAwareType {
return markedDirty;
}
@Override
public Object freeze() {
return this;
}
@Override
public void setMarkedDirty(boolean markedDirty) {
this.markedDirty = markedDirty;
@@ -1,5 +1,7 @@
package org.tests.query;
import io.ebean.Transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
@@ -7,23 +9,145 @@ import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.basic.*;
import java.io.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class TestQueryOrderById extends BaseTestCase {
private Set<Integer> cachedCustomerIds;
@Test
void cachedBeanContext_attach() {
ResetBasicData.reset();
FrozenBeans cachedBeanContext = testSerialiseCachedBeans(buildCachedBeans());
testSerialiseCustomer(cachedCustomers.get(0));
try (Transaction txn = DB.beginTransaction()) {
txn.attach(cachedBeanContext);
List<Order> orders = DB.find(Order.class)
.findList();
for (Order order : orders) {
Customer customer = order.getCustomer();
if (cachedCustomerIds.contains(customer.getId())) {
List<Order> orders1 = customer.getOrders();
assertThat(orders1).isEmpty();
assertThat(orders1).isSameAs(Collections.EMPTY_LIST);
// customer.setContacts(new ArrayList<>());
// customer.setName("modified");
Address billingAddress = customer.getBillingAddress();
if (billingAddress != null) {
String line1 = billingAddress.getLine1();
Country country = billingAddress.getCountry();
}
Address shippingAddress = customer.getShippingAddress();
// if (shippingAddress != null) {
// // shippingAddress.getCountry();
// }
}
}
}
}
private static FrozenBeans testSerialiseCachedBeans(FrozenBeans source) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(source);
oos.close();
byte[] byteArray = os.toByteArray();
var is = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new ObjectInputStream(is);
FrozenBeans result = (FrozenBeans)ois.readObject();
assertThat(result).isNotNull();
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void testSerialiseCustomer(Customer source) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(source);
oos.close();
byte[] byteArray = os.toByteArray();
var is = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new ObjectInputStream(is);
Customer customer = (Customer)ois.readObject();
assertThat(customer.getId()).isEqualTo(source.getId());
assertThat(customer.getName()).isEqualTo(source.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<Customer> cachedCustomers;
private FrozenBeans buildCachedBeans() {
try (Transaction txn = DB.beginTransaction()) {
List<Customer> list = DB.find(Customer.class)
.setReadOnly(true)
//.setDisableLazyLoading(true)
.orderBy("id")
.setMaxRows(3)
.findList();
// perform some lazy loading if we desire
for (Customer customer : list) {
Address billingAddress = customer.getBillingAddress();
if (billingAddress != null) {
Country country = billingAddress.getCountry();
if (country != null) {
country.getName();
}
}
}
// can selectively use DisableLazyLoad which is "lazy loading is NO-operation"
//for (Customer customer : list) {
// DB.beanState(customer).setDisableLazyLoad(true);
//}
cachedCustomerIds = list.stream()
.map(BasicDomain::getId)
.collect(Collectors.toSet());
cachedCustomers = list;
return txn.freezeAndDetach();
}
}
@Test
public void orderById_default_expectNotOrderById() {
ResetBasicData.reset();
Query<Customer> query = DB.find(Customer.class)
.select("id,name")
.order("id")
.orderBy("id")
.setFirstRow(1)
.setMaxRows(5);