Compare commits

...
Author SHA1 Message Date
Rob Bygrave 93ae5f2b7c ToMany collections that are not loaded on freeze are marked unloaded
They are marked as unloaded which means accessing the collection will
throw a PersistenceException.

e.g. customer.getOrders() will throw PersistenceException rather than
return an empty list.
2024-01-30 22:32:49 +13:00
Rob Bygrave bc302b19db 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
2024-01-30 20:40:18 +13:00
28 changed files with 831 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,12 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
builder.addCollection(list);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return list == null ? null : Collections.unmodifiableList(list);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
@@ -37,6 +37,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return map == null ? null : 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,12 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
builder.addCollection(set);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return set == null ? null : 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,17 @@ 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());
if (bc.isReference()) {
// make it an error to access the collection (no lazy loading allowed)
entityBean._ebean_getIntercept().setPropertyUnloaded(propertyIndex);
}
}
}
}
@@ -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,150 @@ 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())) {
// Illegal to access customer.orders as that was not loaded and no lazy loading is allowed
// List<Order> orders1 = customer.getOrders();
// assertThat(orders1).isEmpty();
// assertThat(orders1).isSameAs(Collections.EMPTY_LIST);
List<Contact> contacts = customer.getContacts();
assertThat(contacts).isNotNull();
// 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) {
customer.getContacts().size();
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);