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