Compare commits

...
Author SHA1 Message Date
Rob BygraveandGitHub 4bcfa80df2 Merge pull request #3594 from ebean-orm/feature/improved-readOnly-immutable-DisabledLazyLoad
Modify disableLazyLoad to throw LazyInitialisationException instead o…
2025-04-29 23:32:46 +12:00
Rob Bygrave c583879d57 Modify disableLazyLoad to throw LazyInitialisationException instead of returning null
Modify such that accessing an unloaded property throws a LazyInitialisationException. This includes ToMany collections that where previously initialised as empty collections (that didn't lazy load).

This change brings the disableLazyLoad behaviour in line with the unmodifiable behaviour.

It does mean that the use case of bulk mapping from entities to DTOs via something like MapStruct will no longer work nicely (mapping nulls and empty lists versus LazyInitialisationException).
2025-03-18 23:46:42 +13:00
Rob Bygrave 6d791c7b50 Add BeanAccessException extends UnsupportedOperationException
And:

LazyInitialisationException extends BeanAccessException ...
UnmodifiableEntityException extends BeanAccessException ...
2025-03-18 22:54:34 +13:00
Rob BygraveandGitHub 1afe42f4c6 Merge branch 'master' into feature/improved-readOnly-immutable 2025-03-18 22:43:00 +13:00
Rob BygraveandGitHub 4e7f3c13a0 Merge pull request #3592 from ebean-orm/feature/improved-readOnly-immutable-part2
Remove readOnly, migrate to unmodifiable (or use normal mutable)
2025-03-18 22:41:12 +13:00
Rob Bygrave d7366dec0f Tidy readOnly removal 2025-03-18 20:59:04 +13:00
Rob Bygrave ceee1d7489 Remove readOnly from Query 2025-03-18 20:49:26 +13:00
Rob Bygrave 1b23319fa4 Remove readOnly from EntityBeanIntercept and BeanCollection 2025-03-18 20:32:22 +13:00
Rob Bygrave 6f726973ea Remove most internal use of readOnly 2025-03-18 19:53:13 +13:00
Rob Bygrave 82284b19d5 Bean cache sharable instances as unmodifiable + bean cache queries can also be unmodifiable
Entities without *any* relationships can support "sharable instances" with bean caching. This change restores that and uses unmodifiable instances (rather than the old ReadOnly).

Currently, for a query to get shared instances it now needs to explicitly use setUnmodifiable(true) where as before it defaulted to shared instances so that is a behaviour change.

Additionally, bean cache queries were not honouring setUnmodifiable(true) and with these changes they now do.
2025-03-16 23:19:54 +13:00
Rob Bygrave 798d01abd1 LazyInitialisationException, plus javadoc 2025-03-16 21:12:25 +13:00
Rob Bygrave 5136a5a708 Support unmodifiable Embedded and EmbeddedId 2025-03-13 22:52:45 +13:00
Rob Bygrave fa25d7cc8a Using Query Cache now implies unmodifiable
- Query Cache now holds unmodifiable collections
- Can no longer have readOnly=false with queryCache=true
- Reference beans now also honor unmodifiable
- Effectively no longer does bean cache lookup for reference beans (which it defaulted to when cacheSharableBeans true, e.g. Country entity bean)
2025-03-11 23:50:34 +13:00
Rob Bygrave 8ff5f7f72a Recursive freeze on entity beans
Noting that the freeze needs to occur after secondary queries have executed
2025-02-26 00:00:07 +13:00
Rob Bygrave 71944bb8fa Rename flags -> loaded, add message with property name to UnmodifiableEntityException 2025-02-23 22:25:46 +13:00
Rob Bygrave 24888fc01e Change InterceptReadWrite to use UnmodifiableEntityException
Add unmodified to DefaultOrmQuery query plan description
Fix incorrect merge conflict
2025-02-21 22:24:48 +13:00
Rob BygraveandGitHub 645d5bb613 Merge branch 'master' into feature/improved-readOnly-immutable 2025-02-21 22:08:38 +13:00
Rob Bygrave 4a6f1a39e0 Add UnloadedPropertyException and UnmodifiableEntityException, change existing use of IllegalStateException
- Adds new exceptions UnloadedPropertyException and UnmodifiableEntityException
- Change InterceptReadOnly to use these exception instead of IllegalStateException
- Change collections BeanSet, BeanList, BeanMap from using IllegalStateException to UnsupportedOperationException [to bring these in line with JDK unmodifiable collections]
2025-02-21 22:05:49 +13:00
Rob Bygrave 1fef4f7c11 Add explicit query.setUnmodifiable(true) [for readOnly=true + disableLazyLoad=true]
Although this is ok, makes me think that another option is just to have query.setReadOnly(true)
to mean ... readOnly + disableLazyLoad + error reading unloaded property or collection. As in,
readOnly true without these extra things does not that good [as in the existing readOnly does
not seem very good/safe/useful to use].
2024-11-20 23:01:59 +13:00
Rob Bygrave e6f92eaceb ReadOnly Immutable query WIP 2024-10-24 16:02:50 +13:00
94 changed files with 1094 additions and 1117 deletions
@@ -0,0 +1,25 @@
package io.ebean;
/**
* Unsupported access of a property on an entity bean.
* <p>
* Attempted a lazy load operation on a bean that has disabled lazy loading
* or attempt to mutate an unmodifiable bean.
*/
public class BeanAccessException extends UnsupportedOperationException {
private static final long serialVersionUID = 1;
/**
* Create with no message.
*/
public BeanAccessException() {
super();
}
/**
* Create with message.
*/
public BeanAccessException(String message) {
super(message);
}
}
@@ -89,12 +89,7 @@ public interface BeanState {
* <p>
* If a setter is called on a readOnly bean it will throw an exception.
*/
boolean isReadOnly();
/**
* Set the readOnly status for the bean.
*/
void setReadOnly(boolean readOnly);
boolean isUnmodifiable();
/**
* Advanced - Used to programmatically build a partially or fully loaded
@@ -0,0 +1,19 @@
package io.ebean;
/**
* Thrown when trying to access a property that isn't loaded on an entity
* that is unmodifiable or has disabled lazy loading.
* <p>
* On a normal mutable entity accessing the property would invoke lazy loading. On
* a unmodifiable entity with lazy loading disabled, accessing an unloaded property
* throws this LazyInitialisationException instead.
*/
public class LazyInitialisationException extends BeanAccessException {
/**
* Create specifying the property that was being accessed.
*/
public LazyInitialisationException(String message) {
super(message);
}
}
@@ -16,4 +16,7 @@ public interface ModifyAwareType {
*/
void setMarkedDirty(boolean markedDirty);
default Object freeze() {
return this; // throw new UnsupportedOperationException();
}
}
@@ -388,9 +388,13 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
SELF setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
* When set to true when you want the returned beans to be unmodifiable read only.
* <p>
* This means that the returning graph can't be mutated via setters, all the collections
* are unmodifiable collections, lazy loading is disabled and that the query uses
* {@link PersistenceContextScope#QUERY}.
*/
SELF setReadOnly(boolean readOnly);
SELF setUnmodifiable(boolean unmodifiable);
/**
* Set a timeout on this query.
@@ -0,0 +1,22 @@
package io.ebean;
/**
* Attempted to modify a read only entity.
*/
public class UnmodifiableEntityException extends BeanAccessException {
private static final long serialVersionUID = 1;
/**
* Create with no message.
*/
public UnmodifiableEntityException() {
super();
}
/**
* Create with message.
*/
public UnmodifiableEntityException(String message) {
super(message);
}
}
@@ -119,18 +119,6 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
*/
void setLoader(BeanCollectionLoader beanLoader);
/**
* Set to true if you want the BeanCollection to be treated as read only. This
* means no elements can be added or removed etc.
*/
void setReadOnly(boolean readOnly);
/**
* Return true if the collection should be treated as readOnly and no elements
* can be added or removed etc.
*/
boolean isReadOnly();
/**
* Add the bean to the collection. This is disallowed for BeanMap.
*/
@@ -237,7 +225,7 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
* Freeze the collection returning an unmodifiable version.
*/
BeanCollection<E> shallowCopy();
Object freeze();
}
@@ -148,18 +148,6 @@ public interface EntityBeanIntercept extends Serializable {
*/
boolean isLoadedFromCache();
/**
* Return true if the bean should be treated as readOnly. If a setter method
* is called when it is readOnly an Exception is thrown.
*/
boolean isReadOnly();
/**
* Set the readOnly status. If readOnly then calls to setter methods through
* an exception.
*/
void setReadOnly(boolean readOnly);
/**
* Set the bean to be updated when persisted (for merge).
*/
@@ -393,11 +381,6 @@ public interface EntityBeanIntercept extends Serializable {
*/
void loadBean(int loadProperty);
/**
* Invoke the lazy loading. This method is synchronised externally.
*/
void loadBeanInternal(int loadProperty, BeanLoader loader);
/**
* Called when a BeanCollection is initialised automatically.
*/
@@ -552,4 +535,9 @@ public interface EntityBeanIntercept extends Serializable {
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
String mutableNext(int propertyIndex);
/**
* Return true if this entity bean should be frozen. Used to handle recursive freezing.
*/
boolean freeze();
}
@@ -0,0 +1,70 @@
package io.ebean.bean;
/**
* Common base features for EntityBeanIntercept.
*/
abstract class InterceptBase implements EntityBeanIntercept {
final EntityBean owner;
boolean fullyLoadedBean;
/**
* Create with a given entity.
*/
InterceptBase(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
InterceptBase() {
this.owner = null;
}
@Override
public final EntityBean owner() {
return owner;
}
@Override
public final boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public final void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public final String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@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 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;
}
}
@@ -1,37 +1,44 @@
package io.ebean.bean;
import io.ebean.LazyInitialisationException;
import io.ebean.UnmodifiableEntityException;
import io.ebean.ValuePair;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* 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 {
public final class InterceptReadOnly extends InterceptBase {
private final EntityBean owner;
private final boolean[] loaded;
private boolean frozen;
/**
* Create with a given entity.
*/
public InterceptReadOnly(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
this.loaded = new boolean[owner._ebean_getPropertyNames().length];
}
@Override
public boolean freeze() {
if (frozen) {
return false;
} else {
frozen = true;
return true;
}
}
@Override
public String toString() {
return "InterceptReadOnly{" + owner + '}';
}
@Override
public EntityBean owner() {
return owner;
return "InterceptReadOnly{frozen:" + frozen + " loaded:" + loadedPropertyNames() + '}';
}
@Override
@@ -94,18 +101,13 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public boolean isFullyLoadedBean() {
return false;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
}
@Override
public boolean isPartial() {
for (boolean flag : loaded) {
if (!flag) {
return true;
}
}
return false;
}
@@ -136,7 +138,14 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public boolean hasIdOnly(int idIndex) {
return false;
for (int i = 0; i < loaded.length; i++) {
if (i == idIndex) {
if (!loaded[i]) return false;
} else if (loaded[i]) {
return false;
}
}
return true;
}
@Override
@@ -159,16 +168,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) {
@@ -234,44 +233,38 @@ 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;
return loaded.length;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property not found - " + propertyName);
}
this.loaded[position] = loaded;
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
loaded[propertyIndex] = false;
}
@Override
public void setLoadedProperty(int propertyIndex) {
loaded[propertyIndex] = true;
}
@Override
public void setLoadedPropertyAll() {
Arrays.fill(loaded, true);
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return false;
return loaded[propertyIndex];
}
@Override
@@ -321,7 +314,16 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public Set<String> loadedPropertyNames() {
return Collections.emptySet();
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < loaded.length; i++) {
if (loaded[i]) {
props.add(property(i));
}
}
return props;
}
@Override
@@ -369,14 +371,11 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public StringBuilder loadedPropertyKey() {
return null;
}
@Override
public boolean[] loaded() {
return new boolean[0];
final boolean[] ret = new boolean[loaded.length];
System.arraycopy(loaded, 0, ret, 0, ret.length);
return ret;
}
@Override
@@ -394,14 +393,9 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
}
@Override
public void initialisedMany(int propertyIndex) {
loaded[propertyIndex] = true;
}
@Override
@@ -416,12 +410,14 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void preGetter(int propertyIndex) {
if (!loaded[propertyIndex]) {
throw new LazyInitialisationException("Property not loaded: " + property(propertyIndex));
}
}
@Override
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
@@ -436,57 +432,57 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
@@ -2,6 +2,7 @@ package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.LazyInitialisationException;
import io.ebean.ValuePair;
import jakarta.persistence.EntityNotFoundException;
@@ -22,7 +23,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;
@@ -56,18 +57,13 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
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, REFERENCE, LOADED.
*/
private int state;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading.
@@ -77,7 +73,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* 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;
@@ -101,7 +96,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* Create with a given entity.
*/
public InterceptReadWrite(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
@@ -109,16 +104,20 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
public InterceptReadWrite() {
this.owner = null;
super();
this.flags = null;
}
@Override
public boolean freeze() {
throw new UnsupportedOperationException("never expected");
}
@Override
public String toString() {
return "InterceptReadWrite@" + hashCode() + "{state=" + state +
(dirty ? " dirty;" : "") +
(forceUpdate ? " forceUpdate;" : "") +
(readOnly ? " readOnly;" : "") +
(disableLazyLoad ? " disableLazyLoad;" : "") +
(lazyLoadFailure ? " lazyLoadFailure;" : "") +
(fullyLoadedBean ? " fullyLoadedBean;" : "") +
@@ -131,11 +130,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
'}';
}
@Override
public EntityBean owner() {
return owner;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
@@ -200,16 +194,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
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) {
@@ -298,16 +282,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;
@@ -411,25 +385,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;
@@ -668,18 +623,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];
@@ -703,12 +646,15 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
public void loadBean(int loadProperty) {
lock.lock();
try {
if (disableLazyLoad) {
throw new LazyInitialisationException("Property not loaded: " + property(loadProperty));
}
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
// For stand-alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
@@ -726,8 +672,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
private void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
@@ -829,7 +774,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
@Override
public void preGetter(int propertyIndex) {
preGetterCallback(propertyIndex);
if (state == STATE_NEW || disableLazyLoad) {
if (state == STATE_NEW) {
return;
}
if (!isLoadedProperty(propertyIndex)) {
@@ -845,18 +790,12 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangeLoaded(propertyIndex);
}
}
@Override
public void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangedProperty(propertyIndex);
if (setDirtyState) {
setOriginalValue(propertyIndex, origValue);
@@ -17,7 +17,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
private static final long serialVersionUID = 3365725236140187588L;
protected final ReentrantLock lock = new ReentrantLock();
protected boolean readOnly;
protected boolean disableLazyLoad;
/**
* The Database this is associated with. (used for lazy fetch).
@@ -55,7 +54,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.name();
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
}
@Override
@@ -103,22 +101,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.name();
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
void checkReadOnly() {
if (readOnly) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
// ---------------------------------------------------------
// Support for modify additions deletions etc - ManyToMany
// ---------------------------------------------------------
@@ -212,14 +194,4 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
return modifyHolder != null && modifyHolder.wasTouched();
}
/**
* Copies all relevant properties for a clone. See {@link #shallowCopy()}
*/
protected void setFromOriginal(AbstractBeanCollection<E> other) {
this.disableLazyLoad = other.disableLazyLoad;
this.ebeanServerName = other.ebeanServerName;
this.loader = other.loader;
this.ownerBean = other.ownerBean;
this.propertyName = other.propertyName;
}
}
@@ -2,7 +2,6 @@ package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.*;
/**
@@ -39,6 +38,17 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
super(loader, ownerBean, propertyName);
}
@Override
public List<E> freeze() {
if (list == null) {
return null;
} else if (list.isEmpty()) {
return List.of();
} else {
return Collections.unmodifiableList(list);
}
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(list);
@@ -223,7 +233,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void add(int index, E element) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(element);
@@ -238,7 +247,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (list.add(bean)) {
@@ -253,7 +261,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
// all elements in c are added (no contains checking)
@@ -264,7 +271,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean addAll(int index, Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
// all elements in c are added (no contains checking)
@@ -275,7 +281,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void clear() {
checkReadOnly();
// TODO: when clear() and not initialised could be more clever
// and fetch just the Id's
initClear();
@@ -320,9 +325,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, list.iterator());
}
@@ -338,9 +340,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public ListIterator<E> listIterator() {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator());
}
if (modifyListening) {
return new ModifyListIterator<>(this, list.listIterator());
}
@@ -350,9 +349,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public ListIterator<E> listIterator(int index) {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator(index));
}
if (modifyListening) {
return new ModifyListIterator<>(this, list.listIterator(index));
}
@@ -368,7 +364,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public E remove(int index) {
checkReadOnly();
init();
if (modifyListening) {
E o = list.remove(index);
@@ -380,7 +375,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
boolean isRemove = list.remove(bean);
@@ -394,7 +388,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -412,7 +405,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean retainAll(Collection<?> retainBeans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -433,7 +425,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public E set(int index, E element) {
checkReadOnly();
init();
if (modifyListening) {
E o = list.set(index, element);
@@ -453,9 +444,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public List<E> subList(int fromIndex, int toIndex) {
init();
if (readOnly) {
return Collections.unmodifiableList(list.subList(fromIndex, toIndex));
}
if (modifyListening) {
return new ModifyList<>(this, list.subList(fromIndex, toIndex));
}
@@ -475,67 +463,4 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
return list.toArray(array);
}
private static final class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable {
private static final long serialVersionUID = 3097271091406323699L;
private final ListIterator<E> i;
ReadOnlyListIterator(ListIterator<E> i) {
this.i = i;
}
@Override
public void add(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public void set(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public boolean hasNext() {
return i.hasNext();
}
@Override
public boolean hasPrevious() {
return i.hasPrevious();
}
@Override
public E next() {
return i.next();
}
@Override
public int nextIndex() {
return i.nextIndex();
}
@Override
public E previous() {
return i.previous();
}
@Override
public int previousIndex() {
return i.previousIndex();
}
}
@Override
public BeanCollection<E> shallowCopy() {
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
copy.setFromOriginal(this);
return copy;
}
}
@@ -37,6 +37,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@Override
public Map<K, E> freeze() {
return map == null ? null : Collections.unmodifiableMap(map);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
@@ -217,7 +222,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
// add all beans to the removal list
@@ -243,9 +247,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Set<Entry<K, E>> entrySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.entrySet());
}
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
}
@@ -264,15 +265,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Set<K> keySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.keySet());
}
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
}
@Override
public E put(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.put(key, value);
@@ -289,7 +286,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void putAll(Map<? extends K, ? extends E> puts) {
checkReadOnly();
init();
if (modifyListening) {
for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
@@ -306,17 +302,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void addBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
throw new UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
}
@Override
public void removeBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
throw new UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
}
@Override
public E remove(Object key) {
checkReadOnly();
init();
if (modifyListening) {
E o = map.remove(key);
@@ -335,16 +330,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Collection<E> values() {
init();
if (readOnly) {
return Collections.unmodifiableCollection(map.values());
}
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
}
@Override
public BeanCollection<E> shallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
}
@@ -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.
@@ -38,6 +35,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
super(loader, ownerBean, propertyName);
}
@Override
public Set<E> freeze() {
return set == null ? null : Collections.unmodifiableSet(set);
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(set);
@@ -211,7 +213,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.add(bean)) {
@@ -226,7 +227,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -244,7 +244,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
for (E bean : set) {
@@ -275,9 +274,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyIterator<>(set.iterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, set.iterator());
}
@@ -286,7 +282,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.remove(bean)) {
@@ -300,7 +295,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -317,7 +311,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean retainAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -355,36 +348,4 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
return set.toArray(array);
}
private static final class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
private static final long serialVersionUID = 2577697326745352605L;
private final Iterator<E> it;
ReadOnlyIterator(Iterator<E> it) {
this.it = it;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public E next() {
return it.next();
}
@Override
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> shallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -1,185 +0,0 @@
package io.ebean.common;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
* to modify lists from cache.
*
* @author Roland Praml, FOCONIS AG
*/
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
private static final long serialVersionUID = 1L;
private final ReentrantLock lock = new ReentrantLock();
/**
* The underlying List implementation.
*/
private List<E> list;
public CopyOnFirstWriteList(List<E> list) {
super();
this.list = list;
}
private volatile boolean copied = false;
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
checkCopyOnWrite();
return list.add(e);
}
@Override
public boolean remove(Object o) {
checkCopyOnWrite();
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
checkCopyOnWrite();
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
checkCopyOnWrite();
return list.retainAll(c);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
checkCopyOnWrite();
list.replaceAll(operator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
checkCopyOnWrite();
return list.removeIf(filter);
}
@Override
public void sort(Comparator<? super E> c) {
checkCopyOnWrite();
list.sort(c);
}
@Override
public void clear() {
if (!copied) {
list = new ArrayList<>();
copied = true;
}
}
@Override
public boolean equals(Object o) {
return list.equals(o);
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
checkCopyOnWrite();
return list.set(index, element);
}
@Override
public void add(int index, E element) {
checkCopyOnWrite();
list.add(index, element);
}
@Override
public E remove(int index) {
checkCopyOnWrite();
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
private void checkCopyOnWrite() {
if (!copied) {
lock.lock();
try {
if (!copied) {
list = new ArrayList<>(list);
copied = true;
}
} finally {
lock.unlock();
}
}
}
}
@@ -7,6 +7,8 @@ import java.sql.SQLException;
public interface DataReader {
boolean unmodifiable();
void close() throws SQLException;
boolean next() throws SQLException;
@@ -55,4 +55,9 @@ public interface LoadContext {
* Use soft-references for streaming queries, so unreachable entries can be garbage collected.
*/
void useReferences(boolean useReferences);
/**
* Return true to include a many as a secondary query for unmodified.
*/
boolean includeSecondary(BeanPropertyAssocMany<?> many);
}
@@ -780,9 +780,9 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
CacheMode queryCacheMode();
/**
* Return true if the beans returned by this query should be read only.
* Return true if the beans returned by this query should be unmodifiable.
*/
Boolean isReadOnly();
boolean isUnmodifiable();
/**
* Return the query timeout.
@@ -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();
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.cache;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -48,25 +49,21 @@ public final class CachedBeanDataFromBean {
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
return null;
}
if (beanEbi.isReadOnly()) {
if (beanEbi instanceof InterceptReadOnly) {
return bean;
}
// create a readOnly sharable instance by copying the data
EntityBean sharableBean = desc.createEntityBean();
EntityBean sharableBean = desc.createEntityBean2(true);
BeanProperty idProp = desc.idProperty();
if (idProp != null) {
Object v = idProp.getValue(bean);
idProp.setValue(sharableBean, v);
}
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
for (BeanProperty aPropertiesNonTransient : propertiesNonTransient) {
Object v = aPropertiesNonTransient.getValue(bean);
aPropertiesNonTransient.setValue(sharableBean, v);
for (BeanProperty nonTransient : desc.propertiesNonTransient()) {
Object v = nonTransient.getValue(bean);
nonTransient.setValue(sharableBean, v);
}
EntityBeanIntercept intercept = sharableBean._ebean_getIntercept();
intercept.setReadOnly(true);
intercept.setLoaded();
desc.freeze(sharableBean);
return sharableBean;
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.server.cache;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -25,10 +23,11 @@ public final class CachedBeanDataToBean {
for (BeanProperty prop : desc.propertiesNonMany()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
}
final boolean addManyReferences = ebi instanceof InterceptReadWrite;
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
} else {
} else if (addManyReferences) {
prop.createReferenceIfNull(bean);
}
}
@@ -67,12 +67,8 @@ final class DefaultBeanLoader {
parentDesc.contextPutIfAbsent(pc, parentId, parentBean);
boolean useManyIdCache = beanCollection != null && parentDesc.isManyPropCaching() && many.isUseCache();
if (useManyIdCache) {
Boolean readOnly = null;
if (ebi.isReadOnly()) {
readOnly = Boolean.TRUE;
}
final String parentKey = parentDesc.cacheKey(parentId);
if (parentDesc.cacheManyPropLoad(many, beanCollection, parentKey, readOnly)) {
if (parentDesc.cacheManyPropLoad(many, beanCollection, parentKey)) {
return;
}
}
@@ -102,9 +98,6 @@ final class DefaultBeanLoader {
query.setMode(Mode.LAZYLOAD_MANY);
query.setLazyLoadManyPath(many.name());
query.setPersistenceContext(pc);
if (ebi.isReadOnly()) {
query.setReadOnly(true);
}
if (many.hasOrderColumn()) {
query.orderBy(many.path() + "." + many.fetchOrderBy());
}
@@ -212,9 +205,6 @@ final class DefaultBeanLoader {
// make sure the query doesn't use the cache
query.setBeanCacheMode(CacheMode.OFF);
}
if (ebi.isReadOnly()) {
query.setReadOnly(true);
}
if (Mode.REFRESH_BEAN == mode) {
// explicitly state to load all properties on REFRESH.
// Lobs default to fetch lazy so this forces lobs to be
@@ -4,6 +4,7 @@ import io.ebean.BeanState;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
import java.util.Map;
import java.util.Set;
@@ -60,13 +61,8 @@ public final class DefaultBeanState implements BeanState {
}
@Override
public boolean isReadOnly() {
return intercept.isReadOnly();
}
@Override
public void setReadOnly(boolean readOnly) {
intercept.setReadOnly(readOnly);
public boolean isUnmodifiable() {
return intercept instanceof InterceptReadOnly;
}
@Override
@@ -8,7 +8,6 @@ import io.ebean.annotation.TxIsolation;
import io.ebean.bean.*;
import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
@@ -610,7 +609,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
InheritInfo inheritInfo = desc.inheritInfo();
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
return (T) desc.contextRef(pc, id);
}
return referenceFindOne(type, id, desc);
}
@@ -994,7 +993,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return null;
}
// Hit the L2 bean cache
return desc.cacheBeanGet(id, query.isReadOnly(), pc);
return desc.cacheBeanGet(id, query.isUnmodifiable(), pc);
}
/**
@@ -1205,11 +1204,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query);
Object result = request.getFromQueryCache();
if (result != null) {
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
return new CopyOnFirstWriteList<>((List<A>) result);
} else {
return (List<A>) result;
}
return (List<A>) result;
}
try {
request.initTransIfRequired();
@@ -67,7 +67,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
@Override
protected void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException {
this.resultSet = resultSet;
this.dataReader = new RsetDataReader(server.dataTimeZone(), resultSet);
this.dataReader = new RsetDataReader(false, server.dataTimeZone(), resultSet);
obtainPlan(queryPlanKey);
}
@@ -25,9 +25,9 @@ public interface OrmQueryEngine {
<T> T findId(OrmQueryRequest<T> request);
/**
* Execute the findList, findSet, findMap query returning an appropriate BeanCollection.
* Execute the findList, findSet, findMap query returning an appropriate Collection.
*/
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
<T> Object findMany(OrmQueryRequest<T> request);
/**
* Execute the findSingleAttributeCollection query.
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.core;
import io.ebean.*;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.common.BeanList;
import io.ebean.common.BeanMap;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.text.json.JsonReadOptions;
@@ -546,7 +546,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
if (orderBy != null && !orderBy.isEmpty()) {
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
}
return cacheBeans;
return query.isUnmodifiable() ? Collections.unmodifiableList(cacheBeans) : cacheBeans;
}
@Override
@@ -565,7 +565,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
for (T bean : cacheBeans) {
map.put((K) property.pathGet(bean), bean);
}
return map;
return query.isUnmodifiable() ? Collections.unmodifiableMap(map) : map;
}
private ElPropertyValue mapProperty() {
@@ -583,7 +583,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
if (orderBy != null && !orderBy.isEmpty()) {
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
}
return new LinkedHashSet<>(cacheBeans);
var set = new LinkedHashSet<>(cacheBeans);
return query.isUnmodifiable() ? Collections.unmodifiableSet(set) : set;
}
@Override
@@ -600,9 +601,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
//
CacheIdLookup<T> idLookup = query.cacheIdLookup();
if (idLookup != null) {
BeanCacheResult<T> cacheResult = beanDescriptor.cacheIdLookup(persistenceContext, idLookup.idValues());
BeanCacheResult<T> cacheResult = beanDescriptor.cacheIdLookup(persistenceContext, query.isUnmodifiable(), idLookup.idValues());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = idLookup.removeHits(cacheResult);
if (query.isUnmodifiable()) {
unmodifiableFreeze(cacheBeans);
}
return idLookup.allHits();
}
if (!beanDescriptor.isNaturalKeyCaching()) {
@@ -613,7 +617,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
NaturalKeySet naturalKeySet = data.buildKeys();
if (naturalKeySet != null) {
// use the natural keys to lookup Ids to then hit the bean cache
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, naturalKeySet.keys());
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, query.isUnmodifiable(), naturalKeySet.keys());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = data.removeHits(cacheResult);
return data.allHits();
@@ -639,31 +643,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return null;
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
if (cached instanceof BeanCollection) {
// raw sql can't use L2 cache so normal queries only in here
Collection<T> actualDetails = ((BeanCollection<T>) cached).actualDetails();
List<Object> ids = new ArrayList<>(actualDetails.size());
for (T bean : actualDetails) {
ids.add(beanDescriptor.idForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanKey.partialKey(), "l2-query-cache", ids);
}
}
if (Boolean.FALSE.equals(query.isReadOnly())) {
// return shallow copies if readonly is explicitly set to false
if (cached instanceof BeanCollection) {
cached = ((BeanCollection<?>) cached).shallowCopy();
} else if (cached instanceof List) {
cached = new CopyOnFirstWriteList<>((List<?>) cached);
} else if (cached instanceof Set) {
cached = new LinkedHashSet<>((Set<?>) cached);
} else if (cached instanceof Map) {
cached = new LinkedHashMap<>((Map<?, ?>) cached);
}
}
return cached;
return beanDescriptor.queryCacheGet(cacheKey);
}
/**
@@ -780,4 +760,30 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
beanDescriptor.contextClear(transaction.persistenceContext());
}
}
public void unmodifiableFreeze(Collection<T> beans) {
if (query.isUnmodifiable()) {
if (beans != null) {
for (T bean : beans) {
beanDescriptor.freeze((EntityBean) bean);
}
}
}
}
public void unmodifiableFreeze(BeanCollection<T> beanCollection) {
if (query.isUnmodifiable()) {
if (beanCollection != null) {
for (T bean : beanCollection.actualDetails()) {
beanDescriptor.freeze((EntityBean) bean);
}
}
}
}
public void unmodifiableFreeze(EntityBean bean) {
if (query.isUnmodifiable() && bean != null) {
beanDescriptor.freeze(bean);
}
}
}
@@ -14,7 +14,7 @@ import java.sql.SQLException;
abstract class AssocOneHelp {
final BeanPropertyAssocOne<?> property;
private final BeanDescriptor<?> target;
protected final BeanDescriptor<?> target;
private final String path;
AssocOneHelp(BeanPropertyAssocOne<?> property) {
@@ -66,9 +66,8 @@ abstract class AssocOneHelp {
if (existing != null) {
return existing;
}
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
Object ref = target.contextRef(pc, id, ctx.unmodifiable(), ctx.isDisableLazyLoading());
if (!ctx.unmodifiable() && !ctx.isDisableLazyLoading()) {
ctx.register(path, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
@@ -81,7 +80,6 @@ abstract class AssocOneHelp {
Object val = read(ctx);
if (bean != null) {
property.setValue(bean, val);
ctx.propagateState(val);
}
return val;
}
@@ -32,7 +32,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
@Override
Object read(DataReader reader) throws SQLException {
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
EntityBean embeddedBean = property.targetDescriptor.createEntityBean2(reader.unmodifiable());
boolean notNull = false;
for (BeanProperty property : property.embeddedProps) {
Object value = property.readSet(reader, embeddedBean);
@@ -53,7 +53,6 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
if (bean != null) {
// set back to the parent bean
property.setValue(bean, dbVal);
ctx.propagateState(dbVal);
return dbVal;
} else {
return null;
@@ -62,7 +61,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
@Override
Object read(DbReadContext ctx) throws SQLException {
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
EntityBean embeddedBean = property.targetDescriptor.createEntityBean2(ctx.unmodifiable());
boolean notNull = false;
for (BeanProperty property : property.embeddedProps) {
Object value = property.readSet(ctx, embeddedBean);
@@ -70,12 +69,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
notNull = true;
}
}
if (notNull) {
ctx.propagateState(embeddedBean);
return embeddedBean;
} else {
return null;
}
return notNull ? embeddedBean : null;
}
@Override
@@ -53,9 +53,8 @@ final class AssocOneHelpRefInherit extends AssocOneHelp {
return existing;
}
// for inheritance hierarchy create the correct type for this row...
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
Object ref = desc.contextRef(pc, id, ctx.unmodifiable(), ctx.isDisableLazyLoading());
if (!ctx.unmodifiable() && !ctx.isDisableLazyLoading()) {
ctx.registerBeanInherit(property, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
@@ -688,6 +688,22 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
@Override
public void freeze(EntityBean entityBean) {
if (entityBean._ebean_getIntercept().freeze()) {
// recursively freeze the graph for this entityBean
for (BeanProperty beanProperty : propertiesMutable) {
beanProperty.freeze(entityBean);
}
for (BeanPropertyAssocOne<?> one : propertiesOne) {
one.freeze(entityBean);
}
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.freeze(entityBean);
}
}
}
public void metricPersistBatch(PersistRequest.Type type, long startNanos, int size) {
iudMetrics.addBatch(type, startNanos, size);
}
@@ -1190,8 +1206,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Try to load the beanCollection from cache return true if successful.
*/
public boolean cacheManyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey, Boolean readOnly) {
return cacheHelp.manyPropLoad(many, bc, parentKey, readOnly);
public boolean cacheManyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey) {
return cacheHelp.manyPropLoad(many, bc, parentKey);
}
/**
@@ -1240,8 +1256,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Load the entity bean as the correct bean type.
*/
EntityBean cacheBeanLoadDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
return cacheHelp.loadBeanDirect(id, readOnly, data, context);
EntityBean cacheBeanLoadDirect(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
return cacheHelp.loadBeanDirect(id, unmodifiable, data, context);
}
/**
@@ -1279,8 +1295,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Return a bean from the bean cache (or null).
*/
public T cacheBeanGet(Object id, Boolean readOnly, PersistenceContext context) {
return cacheHelp.beanCacheGet(cacheKey(id), readOnly, context);
public T cacheBeanGet(Object id, boolean unmodifiable, PersistenceContext context) {
return cacheHelp.beanCacheGet(cacheKey(id), unmodifiable, context);
}
/**
@@ -1309,15 +1325,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
}
public BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
return cacheHelp.cacheIdLookup(context, ids);
public BeanCacheResult<T> cacheIdLookup(PersistenceContext context, boolean unmodifiable, Collection<?> ids) {
return cacheHelp.cacheIdLookup(context, unmodifiable, ids);
}
/**
* Use natural key lookup to hit the bean cache.
*/
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
return cacheHelp.naturalKeyLookup(context, keys);
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, boolean unmodifiable, Set<Object> keys) {
return cacheHelp.naturalKeyLookup(context, unmodifiable, keys);
}
public void cacheNaturalKeyPut(String key, String newKey) {
@@ -1765,10 +1781,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Create a reference with a check for the bean in the persistence context.
*/
public EntityBean createReference(Boolean readOnly, Object id, PersistenceContext pc) {
public EntityBean createReference(PersistenceContext pc, Object id) {
Object refBean = contextGet(pc, id);
if (refBean == null) {
refBean = createReference(readOnly, false, id, pc);
refBean = createReference(false, false, id, pc);
}
return (EntityBean) refBean;
}
@@ -1777,8 +1793,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Create a reference bean based on the id.
*/
@SuppressWarnings("unchecked")
public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) {
if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) {
public T createReference(boolean unmodifiable, boolean disableLazyLoad, Object id, PersistenceContext pc) {
if (cacheSharableBeans && unmodifiable) {
CachedBeanData d = cacheHelp.beanCacheGetData(cacheKey(id));
if (d != null) {
Object shareableBean = d.getSharableBean();
@@ -1794,18 +1810,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
EntityBean eb = createEntityBean2(unmodifiable);
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
if (disableLazyLoad) {
ebi.setDisableLazyLoad(true);
} else {
} else if (!unmodifiable) {
ebi.setBeanLoader(refBeanLoader());
}
ebi.setReference(idPropertyIndex);
if (Boolean.TRUE == readOnly) {
ebi.setReadOnly(true);
}
if (pc != null) {
contextPut(pc, id, eb);
ebi.setPersistenceContext(pc);
@@ -2030,11 +2043,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return pc.putIfAbsent(rootBeanType, id, localBean);
}
/**
* Create a reference bean and put it in the persistence context (and return it).
*/
public Object contextRef(PersistenceContext pc, Boolean readOnly, boolean disableLazyLoad, Object id) {
return createReference(readOnly, disableLazyLoad, id, pc);
public Object contextRef(PersistenceContext pc, Object id) {
return createReference(false, false, id, pc);
}
public Object contextRef(PersistenceContext pc, Object id, boolean unmodifiable, boolean disableLazyLoad) {
return createReference(unmodifiable, disableLazyLoad, id, pc);
}
/**
@@ -2149,7 +2163,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Set the Id value to the bean (without type conversion).
*/
public void setId(Object idValue, EntityBean bean) {
idProperty.setValueIntercept(bean, idValue);
idProperty.setValue(bean, idValue);
}
@Override
@@ -261,7 +261,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Try to load the bean collection from cache return true if successful.
*/
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey, Boolean readOnly) {
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey) {
if (many.isElementCollection()) {
// held as part of the bean cache so skip
return false;
@@ -280,7 +280,7 @@ final class BeanDescriptorCacheHelp<T> {
bc.checkEmptyLazyLoad();
int i = 0;
for (Object id : idList) {
final EntityBean ref = targetDescriptor.createReference(readOnly, id, persistenceContext);
final EntityBean ref = targetDescriptor.createReference(persistenceContext, id);
if (many.hasOrderColumn()) {
ref._ebean_getIntercept().setSortOrder(++i);
}
@@ -344,7 +344,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Hit the bean cache with the given ids returning the hits.
*/
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, boolean unmodifiable, Collection<?> ids) {
Set<Object> keys = new HashSet<>(ids.size());
for (Object id : ids) {
keys.add(desc.cacheKey(id));
@@ -359,7 +359,7 @@ final class BeanDescriptorCacheHelp<T> {
BeanCacheResult<T> result = new BeanCacheResult<>();
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(entry.getKey(), false, context, cachedBeanData);
T bean = convertToBean(entry.getKey(), unmodifiable, context, cachedBeanData);
result.add(bean, desc.id(bean));
}
return result;
@@ -368,7 +368,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Use natural keys to hit the bean cache and return resulting hits.
*/
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, boolean unmodifiable, Set<Object> keys) {
if (context == null) {
context = new DefaultPersistenceContext();
}
@@ -399,7 +399,7 @@ final class BeanDescriptorCacheHelp<T> {
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
Object id = entry.getKey();
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(id, false, context, cachedBeanData);
T bean = convertToBean(id, unmodifiable, context, cachedBeanData);
Object naturalKey = reverseMap.get(id);
result.add(bean, naturalKey);
}
@@ -561,9 +561,9 @@ final class BeanDescriptorCacheHelp<T> {
return (CachedBeanData) getBeanCache().get(key);
}
T beanCacheGet(String key, Boolean readOnly, PersistenceContext context) {
T bean = beanCacheGetInternal(key, readOnly, context);
if (bean != null) {
T beanCacheGet(String key, boolean unmodifiable, PersistenceContext context) {
T bean = beanCacheGetInternal(key, unmodifiable, context);
if (bean != null && !unmodifiable) {
setupContext(bean, context);
}
return bean;
@@ -572,7 +572,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Return a bean from the bean cache.
*/
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
private T beanCacheGetInternal(String key, boolean unmodifiable, PersistenceContext context) {
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
if (data == null) {
if (beanLog.isLoggable(TRACE)) {
@@ -583,12 +583,12 @@ final class BeanDescriptorCacheHelp<T> {
if (beanLog.isLoggable(TRACE)) {
beanLog.log(TRACE, " GET {0}({1}) - hit", cacheName, key);
}
return convertToBean(key, readOnly, context, data);
return convertToBean(key, unmodifiable, context, data);
}
@SuppressWarnings("unchecked")
private T convertToBean(Object id, Boolean readOnly, PersistenceContext context, CachedBeanData data) {
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
private T convertToBean(Object id, boolean unmodifiable, PersistenceContext context, CachedBeanData data) {
if (cacheSharableBeans && unmodifiable) {
Object bean = data.getSharableBean();
if (bean != null) {
if (beanLog.isLoggable(TRACE)) {
@@ -600,18 +600,18 @@ final class BeanDescriptorCacheHelp<T> {
return (T) bean;
}
}
return (T) loadBean(id, readOnly, data, context);
return (T) loadBean(id, unmodifiable, data, context);
}
/**
* Load the entity bean taking into account inheritance.
*/
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
private EntityBean loadBean(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
String discValue = data.getDiscValue();
if (discValue == null) {
return loadBeanDirect(id, readOnly, data, context);
return loadBeanDirect(id, unmodifiable, data, context);
} else {
return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data, context);
return rootDescriptor(discValue).cacheBeanLoadDirect(id, unmodifiable, data, context);
}
}
@@ -625,26 +625,22 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Load the entity bean from cache data given this is the root bean type.
*/
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
EntityBean loadBeanDirect(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
id = desc.convertId(id);
EntityBean bean = null;
if (context == null) {
context = new DefaultPersistenceContext();
} else {
bean = (EntityBean) desc.contextGet(context, id);
}
EntityBean bean = context == null ? null : (EntityBean) desc.contextGet(context, id);;
if (bean == null) {
bean = desc.createEntityBean();
bean = desc.createEntityBean2(unmodifiable);
desc.setId(id, bean);
desc.contextPut(context, id, bean);
EntityBeanIntercept ebi = bean._ebean_getIntercept();
// Not using loadContext here so no batch lazy loading for these beans
ebi.setBeanLoader(desc.l2BeanLoader());
if (Boolean.TRUE.equals(readOnly)) {
ebi.setReadOnly(true);
if (!unmodifiable) {
if (context == null) {
context = new DefaultPersistenceContext();
}
desc.contextPut(context, id, bean);
EntityBeanIntercept ebi = bean._ebean_getIntercept();
ebi.setPersistenceContext(context);
// Not using loadContext here so no batch lazy loading for these beans
ebi.setBeanLoader(desc.l2BeanLoader());
}
ebi.setPersistenceContext(context);
}
CachedBeanDataToBean.load(desc, bean, data, context);
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.DataIntegrityException;
import io.ebean.ModifyAwareType;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -1519,4 +1520,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());
}
}
}
@@ -1165,4 +1165,22 @@ 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<?> beanCollection = (BeanCollection<?>) value;
Collection<?> entities = beanCollection.actualDetails();
if (entities != null) {
for (Object actualEntry : entities) {
targetDescriptor.freeze((EntityBean) actualEntry);
}
}
setValue(entityBean, beanCollection.freeze());
} else if (value == null) {
// make it an error to access the collection (no lazy loading allowed)
entityBean._ebean_getIntercept().setPropertyUnloaded(propertyIndex);
}
}
}
@@ -663,6 +663,14 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
setValue(entityBean, targetDescriptor.createRef(tenantId, null));
}
@Override
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof EntityBean) {
targetDescriptor.freeze((EntityBean) value);
}
}
@Override
public void setValue(EntityBean bean, Object value) {
super.setValue(bean, value);
@@ -14,16 +14,6 @@ import java.util.Map;
*/
public interface DbReadContext {
/**
* Return the state of the object graph.
*/
Boolean isReadOnly();
/**
* Propagate the state to the bean.
*/
void propagateState(Object e);
/**
* Return the DataReader.
*/
@@ -102,4 +92,14 @@ public interface DbReadContext {
*/
void handleLoadError(String fullName, Exception e);
/**
* Return true if this many property should be included in unmodifiable
* query via a secondary query.
*/
boolean includeSecondary(BeanPropertyAssocMany<?> many);
/**
* Return true if we are loading unmodifiable beans.
*/
boolean unmodifiable();
}
@@ -228,8 +228,8 @@ public final class InheritInfo {
/**
* Create an EntityBean for this type.
*/
public EntityBean createEntityBean() {
return descriptor.createEntityBean();
public EntityBean createEntityBean(boolean unmodifiable) {
return descriptor.createEntityBean2(unmodifiable);
}
/**
@@ -3,7 +3,6 @@ package io.ebeaninternal.server.deploy.id;
import io.ebean.bean.EntityBean;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiExpressionBind;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
@@ -324,7 +323,7 @@ public final class IdBinderEmbedded implements IdBinder {
@Override
public Object read(DbReadContext ctx) throws SQLException {
final EntityBean embId = idDesc.createEntityBean();
final EntityBean embId = idDesc.createEntityBean2(ctx.unmodifiable());
boolean nullValue = true;
for (BeanProperty prop : props) {
final Object value = prop.read(ctx);
@@ -245,7 +245,8 @@ public final class IdBinderSimple implements IdBinder {
idValue = scalarType.toBeanType(idValue);
}
if (bean != null) {
idProperty.setValueIntercept(bean, idValue);
// not using interception to support unmodifiable entities
idProperty.setValue(bean, idValue);
}
return idValue;
}
@@ -11,9 +11,7 @@ import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Default implementation of LoadContext.
@@ -27,7 +25,7 @@ public final class DLoadContext implements LoadContext {
private final DLoadBeanContext rootBeanContext;
private final boolean asDraft;
private final Timestamp asOf;
private final Boolean readOnly;
private final boolean unmodifiable;
private final CacheMode useBeanCache;
private final int defaultBatchSize;
private final boolean disableLazyLoading;
@@ -48,6 +46,7 @@ public final class DLoadContext implements LoadContext {
boolean useReferences;
private List<OrmQueryProperties> secQuery;
private Object tenantId;
private final Set<BeanProperty> secondaryProperties;
/**
* Construct for use with JSON marshalling (doc store).
@@ -62,7 +61,7 @@ public final class DLoadContext implements LoadContext {
this.useBeanCache = CacheMode.OFF;
this.asDraft = false;
this.asOf = null;
this.readOnly = false;
this.unmodifiable = false;
this.disableLazyLoading = false;
this.disableReadAudit = false;
this.includeSoftDeletes = false;
@@ -71,6 +70,7 @@ public final class DLoadContext implements LoadContext {
this.profileLocation = null;
this.profilingListener = null;
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
this.secondaryProperties = null;
}
private ObjectGraphOrigin initOrigin() {
@@ -90,13 +90,14 @@ public final class DLoadContext implements LoadContext {
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.includeSoftDeletes = query.isIncludeSoftDeletes() && query.mode() == SpiQuery.Mode.NORMAL;
this.readOnly = query.isReadOnly();
this.unmodifiable = query.isUnmodifiable();
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
this.useBeanCache = query.beanCacheMode();
this.profilingListener = query.profilingListener();
this.planLabel = query.planLabel();
this.profileLocation = query.profileLocation();
this.secondaryProperties = query.isUnmodifiable() || query.isDisableLazyLoading() ? new HashSet<>() : null;
ObjectGraphNode parentNode = query.parentNode();
if (parentNode != null) {
@@ -156,6 +157,14 @@ public final class DLoadContext implements LoadContext {
ElPropertyValue elGetValue = rootDescriptor.elGetValue(props.getPath());
boolean many = elGetValue.beanProperty().containsMany();
registerSecondaryNode(many, props);
if (many && secondaryProperties != null) {
secondaryProperties.add(elGetValue.beanProperty());
}
}
@Override
public boolean includeSecondary(BeanPropertyAssocMany<?> many) {
return secondaryProperties != null && secondaryProperties.contains(many);
}
boolean isBeanCacheGet() {
@@ -230,14 +239,6 @@ public final class DLoadContext implements LoadContext {
return ebeanServer;
}
/**
* Return the parent state which defines the sharedInstance and readOnly status
* which needs to be propagated to other beans and collections.
*/
Boolean isReadOnly() {
return readOnly;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
@@ -320,9 +321,7 @@ public final class DLoadContext implements LoadContext {
if (useDocStore && docStoreMapped) {
query.setUseDocStore(true);
}
if (readOnly != null) {
query.setReadOnly(readOnly);
}
query.setUnmodifiable(unmodifiable);
query.setDisableLazyLoading(disableLazyLoading);
query.asOf(asOf);
if (asDraft) {
@@ -198,7 +198,7 @@ final class DLoadManyContext extends DLoadBaseContext implements LoadManyContext
BeanDescriptor<?> parentDesc = context.desc.descriptor(ownerBean.getClass());
Object parentId = parentDesc.getId(ownerBean);
final String parentKey = parentDesc.cacheKey(parentId);
if (parentDesc.cacheManyPropLoad(context.property, bc, parentKey, context.parent.isReadOnly())) {
if (parentDesc.cacheManyPropLoad(context.property, bc, parentKey)) {
// we loaded the bean collection from cache so remove it from the buffer
if (removeFromBuffer(bc)) {
bc.setLoader(context.parent.server());
@@ -401,7 +401,7 @@ public final class Binder {
return new DataBind(dataTimeZone, stmt, connection);
}
public DataReader createDataReader(ResultSet resultSet) {
return new RsetDataReader(dataTimeZone, resultSet);
public DataReader createDataReader(boolean unmodifiable, ResultSet resultSet) {
return new RsetDataReader(unmodifiable, dataTimeZone, resultSet);
}
}
@@ -44,6 +44,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
private final ReentrantLock lock = new ReentrantLock();
private final boolean loadContextBean;
private final boolean unmodifiable;
/**
* The resultSet rows read.
@@ -152,8 +153,6 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
private final ProfilingListener profilingListener;
private final Boolean readOnly;
private long profileOffset;
private long startNano;
@@ -186,7 +185,6 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
this.queryMode = query.mode();
this.loadContextBean = queryMode.isLoadContextBean() || query.getForUpdateLockType() != null;
this.lazyLoadManyProperty = query.lazyLoadMany();
this.readOnly = query.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.parentNode();
this.profilingListener = query.profilingListener();
@@ -207,6 +205,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
} else {
this.help = createHelp(request);
}
this.unmodifiable = request.query().isUnmodifiable();
this.collection = (help != null ? help.createEmptyNoParent() : null);
}
@@ -239,17 +238,8 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
}
@Override
public Boolean isReadOnly() {
return readOnly;
}
@Override
public void propagateState(Object e) {
if (Boolean.TRUE.equals(readOnly)) {
if (e instanceof EntityBean) {
((EntityBean) e)._ebean_getIntercept().setReadOnly(true);
}
}
public boolean unmodifiable() {
return unmodifiable;
}
@Override
@@ -299,7 +289,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
if (resultSet == null) {
return false;
}
dataReader = queryPlan.createDataReader(resultSet);
dataReader = queryPlan.createDataReader(query.isUnmodifiable(), resultSet);
return true;
}
@@ -474,6 +464,14 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
return result;
}
EntityBean nextBean() {
EntityBean bean = next();
if (unmodifiable) {
request.unmodifiableFreeze(bean);
}
return bean;
}
EntityBean next() {
if (audit) {
auditNextBean();
@@ -603,6 +601,11 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
request.loadContext().register(path(many.name()), many, bc);
}
@Override
public boolean includeSecondary(BeanPropertyAssocMany<?> many) {
return request.loadContext().includeSecondary(many);
}
/**
* Return true if this is a raw sql query as opposed to Ebean generated sql.
*/
@@ -107,15 +107,9 @@ public final class CQueryEngine {
if (collection instanceof List) {
collection = (A) Collections.unmodifiableList((List<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new ArrayList<>(collection);
}
} else if (collection instanceof Set) {
collection = (A) Collections.unmodifiableSet((Set<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new LinkedHashSet<>(collection);
}
}
}
return collection;
@@ -358,6 +352,7 @@ public final class CQueryEngine {
if (request.isQueryCachePut()) {
request.addDependentTables(cquery.dependentTables());
}
request.unmodifiableFreeze(beanCollection);
return beanCollection;
} catch (SQLException e) {
@@ -390,6 +385,7 @@ public final class CQueryEngine {
cquery.auditFind(bean);
}
request.executeSecondaryQueries(false);
request.unmodifiableFreeze(bean);
return (T) bean;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
@@ -137,7 +137,7 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca
} finally {
lock.unlock();
}
dataReader = new RsetDataReader(request.dataTimeZone(), pstmt.executeQuery());
dataReader = new RsetDataReader(query.isUnmodifiable(), request.dataTimeZone(), pstmt.executeQuery());
query.checkCancelled();
}
@@ -37,7 +37,7 @@ final class CQueryIteratorSimple<T> implements QueryIterator<T> {
@Override
@SuppressWarnings("unchecked")
public T next() {
return (T) cquery.next();
return (T) cquery.nextBean();
}
@Override
@@ -44,6 +44,7 @@ final class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
}
request.executeSecondaryQueries(true);
}
request.unmodifiableFreeze(buffer);
ret = !buffer.isEmpty();
return ret;
} catch (SQLException e) {
@@ -205,8 +205,8 @@ public class CQueryPlan implements SpiQueryPlan {
return new DQueryPlanOutput(beanType(), name, hash, sql, profileLocation, bind, planString);
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(dataTimeZone, rset);
public DataReader createDataReader(boolean unmodifiable, ResultSet rset) {
return new RsetDataReader(unmodifiable, dataTimeZone, rset);
}
/**
@@ -22,8 +22,8 @@ final class CQueryPlanRawSql extends CQueryPlan {
}
@Override
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReaderIndexed(dataTimeZone, rset, rsetIndexPositions);
public DataReader createDataReader(boolean unmodifiable, ResultSet rset) {
return new RsetDataReaderIndexed(unmodifiable, dataTimeZone, rset, rsetIndexPositions);
}
private int[] createIndexPositions(OrmQueryRequest<?> request, SqlTree sqlTree) {
@@ -504,7 +504,7 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
}
@Override
public Query<T> setReadOnly(boolean readOnly) {
public Query<T> setUnmodifiable(boolean unmodifiable) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@@ -114,7 +114,7 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
}
@Override
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
public <T> Object findMany(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
BeanFindController finder = request.finder();
@@ -129,22 +129,21 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
result = finder.postProcessMany(request, result);
}
SpiQuery<T> query = request.query();
if (result != null && request.isBeanCachePutMany()) {
// load the individual beans into the bean cache
request.descriptor().cacheBeanPutAll(result.actualDetails());
}
request.mergeCacheHits(result);
if (request.isQueryCachePut()) {
// load the query result into the query cache
result.setReadOnly(true);
request.putToQueryCache(result);
if (Boolean.FALSE.equals(query.isReadOnly())) {
result = result.shallowCopy();
Object finalResult = result;
if (request.query().isUnmodifiable()) {
finalResult = result == null ? null : result.freeze();
if (request.isQueryCachePut()) {
// load the query result into the query cache
request.putToQueryCache(finalResult);
}
}
return result;
return finalResult;
}
/**
@@ -147,7 +147,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
T value = null;
if (dataReader.next()) {
value = scalarType.read(dataReader);
@@ -172,7 +172,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
List<T> rows = new ArrayList<>();
while (dataReader.next()) {
rows.add(scalarType.read(dataReader));
@@ -197,7 +197,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindEach);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
while (dataReader.next()) {
consumer.accept(scalarType.read(dataReader));
}
@@ -34,7 +34,7 @@ public interface STreePropertyAssocMany extends STreePropertyAssoc {
BeanCollection<?> createReference(EntityBean localBean, boolean forceNewReference);
/**
* Populate the collection for read-only disabled lazy loading (aka Java Collections non mutable empty collection).
* Populate the collection for read-only disabled lazy loading (aka Java Collections non-mutable empty collection).
*/
void createEmptyReference(EntityBean localBean);
@@ -99,6 +99,11 @@ public interface STreeType {
*/
void postLoad(Object localBean);
/**
* Freeze the properties of the entity.
*/
void freeze(EntityBean entityBean);
/**
* Return the base table to use given the temporalMode.
*/
@@ -45,12 +45,12 @@ public final class SqlTreeBuilder {
*/
private final boolean rawNoId;
private final boolean disableLazyLoad;
private final boolean readOnly;
private final SpiQuery.TemporalMode temporalMode;
private final SqlTreeNode rootNode;
private boolean sqlDistinct;
private final boolean platformDistinctNoLobs;
private final SqlTreeCommon common;
private final boolean unmodifiable;
/**
* Construct for RawSql query.
@@ -60,7 +60,7 @@ public final class SqlTreeBuilder {
this.desc = request.descriptor();
this.rawNoId = rawNoId;
this.disableLazyLoad = request.query().isDisableLazyLoading();
this.readOnly = Boolean.TRUE.equals(request.query().isReadOnly());
this.unmodifiable = request.query().isUnmodifiable();
this.query = null;
this.subQuery = false;
this.distinctOnPlatform = false;
@@ -72,7 +72,7 @@ public final class SqlTreeBuilder {
this.manyWhereJoins = null;
this.alias = null;
this.ctx = null;
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, readOnly, null);
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, unmodifiable, null);
this.rootNode = buildRootNode(desc);
}
@@ -88,7 +88,7 @@ public final class SqlTreeBuilder {
this.query = request.query();
this.temporalMode = SpiQuery.TemporalMode.of(query);
this.disableLazyLoad = query.isDisableLazyLoading();
this.readOnly = Boolean.TRUE.equals(query.isReadOnly());
this.unmodifiable = query.isUnmodifiable();
this.subQuery = Type.SQ_EXISTS == query.type()
|| Type.SQ_EX == query.type()
|| Type.ID_LIST == query.type()
@@ -101,7 +101,7 @@ public final class SqlTreeBuilder {
this.alias = new SqlTreeAlias(request.baseTableAlias(), temporalMode);
this.distinctOnPlatform = builder.isPlatformDistinctOn();
this.platformDistinctNoLobs = builder.isPlatformDistinctNoLobs();
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, readOnly, includeJoin);
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, unmodifiable, includeJoin);
this.rootNode = buildRootNode(desc);
String fromForUpdate = builder.fromForUpdate(query);
CQueryHistorySupport historySupport = builder.historySupport(query);
@@ -7,13 +7,13 @@ final class SqlTreeCommon {
private final SpiQuery.TemporalMode temporalMode;
private final boolean disableLazyLoad;
private final boolean readOnly;
private final boolean unmodifiable;
private final TableJoin includeJoin;
SqlTreeCommon(SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly, TableJoin includeJoin) {
SqlTreeCommon(SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean unmodifiable, TableJoin includeJoin) {
this.temporalMode = temporalMode;
this.disableLazyLoad = disableLazyLoad;
this.readOnly = readOnly;
this.unmodifiable = unmodifiable;
this.includeJoin = includeJoin;
}
@@ -25,8 +25,8 @@ final class SqlTreeCommon {
return disableLazyLoad;
}
boolean readOnly() {
return readOnly;
boolean unmodifiable() {
return unmodifiable;
}
TableJoin includeJoin() {
@@ -31,7 +31,8 @@ class SqlTreeLoadBean implements SqlTreeLoad {
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
private final boolean readOnlyNoIntercept;
private final boolean unmodifiable;
private final boolean loadListReferences;
private final InheritInfo inheritInfo;
final String prefix;
private final Map<String, String> pathMap;
@@ -54,7 +55,8 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;
this.readOnlyNoIntercept = disableLazyLoad && node.readOnly;
this.unmodifiable = node.unmodifiable;
this.loadListReferences = !unmodifiable && !disableLazyLoad;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
@@ -111,7 +113,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
localIdBinder = idBinder;
localDesc = desc;
} else {
localBean = localInfo.createEntityBean();
localBean = localInfo.createEntityBean(unmodifiable);
localType = localInfo.getType();
localIdBinder = localInfo.getIdBinder();
localDesc = localInfo.desc();
@@ -162,7 +164,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
void initBeanType() throws SQLException {
localDesc = desc;
localBean = desc.createEntityBean2(readOnlyNoIntercept);
localBean = desc.createEntityBean2(unmodifiable);
localIdBinder = idBinder;
}
@@ -224,7 +226,6 @@ class SqlTreeLoadBean implements SqlTreeLoad {
private void initSqlLoadBean() {
ctx.setCurrentPrefix(prefix, pathMap);
ctx.propagateState(localBean);
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
}
@@ -302,9 +303,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != loadingChildProperty) {
if (readOnlyNoIntercept) {
many.createEmptyReference(localBean);
} else {
if (loadListReferences || ctx.includeSecondary(many.asMany())) {
// create a proxy for the many (deferred fetching)
BeanCollection<?> ref = many.createReference(localBean, forceNewReference);
if (ref != null) {
@@ -38,7 +38,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
final boolean readId;
final boolean readIdNormal;
final boolean disableLazyLoad;
final boolean readOnly;
final boolean unmodifiable;
final InheritInfo inheritInfo;
final String prefix;
final Map<String, String> pathMap;
@@ -92,7 +92,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.readId = !aggregationRoot && withId && desc.hasId();
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = common.disableLazyLoad() || !readIdNormal || desc.isRawSqlBased();
this.readOnly = common.readOnly();
this.unmodifiable = common.unmodifiable();
this.partialObject = props.isPartialObject();
this.properties = props.props();
this.children = myChildren == null ? Collections.emptyList() : myChildren;
@@ -132,7 +132,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private boolean usageProfiling = true;
private CacheMode useBeanCache = CacheMode.AUTO;
private CacheMode useQueryCache = CacheMode.OFF;
private Boolean readOnly;
private boolean unmodifiable;
private PersistenceContextScope persistenceContextScope;
/**
@@ -514,6 +514,10 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
if (!useDocStore) {
createExtraJoinsToSupportManyWhereClause();
}
if (unmodifiable) {
disableLazyLoading = true;
persistenceContextScope = PersistenceContextScope.QUERY;
}
return markQueryJoins();
}
@@ -763,7 +767,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
copy.nativeSql = nativeSql;
copy.useBeanCache = useBeanCache;
copy.useQueryCache = useQueryCache;
copy.readOnly = readOnly;
copy.unmodifiable = unmodifiable;
if (detail != null) {
copy.detail = detail.copy();
}
@@ -1113,7 +1117,9 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
if (allowLoadErrors) {
sb.append("/ae");
}
if (disableLazyLoading) {
if (unmodifiable) {
sb.append("/um");
} else if (disableLazyLoading) {
sb.append("/dl");
}
if (baseTable != null) {
@@ -1277,14 +1283,14 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
}
@Override
public final Boolean isReadOnly() {
return readOnly;
public Query<T> setUnmodifiable(boolean unmodifiable) {
this.unmodifiable = unmodifiable;
return this;
}
@Override
public final Query<T> setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
public boolean isUnmodifiable() {
return unmodifiable;
}
@Override
@@ -1328,6 +1334,9 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final Query<T> setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
if (CacheMode.OFF != useQueryCache) {
unmodifiable = true;
}
return this;
}
@@ -23,16 +23,23 @@ public class RsetDataReader implements DataReader {
static final int clobBufferSize = 512;
static final int stringInitialSize = 512;
private final boolean unmodifiable;
private final DataTimeZone dataTimeZone;
private final ResultSet rset;
protected int pos;
private String json;
public RsetDataReader(DataTimeZone dataTimeZone, ResultSet rset) {
public RsetDataReader(boolean unmodifiable, DataTimeZone dataTimeZone, ResultSet rset) {
this.unmodifiable = unmodifiable;
this.dataTimeZone = dataTimeZone;
this.rset = rset;
}
@Override
public boolean unmodifiable() {
return unmodifiable;
}
@Override
public final void pushJson(String json) {
this.json = json;
@@ -11,8 +11,8 @@ public final class RsetDataReaderIndexed extends RsetDataReader {
private final int[] rsetIndexPositions;
public RsetDataReaderIndexed(DataTimeZone dataTimeZone, ResultSet rset, int[] rsetIndexPositions) {
super(dataTimeZone, rset);
public RsetDataReaderIndexed(boolean unmodifiable, DataTimeZone dataTimeZone, ResultSet rset, int[] rsetIndexPositions) {
super(unmodifiable, dataTimeZone, rset);
this.rsetIndexPositions = rsetIndexPositions;
}
@@ -28,33 +28,33 @@ public class BeanDescriptorTest extends BaseTest {
@Test
public void createReference() {
Customer bean = customerDesc.createReference(null, false, 42, null);
Customer bean = customerDesc.createReference(false, false, 42, null);
assertThat(bean.getId()).isEqualTo(42);
Assertions.assertThat(server().beanState(bean).isReadOnly()).isFalse();
assertThat(server().beanState(bean).isUnmodifiable()).isFalse();
}
@Test
public void createReference_whenReadOnly() {
Customer bean = customerDesc.createReference(Boolean.TRUE, false, 42, null);
Assertions.assertThat(server().beanState(bean).isReadOnly()).isTrue();
Customer bean = customerDesc.createReference(true, false, 42, null);
assertThat(server().beanState(bean).isUnmodifiable()).isTrue();
}
@Test
public void createReference_whenNotReadOnly() {
Customer bean = customerDesc.createReference(Boolean.FALSE, false, 42, null);
Assertions.assertThat(server().beanState(bean).isReadOnly()).isFalse();
Customer bean = customerDesc.createReference(false, false, 42, null);
assertThat(server().beanState(bean).isUnmodifiable()).isFalse();
bean = customerDesc.createReference(42, null);
Assertions.assertThat(server().beanState(bean).isReadOnly()).isFalse();
assertThat(server().beanState(bean).isUnmodifiable()).isFalse();
}
@Test
public void createReference_when_disabledLazyLoad() {
Customer bean = customerDesc.createReference(Boolean.FALSE, true, 42, null);
Assertions.assertThat(server().beanState(bean).isDisableLazyLoad()).isTrue();
Customer bean = customerDesc.createReference(false, true, 42, null);
assertThat(server().beanState(bean).isDisableLazyLoad()).isTrue();
}
@Test
@@ -78,7 +78,7 @@ public class BeanDescriptorTest extends BaseTest {
BeanDescriptor<Animal> animalDesc = spiEbeanServer().descriptor(Animal.class);
Animal bean = animalDesc.createReference(Boolean.FALSE, false, dog.getId(), null);
Animal bean = animalDesc.createReference(false, false, dog.getId(), null);
assertThat(bean.getId()).isEqualTo(dog.getId());
}
@@ -8,6 +8,7 @@ import java.io.*;
import java.util.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;
@@ -241,6 +242,36 @@ public class ModifyAwareListTest {
assertTrue(set.isMarkedDirty());
}
@Test
void freeze() {
ModifyAwareList<String> orig = createList();
List<String> frozen = orig.freeze();
assertThatThrownBy(() -> frozen.add("junk"))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
public void freezeAndSerialise() throws IOException, ClassNotFoundException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
List<String> orig = createList().freeze();
oos.writeObject(orig);
oos.flush();
oos.close();
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
@SuppressWarnings("unchecked")
List<String> read = (List<String>)ois.readObject();
assertThat(read).contains("A", "B", "C", "D", "E");
assertThatThrownBy(() -> read.add("junk"))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
public void serialise() throws IOException, ClassNotFoundException {
@@ -5,8 +5,11 @@ import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class ModifyAwareMapTest {
@@ -25,6 +28,13 @@ public class ModifyAwareMapTest {
return new ModifyAwareMap<>(set);
}
@Test
void freeze() {
Map<String, Integer> frozen = createMap().freeze();
assertThatThrownBy(() -> frozen.put("junk", 1))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
public void serialise() throws IOException, ClassNotFoundException {
@@ -6,8 +6,10 @@ import org.junit.jupiter.api.Test;
import java.io.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class ModifyAwareSetTest {
@@ -22,6 +24,13 @@ public class ModifyAwareSetTest {
return new ModifyAwareSet<>(set);
}
@Test
void freeze() {
Set<String> frozen = createSet().freeze();
assertThatThrownBy(() -> frozen.add("junk"))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
public void serialise() throws IOException, ClassNotFoundException {
@@ -494,8 +494,8 @@ public abstract class QueryBean<T, R extends QueryBean<T, R>> implements IQueryB
}
@Override
public final R setReadOnly(boolean readOnly) {
query.setReadOnly(readOnly);
public R setUnmodifiable(boolean unmodifiable) {
query.setUnmodifiable(unmodifiable);
return root;
}
@@ -1,10 +1,7 @@
package io.ebean.xtest.config;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.Transaction;
import io.ebean.*;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DbIdentity;
import io.ebean.config.dbplatform.IdType;
@@ -15,6 +12,7 @@ import org.tests.model.basic.EBasicVer;
import org.tests.model.draftable.BasicDraftableBean;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class PlatformNoGeneratedKeysTest {
@@ -39,7 +37,7 @@ public class PlatformNoGeneratedKeysTest {
.findOne();
assertThat(found.getName()).isEqualTo("basic");
assertThat(found.getDescription()).isNull();
assertThrows(LazyInitialisationException.class, found::getDescription);
}
@Test
@@ -12,6 +12,7 @@ import org.tests.model.basic.ResetBasicData;
import java.util.Map;
import java.util.Set;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestLazyLoadInCache extends BaseTestCase {
@@ -22,9 +23,9 @@ public class TestLazyLoadInCache extends BaseTestCase {
ResetBasicData.reset();
Map<Integer, Customer> map = DB.find(Customer.class)
.select("id, name")
.select("id, name, status, billingAddress")
.setBeanCacheMode(CacheMode.PUT)
.setReadOnly(true)
.setUnmodifiable(true)
.orderBy().asc("id")
.findMap();
@@ -35,7 +36,8 @@ public class TestLazyLoadInCache extends BaseTestCase {
Customer cust1 = map.get(id);
Customer cust1B = DB.find(Customer.class)
.setReadOnly(true)
.setUnmodifiable(true)
.setUseCache(false)
.setId(id)
.findOne();
@@ -43,20 +45,19 @@ public class TestLazyLoadInCache extends BaseTestCase {
Set<String> loadedProps = DB.beanState(cust1).loadedProps();
assertTrue(loadedProps.contains("name"));
assertFalse(loadedProps.contains("status"));
cust1.getStatus();
assertThat(loadedProps).contains("id", "name", "status");
// cust1.getStatus(); // can't lazy load with unmodifiable
assertThat(DB.beanState(cust1).isUnmodifiable()).isTrue();
// a readOnly reference
Address billingAddress = cust1.getBillingAddress();
BeanState billAddrState = DB.beanState(billingAddress);
assertTrue(billAddrState.isReference());
assertTrue(billAddrState.isReadOnly());
// assertTrue(billAddrState.isReference()); // not supported by unmodifiable
assertTrue(billAddrState.isUnmodifiable());
// lazy load .. no longer a reference
billingAddress.getCity();
assertFalse(billAddrState.isReference());
// billingAddress.getCity(); // lazy loading not supported by unmodifiable
// assertFalse(billAddrState.isReference());
}
@@ -35,14 +35,14 @@ class TestLoadBeanCache extends BaseTestCase {
Map<String, Country> map = DB.find(Country.class)
.setBeanCacheMode(CacheMode.PUT)
.setUseQueryCache(true)
.setReadOnly(true)
.setUnmodifiable(true)
.orderBy("name")
.findMap();
Country loadedNz = map.get("NZ");
// this will hit the cache
Country nz = DB.find(Country.class, "NZ");
// this will hit the cache, with setUnmodifiable(true) we can use shared bean instances
Country nz = DB.find(Country.class).setId("NZ").setUnmodifiable(true).findOne();
assertSame(loadedNz, nz);
}
@@ -12,12 +12,13 @@ import org.junit.jupiter.api.Test;
import org.tests.model.basic.Country;
import org.tests.model.basic.ResetBasicData;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
public class TestQueryWithCache extends BaseTestCase {
class TestQueryWithCache extends BaseTestCase {
@Test
public void testCountryDeploy() {
void testCountryDeploy() {
ResetBasicData.reset();
@@ -50,15 +51,13 @@ public class TestQueryWithCache extends BaseTestCase {
Country nz4 = DB.find(Country.class).setId("NZ").setAutoTune(false).setUseCache(false)
.findOne();
assertTrue(nz2 == nz2b);
assertTrue(nz2 == nz3);
assertTrue(nz3 != nz4);
assertThat(nz2).isNotSameAs(nz2b); // Changed behaviour with unmodifiable
assertThat(nz2).isNotSameAs(nz3); // Changed behaviour with unmodifiable
assertThat(nz3).isNotSameAs(nz4);
}
@Test
public void testSkipCache() {
void testSkipCache() {
ResetBasicData.reset();
DB.find(Country.class, "NZ");
@@ -2,7 +2,6 @@ package org.tests.basic;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.bean.BeanCollection;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.*;
@@ -10,80 +9,53 @@ import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.*;
public class TestReadOnlyPropagation extends BaseTestCase {
class TestReadOnlyPropagation extends BaseTestCase {
@Test
public void testReadOnly() {
void testReadOnly() {
ResetBasicData.reset();
DB.cacheManager().clearAll();
Order order = DB.find(Order.class)
.setReadOnly(true)
.setUnmodifiable(true)
.fetch("customer")
.fetch("customer.billingAddress")
.fetch("details")
.setId(1)
.findOne();
assertTrue(DB.beanState(order).isReadOnly());
assertTrue(DB.beanState(order).isUnmodifiable());
Customer customer = order.getCustomer();
assertTrue(DB.beanState(customer).isReadOnly());
assertTrue(DB.beanState(customer).isUnmodifiable());
Address billingAddress = customer.getBillingAddress();
assertNotNull(billingAddress);
assertTrue(DB.beanState(billingAddress).isReadOnly());
assertTrue(DB.beanState(billingAddress).isUnmodifiable());
List<OrderDetail> details = order.getDetails();
BeanCollection<?> bc = (BeanCollection<?>) details;
assertTrue(bc.isReadOnly());
assertTrue(!bc.isPopulated());
bc.size();
assertTrue(!bc.isEmpty());
assertTrue(bc.isReadOnly());
assertTrue(bc.isPopulated());
try {
details.add(new OrderDetail());
assertTrue(false);
} catch (IllegalStateException e) {
assertTrue(true);
}
try {
details.remove(0);
assertTrue(false);
} catch (IllegalStateException e) {
assertTrue(true);
}
try {
Iterator<OrderDetail> it = details.iterator();
it.next();
it.remove();
assertTrue(false);
} catch (IllegalStateException e) {
assertTrue(true);
}
try {
ListIterator<OrderDetail> it = details.listIterator();
it.next();
it.remove();
assertTrue(false);
} catch (IllegalStateException e) {
assertTrue(true);
}
try {
List<OrderDetail> subList = details.subList(0, 1);
subList.remove(0);
assertTrue(false);
} catch (UnsupportedOperationException e) {
assertTrue(true);
}
assertThrows(UnsupportedOperationException.class, details::clear);
assertThatThrownBy(() -> details.add(new OrderDetail())).isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> details.remove(0)).isInstanceOf(UnsupportedOperationException.class);
Iterator<OrderDetail> it = details.iterator();
it.next();
assertThatThrownBy(it::remove).isInstanceOf(UnsupportedOperationException.class);
ListIterator<OrderDetail> it2 = details.listIterator();
it2.next();
assertThatThrownBy(it2::remove).isInstanceOf(UnsupportedOperationException.class);
List<OrderDetail> subList = details.subList(0, 1);
assertThatThrownBy(() -> subList.remove(0)).isInstanceOf(UnsupportedOperationException.class);
}
}
@@ -2,7 +2,6 @@ package org.tests.basic;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.bean.BeanCollection;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Order;
import org.tests.model.basic.OrderDetail;
@@ -28,32 +27,27 @@ public class TestSharedInstancePropagation extends BaseTestCase {
Order order = DB.find(Order.class)
.setAutoTune(false)
.setReadOnly(true)
.setUnmodifiable(true)
.fetch("details")
.fetch("details.product", "name")
.setId(1)
.findOne();
assertNotNull(order);
assertTrue(DB.beanState(order).isReadOnly());
assertTrue(DB.beanState(order).isUnmodifiable());
List<OrderDetail> details = order.getDetails();
BeanCollection<?> bc = (BeanCollection<?>) details;
assertTrue(bc.isReadOnly());
assertFalse(bc.isPopulated());
assertThrows(UnsupportedOperationException.class, details::clear);
// lazy load
bc.size();
assertTrue(bc.isPopulated());
assertTrue(!bc.isEmpty());
OrderDetail detail = details.get(0);
assertTrue(DB.beanState(detail).isReadOnly());
assertTrue(DB.beanState(detail).isUnmodifiable());
assertFalse(DB.beanState(detail).isReference());
Product product = detail.getProduct();
assertTrue(DB.beanState(product).isReadOnly());
assertTrue(DB.beanState(product).isUnmodifiable());
// lazy load
product.getName();
@@ -2,6 +2,7 @@ package org.tests.batchload;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.LazyInitialisationException;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.xtest.BaseTestCase;
@@ -82,7 +83,7 @@ class TestBeanState extends BaseTestCase {
BeanState beanState = DB.beanState(customer);
beanState.setDisableLazyLoad(true);
assertNull(customer.getName());
assertThrows(LazyInitialisationException.class, customer::getName);
}
@Test
@@ -116,29 +117,4 @@ class TestBeanState extends BaseTestCase {
assertThat(beanState.changedProps()).containsOnly("contacts");
}
@Test
void readOnly_when_setManyProperty() {
Customer customer = new Customer();
customer.setContacts(new ArrayList<>());
BeanState beanState = DB.beanState(customer);
beanState.setLoaded();
beanState.setReadOnly(true);
// act, try to mutate read only bean
assertThrows(IllegalStateException.class, () -> customer.setContacts(new ArrayList<>()));
}
@Test
void readOnly_when_setProperty() {
Customer customer = new Customer();
customer.setName("a");
BeanState beanState = DB.beanState(customer);
beanState.setLoaded();
beanState.setReadOnly(true);
// act, try to mutate read only bean
assertThrows(IllegalStateException.class, () -> customer.setName("b"));
}
}
@@ -1,18 +1,18 @@
package org.tests.batchload;
import io.ebean.LazyInitialisationException;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Order;
import org.tests.model.basic.OrderDetail;
import org.tests.model.basic.ResetBasicData;
import java.sql.Date;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.*;
public class TestQueryDisableLazyLoad extends BaseTestCase {
@@ -32,8 +32,7 @@ public class TestQueryDisableLazyLoad extends BaseTestCase {
Order order = l0.get(0);
List<OrderDetail> details = order.getDetails();
assertEquals(details.size(), 0);
assertThrows(LazyInitialisationException.class, order::getDetails);
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(1);
@@ -59,7 +58,7 @@ public class TestQueryDisableLazyLoad extends BaseTestCase {
Order order = l0.get(0);
// normally invokes lazy loading
assertNull(order.getCustomer().getStatus());
assertThrows(LazyInitialisationException.class, () -> order.getCustomer().getStatus());
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(1);
@@ -83,7 +82,29 @@ public class TestQueryDisableLazyLoad extends BaseTestCase {
Order order = l0.get(0);
// normally invokes lazy loading
assertNull(order.getCustomer().getStatus());
assertThrows(LazyInitialisationException.class, () -> order.getCustomer().getStatus());
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(1);
}
@Test
public void onSetter_expect_LazyInitialisationException() {
ResetBasicData.reset();
LoggedSql.start();
List<Order> l0 = DB.find(Order.class)
.setDisableLazyLoading(true)
.select("status, orderDate")
.orderBy().asc("id")
.findList();
assertThat(l0).isNotEmpty();
Order order = l0.get(0);
// normally invokes lazy loading
assertThrows(LazyInitialisationException.class, () -> order.setShipDate(new Date(System.currentTimeMillis())));
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(1);
@@ -1,5 +1,6 @@
package org.tests.batchload;
import io.ebean.LazyInitialisationException;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
@@ -16,6 +17,7 @@ import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class TestQueryJoinToAssocOne extends BaseTestCase {
@@ -91,15 +93,15 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
Order order = l0.get(0);
// normally invokes lazy loading
order.getOrderDate();
assertThrows(LazyInitialisationException.class, order::getOrderDate);
List<OrderDetail> details = order.getDetails();
OrderDetail orderDetail = details.get(0);
// normally invokes lazy loading
orderDetail.getShipQty();
assertThrows(LazyInitialisationException.class, orderDetail::getShipQty);
// normally invokes lazy loading
order.getShipments().size();
assertThrows(LazyInitialisationException.class, order::getShipments);
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(2);
@@ -129,13 +131,11 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
Order order = l0.get(0);
// try to invoke lazy loading on the bean
assertThat(order.getCustomer()).isNull();
assertThat(order.getCretime()).isNull();
assertThrows(LazyInitialisationException.class, order::getCustomer);
assertThrows(LazyInitialisationException.class, order::getCretime);
// try to invoke lazy loading on the OneToMany ...
List<OrderDetail> details = order.getDetails();
assertThat(details).isEmpty();
assertThat(details.size()).isEqualTo(0);
assertThrows(LazyInitialisationException.class, order::getDetails);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
@@ -163,7 +163,7 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
.setId(tenant.getId())
.findOne();
assertThat(found.getRoles().size()).isEqualTo(0);
assertThrows(LazyInitialisationException.class, found::getRoles);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
@@ -189,7 +189,7 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
.findOne();
// normally invokes lazy loading
assertThat(found.getRoles().size()).isEqualTo(0);
assertThrows(LazyInitialisationException.class, found::getRoles);
// only 1 query ... no lazy loading query
List<String> sql = LoggedSql.stop();
@@ -217,12 +217,12 @@ public class TestQueryJoinToAssocOne extends BaseTestCase {
Order order = l0.get(0);
// normally invokes lazy loading
order.getOrderDate();
assertThrows(LazyInitialisationException.class, order::getOrderDate);
List<OrderDetail> details = order.getDetails();
OrderDetail orderDetail = details.get(0);
// normally invokes lazy loading
orderDetail.getShipQty();
assertThrows(LazyInitialisationException.class, orderDetail::getShipQty);
List<String> loggedSql = LoggedSql.stop();
assertThat(loggedSql).hasSize(1);
+21 -10
View File
@@ -3,6 +3,7 @@ package org.tests.cache;
import io.ebean.DB;
import io.ebean.Query;
import io.ebean.Transaction;
import io.ebean.UnmodifiableEntityException;
import io.ebean.cache.ServerCache;
import io.ebean.cache.ServerCacheStatistics;
import io.ebean.test.LoggedSql;
@@ -19,7 +20,7 @@ import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/**
@@ -50,7 +51,7 @@ public class TestBeanCache extends BaseTestCase {
// expect to hit the cache, no SQL
LoggedSql.start();
OCachedBean bean2 = DB.find(OCachedBean.class).setReadOnly(true).setId(String.valueOf(bean.getId())).findOne();
OCachedBean bean2 = DB.find(OCachedBean.class).setUnmodifiable(true).setId(String.valueOf(bean.getId())).findOne();
sql = LoggedSql.stop();
assertNotNull(bean2);
assertThat(sql).isEmpty();
@@ -138,12 +139,15 @@ public class TestBeanCache extends BaseTestCase {
LoggedSql.start();
log.info("All misses (0 of 3) ...");
List<OCachedBean> list = DB.find(OCachedBean.class)
final List<OCachedBean> list1 = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.setUnmodifiable(true)
.findList();
assertThat(list).hasSize(3);
assertThat(list1).hasSize(3);
assertThatThrownBy(() -> list1.get(0).setName("junk")).isInstanceOf(UnmodifiableEntityException.class);
assertBeanCacheHitMiss(0, 3);
List<String> sql = LoggedSql.collect();
assertThat(sql).hasSize(1);
@@ -152,13 +156,17 @@ public class TestBeanCache extends BaseTestCase {
}
log.info("All hits (3 of 3) ...");
list = DB.find(OCachedBean.class)
List<OCachedBean> list2 = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.setUnmodifiable(true)
.findList();
assertBeanCacheHitMiss(3, 0);
assertThat(list).hasSize(3);
assertThat(list2).hasSize(3);
assertThatThrownBy(() -> list2.add(new OCachedBean())).isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> list2.get(0).setName("junk")).isInstanceOf(UnmodifiableEntityException.class);
sql = LoggedSql.collect();
assertThat(sql).hasSize(0); // no misses
@@ -166,13 +174,16 @@ public class TestBeanCache extends BaseTestCase {
beanCache.remove(beans.get(0).getId().toString());
log.info("Partial hits (2 of 3) ...");
list = DB.find(OCachedBean.class)
List<OCachedBean> list3 = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertBeanCacheHitMiss(2, 1);
assertThat(list).hasSize(3);
assertThat(list3).hasSize(3);
list3.get(0).setName("junk"); // we can mutate the beans
list3.clear(); // we can mutate the list
sql = LoggedSql.collect();
assertThat(sql).hasSize(1);
if (isH2()) {
@@ -185,13 +196,13 @@ public class TestBeanCache extends BaseTestCase {
beanCache.remove(beans.get(2).getId().toString());
log.info("Partial hits (1 of 3) ...");
list = DB.find(OCachedBean.class)
List<OCachedBean> list4 = DB.find(OCachedBean.class)
.where().idIn(ids)
.setUseCache(true)
.findList();
assertBeanCacheHitMiss(1, 2);
assertThat(list).hasSize(3);
assertThat(list4).hasSize(3);
sql = LoggedSql.stop();
assertThat(sql).hasSize(1);
if (isH2()) {
@@ -40,7 +40,7 @@ public class TestBeanCacheWithDeleteQuery extends BaseTestCase {
// expect not to hit the cache, expect SQL
LoggedSql.start();
OCachedBean bean2 = DB.find(OCachedBean.class).setReadOnly(true).setId(String.valueOf(bean.getId())).findOne();
OCachedBean bean2 = DB.find(OCachedBean.class).setUnmodifiable(true).setId(String.valueOf(bean.getId())).findOne();
sql = LoggedSql.stop();
assertNull(bean2);
assertThat(sql).isNotEmpty();
@@ -70,7 +70,7 @@ public class TestBeanCacheWithDeleteQuery extends BaseTestCase {
// expect not to hit the cache, expect SQL
LoggedSql.start();
OCachedBean bean2 = DB.find(OCachedBean.class).setReadOnly(true).setId(String.valueOf(bean.getId())).findOne();
OCachedBean bean2 = DB.find(OCachedBean.class).setUnmodifiable(true).setId(String.valueOf(bean.getId())).findOne();
sql = LoggedSql.stop();
assertNotNull(bean2);
assertThat(sql).isNotEmpty();
@@ -29,7 +29,7 @@ public class TestCacheBasic extends BaseTestCase {
Country c0 = DB.reference(Country.class, "NZ");
ServerCacheStatistics statistics = countryCache.statistics(false);
long hc = statistics.getHitCount();
assertEquals(1, hc);
assertEquals(0, hc); // Change behaviour, reference() no longer hits cache with unmodifiable
assertNotNull(c0);
// Country c1 = DB.reference(Country.class, "NZ");
@@ -4,58 +4,45 @@ import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
import io.ebean.cache.ServerCache;
import io.ebeaninternal.server.querydefn.OrmQueryDetail;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.FeatureDescription;
import static org.junit.jupiter.api.Assertions.*;
public class TestL2CacheWithSharedBean extends BaseTestCase {
// private TunedQueryInfo createTunedQueryInfo(OrmQueryDetail tunedDetail) {
// Origin origin = new Origin();
// origin.setDetail(tunedDetail.toString());
// return new TunedQueryInfo(origin);
// }
class TestL2CacheWithSharedBean extends BaseTestCase {
@Test
public void test() {
void test() {
FeatureDescription f1 = new FeatureDescription();
f1.setName("one");
f1.setDescription("helloOne");
DB.save(f1);
ServerCache beanCache = DB.cacheManager().beanCache(FeatureDescription.class);
beanCache.statistics(true);
OrmQueryDetail tunedDetail = new OrmQueryDetail();
tunedDetail.select("name");
// TunedQueryInfo tunedInfo = createTunedQueryInfo(tunedDetail);
Query<FeatureDescription> query = DB.find(FeatureDescription.class).setId(f1.getId());
// tunedInfo.tuneQuery((SpiQuery<?>) query);
query.findOne(); // PUT into cache
FeatureDescription fd2 = query.findOne(); // LOAD cache
FeatureDescription fd2 = query.findOne(); // LOAD from cache
assertEquals("helloOne", fd2.getDescription());
String description0 = fd2.getDescription(); // invoke lazy load
assertEquals("helloOne", description0);
// load the cache
FeatureDescription fetchOne = DB.find(FeatureDescription.class, f1.getId());
// load from cache
FeatureDescription fetchOne = findByIdUnmodifiable(f1.getId());
assertNotNull(fetchOne);
assertEquals(1, beanCache.statistics(false).getSize());
FeatureDescription fetchTwo = DB.find(FeatureDescription.class, f1.getId());
FeatureDescription fetchThree = DB.find(FeatureDescription.class, f1.getId());
FeatureDescription fetchTwo = findByIdUnmodifiable(f1.getId());
FeatureDescription fetchThree = findByIdUnmodifiable(f1.getId());
assertSame(fetchTwo, fetchThree);
assertEquals("helloOne", fetchThree.getDescription());
}
String description1 = fetchThree.getDescription();
assertEquals("helloOne", description1);
private static FeatureDescription findByIdUnmodifiable(Integer id) {
return DB.find(FeatureDescription.class)
.setId(id)
.setUnmodifiable(true) // with this true, we can return shared bean instances
.findOne();
}
}
+19 -27
View File
@@ -5,7 +5,6 @@ import io.ebean.DB;
import io.ebean.ExpressionList;
import io.ebean.annotation.Transactional;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.BeanCollection;
import io.ebean.cache.ServerCache;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
@@ -304,46 +303,39 @@ public class TestQueryCache extends BaseTestCase {
@Test
@SuppressWarnings("unchecked")
public void testReadOnlyFind() {
void testReadOnlyFind() {
ResetBasicData.reset();
ServerCache customerCache = DB.cacheManager().queryCache(Customer.class);
customerCache.clear();
List<Customer> list = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
.ilike("name", "Rob").findList();
List<Customer> list = DB.find(Customer.class).setUnmodifiable(true) //.setUseQueryCache(true)
.where().ilike("name", "Rob")
.findList();
BeanCollection<Customer> bc = (BeanCollection<Customer>) list;
assertTrue(bc.isReadOnly());
assertFalse(bc.isEmpty());
assertTrue(!list.isEmpty());
assertTrue(DB.beanState(list.get(0)).isReadOnly());
List<Customer> list2 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true).where()
.ilike("name", "Rob").findList();
assertThat(list).isNotEmpty();
assertThat(DB.beanState(list.get(0)).isUnmodifiable()).isTrue();
List<Customer> list2 = DB.find(Customer.class).setUseQueryCache(true)
.where().ilike("name", "Rob")
.findList();
List<Customer> list2B = DB.find(Customer.class).setUseQueryCache(true)
// .setReadOnly(true)
.where().ilike("name", "Rob").findList();
.where().ilike("name", "Rob")
.findList();
assertSame(list, list2);
assertThat(list2).isEqualTo(list);
// readOnly defaults to true for query cache
assertSame(list, list2B);
assertSame(list2, list2B);
List<Customer> list3 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(false).where()
.ilike("name", "Rob").findList();
assertNotSame(list, list3);
BeanCollection<Customer> bc3 = (BeanCollection<Customer>) list3;
assertFalse(bc3.isReadOnly());
assertFalse(bc3.isEmpty());
assertTrue(list3.size() > 0);
// TODO: At this stage setReadOnly(false) does create a shallow copy of the List/Set/Map, but does not
// change the read only state in the entities.
// assertFalse(DB.beanState(list3.get(0)).isReadOnly());
List<Customer> list3 = DB.find(Customer.class).setUseQueryCache(true)
.where().ilike("name", "Rob")
.findList();
assertSame(list2, list3);
assertThat(list3).isNotEmpty();
}
@Test
@@ -25,24 +25,24 @@ public class TestQueryCacheReadOnly extends BaseTestCase {
List<EBasicVer> alist = baseQuery.findList();
assertThat(alist).isNotEmpty();
assertThatThrownBy(alist::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(alist::clear).isInstanceOf(UnsupportedOperationException.class);
alist = baseQuery.findList();
assertThat(alist).isNotEmpty();
assertThatThrownBy(alist::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(alist::clear).isInstanceOf(UnsupportedOperationException.class);
Map<String,EBasicVer> amap = baseQuery.setMapKey("name").findMap();
assertThat(amap).isNotEmpty();
assertThatThrownBy(amap::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(amap::clear).isInstanceOf(UnsupportedOperationException.class);
amap = baseQuery.setMapKey("name").findMap();
assertThat(amap).isNotEmpty();
assertThatThrownBy(amap::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(amap::clear).isInstanceOf(UnsupportedOperationException.class);
Set<EBasicVer> aset = baseQuery.findSet();
assertThat(aset).isNotEmpty();
assertThatThrownBy(aset::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(aset::clear).isInstanceOf(UnsupportedOperationException.class);
aset = baseQuery.findSet();
assertThat(aset).isNotEmpty();
assertThatThrownBy(aset::clear).hasMessageContaining("This collection is in ReadOnly mode");
assertThatThrownBy(aset::clear).isInstanceOf(UnsupportedOperationException.class);
// we will get an unmodifiable collection here
List<Object> attributeList = baseQuery.select("name").findSingleAttributeList();
@@ -68,41 +68,41 @@ public class TestQueryCacheReadOnly extends BaseTestCase {
EBasicVer account = new EBasicVer("an other junk");
server.save(account);
Query<EBasicVer> baseQuery = server.find(EBasicVer.class).setUseQueryCache(CacheMode.ON).setReadOnly(false);
Query<EBasicVer> baseQuery = server.find(EBasicVer.class).setUseQueryCache(CacheMode.ON);
List<EBasicVer> alist = baseQuery.findList();
assertThat(alist).isNotEmpty();
alist.clear();
assertThatThrownBy(alist::clear).isInstanceOf(UnsupportedOperationException.class);
alist = baseQuery.findList();
assertThat(alist).isNotEmpty();
alist.clear();
assertThatThrownBy(alist::clear).isInstanceOf(UnsupportedOperationException.class);
Map<String,EBasicVer> amap = baseQuery.setMapKey("name").findMap();
assertThat(amap).isNotEmpty();
amap.clear();
assertThatThrownBy(amap::clear).isInstanceOf(UnsupportedOperationException.class);
amap = baseQuery.setMapKey("name").findMap();
assertThat(amap).isNotEmpty();
amap.clear();
assertThatThrownBy(amap::clear).isInstanceOf(UnsupportedOperationException.class);
Set<EBasicVer> aset = baseQuery.findSet();
assertThat(aset).isNotEmpty();
aset.clear();
assertThatThrownBy(aset::clear).isInstanceOf(UnsupportedOperationException.class);
aset = baseQuery.findSet();
assertThat(aset).isNotEmpty();
aset.clear();
assertThatThrownBy(aset::clear).isInstanceOf(UnsupportedOperationException.class);
List<Object> attributeList = baseQuery.select("name").findSingleAttributeList();
final List<Object> attributeList = baseQuery.select("name").findSingleAttributeList();
assertThat(attributeList).isNotEmpty();
attributeList.clear();
attributeList = baseQuery.select("name").findSingleAttributeList();
assertThat(attributeList).isNotEmpty();
attributeList.clear();
assertThatThrownBy(attributeList::clear).isInstanceOf(UnsupportedOperationException.class);
final List<Object> attributeList2 = baseQuery.select("name").findSingleAttributeList();
assertThat(attributeList2).isNotEmpty();
assertThatThrownBy(attributeList2::clear).isInstanceOf(UnsupportedOperationException.class);
List<Object> idList = baseQuery.select("name").findIds();
assertThat(idList).isNotEmpty();
idList.clear();
idList = baseQuery.select("name").findIds();
assertThat(idList).isNotEmpty();
idList.clear();
assertThatThrownBy(idList::clear).isInstanceOf(UnsupportedOperationException.class);
List<Object> idList2 = baseQuery.select("name").findIds();
assertThat(idList2).isNotEmpty();
assertThatThrownBy(idList2::clear).isInstanceOf(UnsupportedOperationException.class);
}
}
@@ -34,13 +34,13 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
.where().eq("line2", "St Lukes")
.findList();
int custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
int custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lukes")
.findCount();
assertThat(custs).isEqualTo(3);
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lukes")
.findCount();
@@ -50,14 +50,14 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
a1.setLine2("St Lucky");
DB.save(a1);
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lukes")
.findCount();
assertThat(custs).isEqualTo(2);
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lucky")
.findCount();
@@ -68,13 +68,13 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
.where().eq("line2", "St Lucky")
.update();
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lucky")
.findCount();
assertThat(custs).isEqualTo(0);
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lucky2")
.findCount();
assertThat(custs).isEqualTo(1);
@@ -83,12 +83,12 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
.setParameters("St Lucky3", "St Lucky2")
.execute();
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lucky2")
.findCount();
assertThat(custs).isEqualTo(0);
custs = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
custs = DB.find(Customer.class).setUseQueryCache(true)
.where().eq("billingAddress.line2", "St Lucky3")
.findCount();
@@ -101,7 +101,7 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
Customer fi = DB.find(Customer.class).where().eq("name", "Fiona").findOne();
int custCount0 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
int custCount0 = DB.find(Customer.class).setUseQueryCache(true)
.where()
.eq("name", "Fiona")
.isNull("contacts.phone")
@@ -117,7 +117,7 @@ public class TestQueryCacheTableDependency extends BaseTestCase {
assertThat(updateRows).isGreaterThan(0);
int custCount1 = DB.find(Customer.class).setUseQueryCache(true).setReadOnly(true)
int custCount1 = DB.find(Customer.class).setUseQueryCache(true)
.where()
.eq("name", "Fiona")
.isNull("contacts.phone")
@@ -1,5 +1,6 @@
package org.tests.compositekeys;
import io.ebean.UnmodifiableEntityException;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import org.junit.jupiter.api.Test;
@@ -9,6 +10,7 @@ import org.tests.model.basic.CKeyParentId;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
@@ -51,6 +53,14 @@ public class TestCKeyDelete extends BaseTestCase {
List<CKeyParentId> ids = DB.find(CKeyParent.class).where().eq("id.oneKey", 101).findIds();
assertThat(ids).hasSize(1);
List<CKeyParentId> idsUnmodifiable = DB.find(CKeyParent.class)
.setUnmodifiable(true)
.where().eq("id.oneKey", 101)
.findIds();
assertThat(idsUnmodifiable).hasSize(1);
assertThatThrownBy(() -> idsUnmodifiable.get(0).setOneKey(7))
.isInstanceOf(UnmodifiableEntityException.class);
CKeyParentId foundId = ids.get(0);
assertThat(foundId.getOneKey()).isEqualTo(101);
assertThat(foundId.getTwoKey()).isEqualTo("deleteMe2");
@@ -1,5 +1,6 @@
package org.tests.model.aggregation;
import io.ebean.LazyInitialisationException;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
@@ -8,6 +9,7 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class TestAggregationMany extends BaseTestCase {
@@ -26,7 +28,7 @@ public class TestAggregationMany extends BaseTestCase {
for (DMachine machine : machines) {
assertThat(machine.getAuxUseAggs()).isNotEmpty();
assertThat(machine.getMachineStats()).isEmpty();
assertThrows(LazyInitialisationException.class, machine::getMachineStats);
}
List<String> sql = LoggedSql.stop();
@@ -57,7 +59,7 @@ public class TestAggregationMany extends BaseTestCase {
for (DMachine machine : machines) {
assertThat(machine.getAuxUseAggs()).isNotEmpty();
assertThat(machine.getMachineStats()).isEmpty();
assertThrows(LazyInitialisationException.class, machine::getMachineStats);
}
List<String> sql = LoggedSql.stop();
@@ -83,7 +85,7 @@ public class TestAggregationMany extends BaseTestCase {
for (DMachine machine : machines) {
assertThat(machine.getAuxUseAggs()).isNotEmpty();
System.out.println(machine);
assertThat(machine.getMachineStats()).isEmpty();
assertThrows(LazyInitialisationException.class, machine::getMachineStats);
}
List<String> sql = LoggedSql.stop();
@@ -1,5 +1,6 @@
package org.tests.model.composite;
import io.ebean.UnmodifiableEntityException;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
@@ -8,11 +9,12 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class TestCompositeKeyUserClient extends BaseTestCase {
class TestCompositeKeyUserClient extends BaseTestCase {
@Test
public void test() {
void test() {
CkeUser user0 = new CkeUser();
user0.setUserPK(new CkeUserKey(20, "sally"));
@@ -29,6 +31,19 @@ public class TestCompositeKeyUserClient extends BaseTestCase {
client.setClientPK(new CkeClientKey(20, "susan"));
client.setUser(user1);
List<CkeUser> unmodifiable = DB.find(CkeUser.class)
.setUnmodifiable(true)
.where().eq("userPK.codCompany", 20)
.findList();
assertThat(unmodifiable).hasSize(2);
for (CkeUser ckeUser : unmodifiable) {
CkeUserKey userPK = ckeUser.getUserPK();
assertThatThrownBy(() -> userPK.setCodCompany(7))
.describedAs("EmbeddedId is unmodifiable")
.isInstanceOf(UnmodifiableEntityException.class);
}
LoggedSql.start();
DB.save(client);
@@ -22,27 +22,30 @@ public class TestQueryFindReadOnly extends BaseTestCase {
DB.save(a0);
Article ar1 = DB.find(Article.class).setReadOnly(true).setId(a0.getId()).findOne();
Article ar1 = DB.find(Article.class)
.setUnmodifiable(true)
.fetch("sections")
.setId(a0.getId()).findOne();
assertNotNull(ar1);
assertTrue(DB.beanState(ar1).isReadOnly());
assertTrue(DB.beanState(ar1).isUnmodifiable());
List<Section> ar1sections = ar1.getSections();
assertEquals(1, ar1sections.size());
Section s2 = ar1sections.get(0);
assertTrue(DB.beanState(s2).isReadOnly());
assertTrue(DB.beanState(s2).isUnmodifiable());
DB.find(Article.class).setBeanCacheMode(CacheMode.PUT).findList();
Article ar0 = DB.find(Article.class, a0.getId());
assertNotNull(ar0);
assertFalse(DB.beanState(ar0).isReadOnly());
assertFalse(DB.beanState(ar0).isUnmodifiable());
List<Section> ar0sections = ar0.getSections();
Section s1 = ar0sections.get(0);
assertFalse(DB.beanState(s1).isReadOnly());
assertFalse(DB.beanState(s1).isUnmodifiable());
}
@@ -1,24 +1,182 @@
package org.tests.query;
import io.ebean.*;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
import io.ebean.test.LoggedSql;
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.util.Collections;
import java.io.*;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
public class TestQueryOrderById extends BaseTestCase {
class TestQueryOrderById extends BaseTestCase {
@Test
public void orderById_default_expectNotOrderById() {
void unmodifiableFindOne() {
UTMaster newBean = new UTMaster("master");
newBean.addDetail(new UTDetail("d0", 4, 10D));
DB.save(newBean);
UTMaster one = DB.find(UTMaster.class)
.setId(newBean.getId())
.fetch("details")
.setUnmodifiable(true)
.findOne();
assertThatThrownBy(() -> one.setName("junk"))
.isInstanceOf(UnmodifiableEntityException.class)
.hasMessageContaining("Attempting to modify name");
List<UTDetail> details = one.getDetails();
assertThat(details).hasSize(1);
assertThatThrownBy(() -> details.add(new UTDetail()))
.isInstanceOf(UnsupportedOperationException.class);
UTDetail utDetail = details.get(0);
assertThatThrownBy(() -> utDetail.setQty(34))
.isInstanceOf(UnmodifiableEntityException.class)
.hasMessageContaining("Attempting to modify qty");
DB.delete(one);
}
@Test
void unmodifiableFindIteratorSimple() {
unmodifiableFindIterator(1);
}
@Test
void unmodifiableFindIteratorBatched() {
unmodifiableFindIterator(100);
}
void unmodifiableFindIterator(int batchSize) {
UTMaster newBean = new UTMaster("unmodifiableFindIterator");
DB.save(newBean);
try (QueryIterator<UTMaster> iterate = DB.find(UTMaster.class)
.setUnmodifiable(true)
.setLazyLoadBatchSize(batchSize)
.where().eq("name", "unmodifiableFindIterator")
.findIterate()) {
while (iterate.hasNext()) {
UTMaster bean = iterate.next();
assertThatThrownBy(() -> bean.setName("junk"))
.isInstanceOf(UnmodifiableEntityException.class)
.hasMessageContaining("Attempting to modify name");
}
}
DB.delete(newBean);
}
@Test
void unmodifiableFindMany() throws IOException, ClassNotFoundException {
ResetBasicData.reset();
LoggedSql.start();
List<Customer> result = DB.find(Customer.class)
.setUnmodifiable(true)
.select("id,name")
.fetch("contacts")
.fetch("contacts.notes")
.orderBy("id")
.findList();
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("from o_customer t0 left join contact t1");
assertThat(sql.get(1)).contains("from contact_note t0 where");
assertThat(result).isNotEmpty();
assertThatThrownBy(() -> result.add(new Customer()))
.isInstanceOf(UnsupportedOperationException.class);
Customer customer = result.get(0);
List<Contact> contacts = customer.getContacts();
assertThat(contacts).isNotEmpty();
assertThatThrownBy(() -> contacts.add(new Contact()))
.isInstanceOf(UnsupportedOperationException.class);
for (Contact contact : contacts) {
Customer customer1 = contact.getCustomer();
assertThat(customer1).isSameAs(customer);
assertThatThrownBy(() -> contact.setFirstName("Attempting to Modify"))
.isInstanceOf(UnmodifiableEntityException.class);;
List<ContactNote> notes = contact.getNotes();
assertThatThrownBy(() -> notes.add(new ContactNote("junk","junk")))
.isInstanceOf(UnsupportedOperationException.class);
}
assertThatThrownBy(customer::getOrders)
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: orders");
assertThatThrownBy(() -> customer.setName("Attempting to Modify"))
.isInstanceOf(UnmodifiableEntityException.class)
.hasMessageContaining("Attempting to modify name");
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(customer);
oos.flush();
oos.close();
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois = new ObjectInputStream(is);
Customer read = (Customer) ois.readObject();
assertThat(read.getName()).isEqualTo(customer.getName());
}
@Test
void unmodifiableWithReference() {
ResetBasicData.reset();
List<Contact> result = DB.find(Contact.class)
.setUnmodifiable(true)
.findList();
assertThat(result).isNotEmpty();
for (Contact contact : result) {
Customer customer = contact.getCustomer();
assertThat(customer.getId()).isNotNull();
assertThatThrownBy(() -> customer.setId(42))
.isInstanceOf(UnsupportedOperationException.class);
}
}
@Test
void immutableResult() {
ResetBasicData.reset();
List<Customer> result = DB.find(Customer.class)
.setUnmodifiable(true) // .setDisableLazyLoading(true)
.select("id,name")
.fetch("contacts")
.findList();
assertThat(result).isNotEmpty();
assertThatThrownBy(() -> result.add(new Customer()))
.isInstanceOf(UnsupportedOperationException.class);
List<Contact> contacts = result.get(0).getContacts();
assertThat(contacts).isNotEmpty();
assertThatThrownBy(() -> contacts.add(new Contact()))
.isInstanceOf(UnsupportedOperationException.class);
}
@Test
void orderById_default_expectNotOrderById() {
ResetBasicData.reset();
Query<Customer> query = DB.find(Customer.class)
@@ -27,7 +185,7 @@ public class TestQueryOrderById extends BaseTestCase {
.setFirstRow(1)
.setMaxRows(5);
query.setReadOnly(true).setDisableLazyLoading(true);
query.setUnmodifiable(true); //.setDisableLazyLoading(true);
List<Customer> list = query.findList();
if (isSqlServer() || isDb2()) {
assertSql(query).isEqualTo("select t0.id, t0.name from o_customer t0 order by t0.id offset 1 rows fetch next 5 rows only");
@@ -37,10 +195,31 @@ public class TestQueryOrderById extends BaseTestCase {
assertThat(list).isNotEmpty();
Customer customer = list.get(0);
EntityBeanIntercept intercept = ((EntityBean) customer)._ebean_getIntercept();
int pos = intercept.findProperty("billingAddress");
assertThat(intercept.isLoadedProperty(pos)).isFalse();
assertThatThrownBy(() -> customer.getBillingAddress())
.describedAs("Not loaded property returns null")
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: billingAddress");
assertThatThrownBy(() -> customer.setBillingAddress(new Address()))
.describedAs("Not allowed to mutate a readOnly bean")
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: billingAddress");
assertThatThrownBy(customer::getBillingAddress)
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: billingAddress");
assertThat(intercept).isInstanceOf(InterceptReadOnly.class);
assertThat(customer.getOrders()).isSameAs(Collections.EMPTY_LIST);
assertThat(customer.getContacts()).isSameAs(Collections.EMPTY_LIST);
assertThatThrownBy(customer::getOrders)
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: orders");
assertThatThrownBy(customer::getContacts)
.isInstanceOf(LazyInitialisationException.class)
.hasMessageContaining("Property not loaded: contacts");
}
@Test
@@ -1,5 +1,6 @@
package org.tests.query.embedded;
import io.ebean.CacheMode;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Database;
@@ -14,13 +15,14 @@ import org.tests.model.embedded.EInvoice.State;
import java.util.Date;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class TestMultipleEmbeddedLoading extends BaseTestCase {
class TestMultipleEmbeddedLoading extends BaseTestCase {
@Test
public void testSimpleCase() {
void testSimpleCase() {
// prepare test
EAddress ship = new EAddress();
@@ -51,6 +53,19 @@ public class TestMultipleEmbeddedLoading extends BaseTestCase {
assertEquals("2 Apple St", invoice.getBillAddress().getStreet());
assertEquals("2 Apple St", invoice2.getBillAddress().getStreet());
EInvoice readOnlyInvoice = DB.find(EInvoice.class)
.setId(invoice.getId())
.setUnmodifiable(true)
.setBeanCacheMode(CacheMode.OFF)
.findOne();
assertThatThrownBy(() -> readOnlyInvoice.getBillAddress().setCity("junk"))
.describedAs("embedded bean is unmodifiable")
.isInstanceOf(UnsupportedOperationException.class);
assertThatThrownBy(() -> readOnlyInvoice.getShipAddress().setCity("junk"))
.describedAs("embedded bean is unmodifiable")
.isInstanceOf(UnsupportedOperationException.class);
// act: only update one of the embedded fields
invoice2.getBillAddress().setStreet("3 Pineapple St");
// bean should be dirty
@@ -132,8 +132,8 @@ public class TestReadAudit extends BaseTestCase {
assertThat(readAuditLogger.beans).hasSize(2);
Country ref = server.reference(Country.class, "AR");
assertThat(readAuditLogger.beans).hasSize(3);
assertThat(ref).isSameAs(found2);
assertThat(readAuditLogger.beans).hasSize(2);
assertThat(ref).isNotSameAs(found2);
}
@Test
@@ -197,9 +197,9 @@ public class TestReadAudit extends BaseTestCase {
+ " plans:" + readAuditLogger.plans
+ " many:" + readAuditLogger.many);
assertThat(readAuditPrepare.count).isEqualTo(2);
assertThat(readAuditPrepare.count).isEqualTo(1);
assertThat(readAuditLogger.plans).hasSize(1);
assertThat(readAuditLogger.many).hasSize(2);
assertThat(readAuditLogger.many).hasSize(1);
}
@Test
@@ -1,5 +1,6 @@
package org.tests.text.json;
import io.ebean.LazyInitialisationException;
import io.ebean.text.json.JsonReadOptions;
import io.ebean.xtest.BaseTestCase;
import io.ebean.BeanState;
@@ -19,8 +20,7 @@ import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
public class TestTextJsonReferenceBean extends BaseTestCase {
@@ -33,7 +33,7 @@ public class TestTextJsonReferenceBean extends BaseTestCase {
assertThat(DB.beanState(productRefBean).isReference()).isTrue();
// does not lazy load by default
assertThat(productRefBean.getName()).isNull();
assertThrows(LazyInitialisationException.class, productRefBean::getName);
}
@Test
@@ -68,13 +68,11 @@ public class TestUpdateAllLoadedProperties extends BaseTestCase {
.orderBy().asc("id")
.findList();
assertEquals(2, beans.size());
assertThat(beans).hasSize(2);
LoggedSql.start();
Transaction txn = DB.beginTransaction();
try {
try (Transaction txn = DB.beginTransaction()) {
txn.setUpdateAllLoadedProperties(true);
EBasicVer basic1 = beans.get(0);
@@ -87,9 +85,6 @@ public class TestUpdateAllLoadedProperties extends BaseTestCase {
DB.save(basic2);
txn.commit();
} finally {
txn.end();
}
List<String> loggedSql = LoggedSql.stop();