mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
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:
@@ -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();
|
||||
}
|
||||
}
|
||||
+45
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user