Compare commits

...
Author SHA1 Message Date
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
91 changed files with 1014 additions and 1080 deletions
@@ -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 UnmodifiableEntityException {
/**
* 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 UnsupportedOperationException {
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).
*/
@@ -552,4 +540,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
@@ -401,7 +400,7 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void initialisedMany(int propertyIndex) {
loaded[propertyIndex] = true;
}
@Override
@@ -416,12 +415,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 +437,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
@@ -22,7 +22,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,10 +56,6 @@ 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;
/**
@@ -67,7 +63,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
*/
private int state;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading.
@@ -77,7 +72,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 +95,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 +103,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 +129,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
'}';
}
@Override
public EntityBean owner() {
return owner;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
@@ -200,16 +193,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 +281,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 +384,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 +622,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];
@@ -708,7 +650,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
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;
@@ -845,18 +787,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;
@@ -620,7 +619,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);
}
@@ -1004,7 +1003,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);
}
/**
@@ -1215,11 +1214,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() ? 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,7 @@ 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 InheritInfo inheritInfo;
final String prefix;
private final Map<String, String> pathMap;
@@ -54,7 +54,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;
this.readOnlyNoIntercept = disableLazyLoad && node.readOnly;
this.unmodifiable = node.unmodifiable;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
@@ -111,7 +111,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 +162,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
void initBeanType() throws SQLException {
localDesc = desc;
localBean = desc.createEntityBean2(readOnlyNoIntercept);
localBean = desc.createEntityBean2(unmodifiable);
localIdBinder = idBinder;
}
@@ -224,7 +224,6 @@ class SqlTreeLoadBean implements SqlTreeLoad {
private void initSqlLoadBean() {
ctx.setCurrentPrefix(prefix, pathMap);
ctx.propagateState(localBean);
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
}
@@ -302,9 +301,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 (!unmodifiable || 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;
}
@@ -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.UnmodifiableEntityException;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.xtest.BaseTestCase;
@@ -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"));
}
}
+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.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);
@@ -1,7 +1,5 @@
package org.tests.model.lazywithid;
import io.ebean.common.BeanList;
import jakarta.persistence.*;
import java.util.List;
@@ -15,7 +13,7 @@ public class Tune {
String name;
@OneToMany(cascade = CascadeType.ALL)
private List<Looney> loonies = new BeanList<>();
private List<Looney> loonies;
public String getName() {
return name;
@@ -138,9 +138,9 @@ public class TestQueryFindIterate extends BaseTestCase {
List<String> loggedSql = LoggedSql.stop();
assertEquals(3, loggedSql.size());
assertTrue(trimSql(loggedSql.get(0), 7).contains("select t0.id, t0.status, t0.order_date, t1.id, t1.name from o_order t0 join o_customer t1"));
assertTrue(trimSql(loggedSql.get(1), 7).contains("select t0.order_id, t0.id, t0.order_qty, t0.ship_qty, t0.unit_price"));
assertTrue(trimSql(loggedSql.get(2), 7).contains("select t0.order_id, t0.id, t0.ship_time, t0.cretime, t0.updtime, t0.version, t0.order_id from or_order_ship"));
assertThat(trimSql(loggedSql.get(0), 7)).contains("select t0.id, t0.status, t0.order_date, t1.id, t1.name from o_order t0 join o_customer t1");
assertThat(trimSql(loggedSql.get(1), 7)).contains(" t0.order_id, t0.id, t0.order_qty, t0.ship_qty, t0.unit_price");
assertThat(trimSql(loggedSql.get(2), 7)).contains(" t0.order_id, t0.id, t0.ship_time, t0.cretime, t0.updtime, t0.version, t0.order_id from or_order_ship");
}
@Test
@@ -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
@@ -53,8 +53,8 @@ public class TestOrderByWithMany extends BaseTestCase {
String lazyLoadSql = loggedSql.get(1);
// contains the foreign key back to the parent bean (t0.order_id)
assertTrue(trimSql(lazyLoadSql, 2).contains("select t0.order_id, t0.id"));
assertTrue(lazyLoadSql.contains("order by t0.order_id, t0.id, t0.order_qty, t0.cretime desc"));
assertThat(trimSql(lazyLoadSql, 2)).contains(" t0.order_id, t0.id");
assertThat(lazyLoadSql).contains("order by t0.order_id, t0.id, t0.order_qty, t0.cretime desc");
}
@@ -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
@@ -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();