Compare commits

..
Author SHA1 Message Date
Rob Bygrave d7366dec0f Tidy readOnly removal 2025-03-18 20:59:04 +13:00
Rob Bygrave ceee1d7489 Remove readOnly from Query 2025-03-18 20:49:26 +13:00
Rob Bygrave 1b23319fa4 Remove readOnly from EntityBeanIntercept and BeanCollection 2025-03-18 20:32:22 +13:00
Rob Bygrave 6f726973ea Remove most internal use of readOnly 2025-03-18 19:53:13 +13:00
Rob Bygrave 82284b19d5 Bean cache sharable instances as unmodifiable + bean cache queries can also be unmodifiable
Entities without *any* relationships can support "sharable instances" with bean caching. This change restores that and uses unmodifiable instances (rather than the old ReadOnly).

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

Additionally, bean cache queries were not honouring setUnmodifiable(true) and with these changes they now do.
2025-03-16 23:19:54 +13:00
Rob Bygrave 798d01abd1 LazyInitialisationException, plus javadoc 2025-03-16 21:12:25 +13:00
Rob Bygrave 5136a5a708 Support unmodifiable Embedded and EmbeddedId 2025-03-13 22:52:45 +13:00
Rob Bygrave fa25d7cc8a Using Query Cache now implies unmodifiable
- Query Cache now holds unmodifiable collections
- Can no longer have readOnly=false with queryCache=true
- Reference beans now also honor unmodifiable
- Effectively no longer does bean cache lookup for reference beans (which it defaulted to when cacheSharableBeans true, e.g. Country entity bean)
2025-03-11 23:50:34 +13:00
Rob Bygrave 8ff5f7f72a Recursive freeze on entity beans
Noting that the freeze needs to occur after secondary queries have executed
2025-02-26 00:00:07 +13:00
Rob Bygrave 71944bb8fa Rename flags -> loaded, add message with property name to UnmodifiableEntityException 2025-02-23 22:25:46 +13:00
Rob Bygrave 24888fc01e Change InterceptReadWrite to use UnmodifiableEntityException
Add unmodified to DefaultOrmQuery query plan description
Fix incorrect merge conflict
2025-02-21 22:24:48 +13:00
Rob BygraveandGitHub 645d5bb613 Merge branch 'master' into feature/improved-readOnly-immutable 2025-02-21 22:08:38 +13:00
Rob Bygrave 4a6f1a39e0 Add UnloadedPropertyException and UnmodifiableEntityException, change existing use of IllegalStateException
- Adds new exceptions UnloadedPropertyException and UnmodifiableEntityException
- Change InterceptReadOnly to use these exception instead of IllegalStateException
- Change collections BeanSet, BeanList, BeanMap from using IllegalStateException to UnsupportedOperationException [to bring these in line with JDK unmodifiable collections]
2025-02-21 22:05:49 +13:00
Rob Bygrave 1fef4f7c11 Add explicit query.setUnmodifiable(true) [for readOnly=true + disableLazyLoad=true]
Although this is ok, makes me think that another option is just to have query.setReadOnly(true)
to mean ... readOnly + disableLazyLoad + error reading unloaded property or collection. As in,
readOnly true without these extra things does not that good [as in the existing readOnly does
not seem very good/safe/useful to use].
2024-11-20 23:01:59 +13:00
Rob Bygrave e6f92eaceb ReadOnly Immutable query WIP 2024-10-24 16:02:50 +13:00
217 changed files with 1617 additions and 2351 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
</parent>
<artifactId>composites</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
</parent>
<name>ebean api</name>
@@ -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
@@ -772,6 +772,18 @@ public interface Database {
*/
<T> T reference(Class<T> beanType, Object id);
/**
* Return the extended API for Database.
* <p>
* The extended API has the options for executing queries that take an explicit
* transaction as an argument.
* <p>
* Typically, we only need to use the extended API when we do NOT want to use the
* usual ThreadLocal based mechanism to obtain the current transaction but instead
* supply the transaction explicitly.
*/
ExtendedServer extended();
/**
* Either Insert or Update the bean depending on its state.
* <p>
@@ -0,0 +1,21 @@
package io.ebean;
import java.time.Clock;
/**
* The extended API for Database.
*/
public interface ExtendedServer {
/**
* Deprecated but no yet determined suitable replacement (to support testing only change of clock).
* <p>
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
* <p>
* Note that we only expect to change the Clock for testing purposes.
* </p>
*/
@Deprecated
void setClock(Clock clock);
}
@@ -0,0 +1,19 @@
package io.ebean;
/**
* Thrown when trying to access a property that isn't loaded on an entity
* that is unmodifiable or has disabled lazy loading.
* <p>
* On a normal mutable entity accessing the property would invoke lazy loading. On
* a unmodifiable entity with lazy loading disabled, accessing an unloaded property
* throws this LazyInitialisationException instead.
*/
public class LazyInitialisationException extends UnmodifiableEntityException {
/**
* Create specifying the property that was being accessed.
*/
public LazyInitialisationException(String message) {
super(message);
}
}
@@ -16,4 +16,7 @@ public interface ModifyAwareType {
*/
void setMarkedDirty(boolean markedDirty);
default Object freeze() {
return this; // throw new UnsupportedOperationException();
}
}
@@ -388,9 +388,13 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
SELF setUseDocStore(boolean useDocStore);
/**
* When set to true when you want the returned beans to be read only.
* When set to true when you want the returned beans to be unmodifiable read only.
* <p>
* This means that the returning graph can't be mutated via setters, all the collections
* are unmodifiable collections, lazy loading is disabled and that the query uses
* {@link PersistenceContextScope#QUERY}.
*/
SELF setReadOnly(boolean readOnly);
SELF setUnmodifiable(boolean unmodifiable);
/**
* Set a timeout on this query.
@@ -0,0 +1,22 @@
package io.ebean;
/**
* Attempted to modify a read only entity.
*/
public class UnmodifiableEntityException extends UnsupportedOperationException {
private static final long serialVersionUID = 1;
/**
* Create with no message.
*/
public UnmodifiableEntityException() {
super();
}
/**
* Create with message.
*/
public UnmodifiableEntityException(String message) {
super(message);
}
}
@@ -119,18 +119,6 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
*/
void setLoader(BeanCollectionLoader beanLoader);
/**
* Set to true if you want the BeanCollection to be treated as read only. This
* means no elements can be added or removed etc.
*/
void setReadOnly(boolean readOnly);
/**
* Return true if the collection should be treated as readOnly and no elements
* can be added or removed etc.
*/
boolean isReadOnly();
/**
* Add the bean to the collection. This is disallowed for BeanMap.
*/
@@ -237,7 +225,7 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
* Freeze the collection returning an unmodifiable version.
*/
BeanCollection<E> shallowCopy();
Object freeze();
}
@@ -148,18 +148,6 @@ public interface EntityBeanIntercept extends Serializable {
*/
boolean isLoadedFromCache();
/**
* Return true if the bean should be treated as readOnly. If a setter method
* is called when it is readOnly an Exception is thrown.
*/
boolean isReadOnly();
/**
* Set the readOnly status. If readOnly then calls to setter methods through
* an exception.
*/
void setReadOnly(boolean readOnly);
/**
* Set the bean to be updated when persisted (for merge).
*/
@@ -552,4 +540,9 @@ public interface EntityBeanIntercept extends Serializable {
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
String mutableNext(int propertyIndex);
/**
* Return true if this entity bean should be frozen. Used to handle recursive freezing.
*/
boolean freeze();
}
@@ -0,0 +1,70 @@
package io.ebean.bean;
/**
* Common base features for EntityBeanIntercept.
*/
abstract class InterceptBase implements EntityBeanIntercept {
final EntityBean owner;
boolean fullyLoadedBean;
/**
* Create with a given entity.
*/
InterceptBase(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
InterceptBase() {
this.owner = null;
}
@Override
public final EntityBean owner() {
return owner;
}
@Override
public final boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public final void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public final String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public final int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public final StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
}
@@ -1,37 +1,44 @@
package io.ebean.bean;
import io.ebean.LazyInitialisationException;
import io.ebean.UnmodifiableEntityException;
import io.ebean.ValuePair;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* EntityBeanIntercept optimised for read only use.
* <p>
* For the read only use this intercept doesn't need to hold any state that is normally
* required for updates such as per property changed, loaded, dirty state, original values
* required for updates such as per property changed, dirty state, original values
* bean state etc.
*/
public class InterceptReadOnly implements EntityBeanIntercept {
public final class InterceptReadOnly extends InterceptBase {
private final EntityBean owner;
private final boolean[] loaded;
private boolean frozen;
/**
* Create with a given entity.
*/
public InterceptReadOnly(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
this.loaded = new boolean[owner._ebean_getPropertyNames().length];
}
@Override
public boolean freeze() {
if (frozen) {
return false;
} else {
frozen = true;
return true;
}
}
@Override
public String toString() {
return "InterceptReadOnly{" + owner + '}';
}
@Override
public EntityBean owner() {
return owner;
return "InterceptReadOnly{frozen:" + frozen + " loaded:" + loadedPropertyNames() + '}';
}
@Override
@@ -94,18 +101,13 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public boolean isFullyLoadedBean() {
return false;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
}
@Override
public boolean isPartial() {
for (boolean flag : loaded) {
if (!flag) {
return true;
}
}
return false;
}
@@ -136,7 +138,14 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public boolean hasIdOnly(int idIndex) {
return false;
for (int i = 0; i < loaded.length; i++) {
if (i == idIndex) {
if (!loaded[i]) return false;
} else if (loaded[i]) {
return false;
}
}
return true;
}
@Override
@@ -159,16 +168,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return false;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean readOnly) {
}
@Override
public void setForceUpdate(boolean forceUpdate) {
@@ -234,44 +233,38 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return null;
}
@Override
public int findProperty(String propertyName) {
return 0;
}
@Override
public String property(int propertyIndex) {
return null;
}
@Override
public int propertyLength() {
return 0;
return loaded.length;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property not found - " + propertyName);
}
this.loaded[position] = loaded;
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
loaded[propertyIndex] = false;
}
@Override
public void setLoadedProperty(int propertyIndex) {
loaded[propertyIndex] = true;
}
@Override
public void setLoadedPropertyAll() {
Arrays.fill(loaded, true);
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return false;
return loaded[propertyIndex];
}
@Override
@@ -321,7 +314,16 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public Set<String> loadedPropertyNames() {
return Collections.emptySet();
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < loaded.length; i++) {
if (loaded[i]) {
props.add(property(i));
}
}
return props;
}
@Override
@@ -369,14 +371,11 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public StringBuilder loadedPropertyKey() {
return null;
}
@Override
public boolean[] loaded() {
return new boolean[0];
final boolean[] ret = new boolean[loaded.length];
System.arraycopy(loaded, 0, ret, 0, ret.length);
return ret;
}
@Override
@@ -401,7 +400,7 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void initialisedMany(int propertyIndex) {
loaded[propertyIndex] = true;
}
@Override
@@ -416,12 +415,14 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void preGetter(int propertyIndex) {
if (!loaded[propertyIndex]) {
throw new LazyInitialisationException("Property not loaded: " + property(propertyIndex));
}
}
@Override
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
@@ -436,57 +437,57 @@ public class InterceptReadOnly implements EntityBeanIntercept {
@Override
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
throw new UnmodifiableEntityException("Attempting to modify " + property(propertyIndex));
}
@Override
@@ -22,7 +22,7 @@ import java.util.concurrent.locks.ReentrantLock;
* This provides the mechanisms to support deferred fetching of reference beans
* and oldValues generation for concurrency checking.
*/
public final class InterceptReadWrite implements EntityBeanIntercept {
public final class InterceptReadWrite extends InterceptBase {
private static final long serialVersionUID = -3664031775464862649L;
@@ -56,10 +56,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private String ebeanServerName;
private boolean deletedFromCollection;
/**
* The actual entity bean that 'owns' this intercept.
*/
private final EntityBean owner;
private EntityBean embeddedOwner;
private int embeddedOwnerIndex;
/**
@@ -67,7 +63,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
*/
private int state;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading.
@@ -77,7 +72,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
private boolean lazyLoadFailure;
private boolean fullyLoadedBean;
private boolean loadedFromCache;
private final byte[] flags;
private Object[] origValues;
@@ -101,7 +95,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* Create with a given entity.
*/
public InterceptReadWrite(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
@@ -109,16 +103,20 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
public InterceptReadWrite() {
this.owner = null;
super();
this.flags = null;
}
@Override
public boolean freeze() {
throw new UnsupportedOperationException("never expected");
}
@Override
public String toString() {
return "InterceptReadWrite@" + hashCode() + "{state=" + state +
(dirty ? " dirty;" : "") +
(forceUpdate ? " forceUpdate;" : "") +
(readOnly ? " readOnly;" : "") +
(disableLazyLoad ? " disableLazyLoad;" : "") +
(lazyLoadFailure ? " lazyLoadFailure;" : "") +
(fullyLoadedBean ? " fullyLoadedBean;" : "") +
@@ -131,11 +129,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
'}';
}
@Override
public EntityBean owner() {
return owner;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
@@ -200,16 +193,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.ebeanServerName = beanLoader.name();
}
@Override
public boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public boolean isPartial() {
for (byte flag : flags) {
@@ -298,16 +281,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return loadedFromCache;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public void setForceUpdate(boolean forceUpdate) {
this.forceUpdate = forceUpdate;
@@ -411,25 +384,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return origValues[propertyIndex];
}
@Override
public int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public int propertyLength() {
return flags.length;
@@ -668,18 +622,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
@Override
public boolean[] loaded() {
final boolean[] ret = new boolean[flags.length];
@@ -708,7 +650,7 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
if (database == null) {
throw new PersistenceException(ebeanServerName == null ? "No registered default server" : "Database [" + ebeanServerName + "] is not registered");
}
// For stand alone reference bean or after deserialisation lazy load
// For stand-alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
@@ -845,18 +787,12 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
if (state == STATE_NEW) {
setLoadedProperty(propertyIndex);
} else {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangeLoaded(propertyIndex);
}
}
@Override
public void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setChangedProperty(propertyIndex);
if (setDirtyState) {
setOriginalValue(propertyIndex, origValue);
@@ -17,7 +17,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
private static final long serialVersionUID = 3365725236140187588L;
protected final ReentrantLock lock = new ReentrantLock();
protected boolean readOnly;
protected boolean disableLazyLoad;
/**
* The Database this is associated with. (used for lazy fetch).
@@ -55,7 +54,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.name();
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.readOnly = ownerBean != null && ownerBean._ebean_getIntercept().isReadOnly();
}
@Override
@@ -103,22 +101,6 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
this.ebeanServerName = loader.name();
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
void checkReadOnly() {
if (readOnly) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
// ---------------------------------------------------------
// Support for modify additions deletions etc - ManyToMany
// ---------------------------------------------------------
@@ -212,14 +194,4 @@ abstract class AbstractBeanCollection<E> implements BeanCollection<E> {
return modifyHolder != null && modifyHolder.wasTouched();
}
/**
* Copies all relevant properties for a clone. See {@link #shallowCopy()}
*/
protected void setFromOriginal(AbstractBeanCollection<E> other) {
this.disableLazyLoad = other.disableLazyLoad;
this.ebeanServerName = other.ebeanServerName;
this.loader = other.loader;
this.ownerBean = other.ownerBean;
this.propertyName = other.propertyName;
}
}
@@ -2,7 +2,6 @@ package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.*;
/**
@@ -39,6 +38,17 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
super(loader, ownerBean, propertyName);
}
@Override
public List<E> freeze() {
if (list == null) {
return null;
} else if (list.isEmpty()) {
return List.of();
} else {
return Collections.unmodifiableList(list);
}
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(list);
@@ -223,7 +233,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void add(int index, E element) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(element);
@@ -238,7 +247,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (list.add(bean)) {
@@ -253,7 +261,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
// all elements in c are added (no contains checking)
@@ -264,7 +271,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean addAll(int index, Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
// all elements in c are added (no contains checking)
@@ -275,7 +281,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public void clear() {
checkReadOnly();
// TODO: when clear() and not initialised could be more clever
// and fetch just the Id's
initClear();
@@ -320,9 +325,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, list.iterator());
}
@@ -338,9 +340,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public ListIterator<E> listIterator() {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator());
}
if (modifyListening) {
return new ModifyListIterator<>(this, list.listIterator());
}
@@ -350,9 +349,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public ListIterator<E> listIterator(int index) {
init();
if (readOnly) {
return new ReadOnlyListIterator<>(list.listIterator(index));
}
if (modifyListening) {
return new ModifyListIterator<>(this, list.listIterator(index));
}
@@ -368,7 +364,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public E remove(int index) {
checkReadOnly();
init();
if (modifyListening) {
E o = list.remove(index);
@@ -380,7 +375,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
boolean isRemove = list.remove(bean);
@@ -394,7 +388,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -412,7 +405,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public boolean retainAll(Collection<?> retainBeans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -433,7 +425,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public E set(int index, E element) {
checkReadOnly();
init();
if (modifyListening) {
E o = list.set(index, element);
@@ -453,9 +444,6 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
@Override
public List<E> subList(int fromIndex, int toIndex) {
init();
if (readOnly) {
return Collections.unmodifiableList(list.subList(fromIndex, toIndex));
}
if (modifyListening) {
return new ModifyList<>(this, list.subList(fromIndex, toIndex));
}
@@ -475,67 +463,4 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
return list.toArray(array);
}
private static final class ReadOnlyListIterator<E> implements ListIterator<E>, Serializable {
private static final long serialVersionUID = 3097271091406323699L;
private final ListIterator<E> i;
ReadOnlyListIterator(ListIterator<E> i) {
this.i = i;
}
@Override
public void add(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public void set(E o) {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
@Override
public boolean hasNext() {
return i.hasNext();
}
@Override
public boolean hasPrevious() {
return i.hasPrevious();
}
@Override
public E next() {
return i.next();
}
@Override
public int nextIndex() {
return i.nextIndex();
}
@Override
public E previous() {
return i.previous();
}
@Override
public int previousIndex() {
return i.previousIndex();
}
}
@Override
public BeanCollection<E> shallowCopy() {
BeanList<E> copy = new BeanList<>(new CopyOnFirstWriteList<>(list));
copy.setFromOriginal(this);
return copy;
}
}
@@ -37,6 +37,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@Override
public Map<K, E> freeze() {
return map == null ? null : Collections.unmodifiableMap(map);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
@@ -217,7 +222,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
// add all beans to the removal list
@@ -243,9 +247,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Set<Entry<K, E>> entrySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.entrySet());
}
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
}
@@ -264,15 +265,11 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Set<K> keySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.keySet());
}
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
}
@Override
public E put(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.put(key, value);
@@ -289,7 +286,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void putAll(Map<? extends K, ? extends E> puts) {
checkReadOnly();
init();
if (modifyListening) {
for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
@@ -306,17 +302,16 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public void addBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
throw new UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
}
@Override
public void removeBean(E bean) {
throw new IllegalStateException("Method not allowed on Map. Please use List instead.");
throw new UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
}
@Override
public E remove(Object key) {
checkReadOnly();
init();
if (modifyListening) {
E o = map.remove(key);
@@ -335,16 +330,6 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
@Override
public Collection<E> values() {
init();
if (readOnly) {
return Collections.unmodifiableCollection(map.values());
}
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
}
@Override
public BeanCollection<E> shallowCopy() {
BeanMap<K, E> copy = new BeanMap<>(new LinkedHashMap<>(map));
copy.setFromOriginal(this);
return copy;
}
}
@@ -3,10 +3,7 @@ package io.ebean.common;
import io.ebean.bean.*;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.*;
/**
* Set capable of lazy loading and modification aware.
@@ -38,6 +35,11 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
super(loader, ownerBean, propertyName);
}
@Override
public Set<E> freeze() {
return set == null ? null : Collections.unmodifiableSet(set);
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(set);
@@ -211,7 +213,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.add(bean)) {
@@ -226,7 +227,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -244,7 +244,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
for (E bean : set) {
@@ -275,9 +274,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyIterator<>(set.iterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, set.iterator());
}
@@ -286,7 +282,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.remove(bean)) {
@@ -300,7 +295,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -317,7 +311,6 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
@Override
public boolean retainAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
@@ -355,36 +348,4 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
return set.toArray(array);
}
private static final class ReadOnlyIterator<E> implements Iterator<E>, Serializable {
private static final long serialVersionUID = 2577697326745352605L;
private final Iterator<E> it;
ReadOnlyIterator(Iterator<E> it) {
this.it = it;
}
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public E next() {
return it.next();
}
@Override
public void remove() {
throw new IllegalStateException("This collection is in ReadOnly mode");
}
}
@Override
public BeanCollection<E> shallowCopy() {
BeanSet<E> copy = new BeanSet<>(new LinkedHashSet<>(set));
copy.setFromOriginal(this);
return copy;
}
}
@@ -1,185 +0,0 @@
package io.ebean.common;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
/**
* List that copies itself on first write access. Needed to keep memory footprint low and the ability
* to modify lists from cache.
*
* @author Roland Praml, FOCONIS AG
*/
public final class CopyOnFirstWriteList<E> extends AbstractList<E> implements List<E>, Serializable {
private static final long serialVersionUID = 1L;
private final ReentrantLock lock = new ReentrantLock();
/**
* The underlying List implementation.
*/
private List<E> list;
public CopyOnFirstWriteList(List<E> list) {
super();
this.list = list;
}
private volatile boolean copied = false;
@Override
public int size() {
return list.size();
}
@Override
public boolean isEmpty() {
return list.isEmpty();
}
@Override
public boolean contains(Object o) {
return list.contains(o);
}
@Override
public Object[] toArray() {
return list.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return list.toArray(a);
}
@Override
public boolean add(E e) {
checkCopyOnWrite();
return list.add(e);
}
@Override
public boolean remove(Object o) {
checkCopyOnWrite();
return list.remove(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return list.containsAll(c);
}
@Override
public boolean addAll(Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(c);
}
@Override
public boolean addAll(int index, Collection<? extends E> c) {
checkCopyOnWrite();
return list.addAll(index, c);
}
@Override
public boolean removeAll(Collection<?> c) {
checkCopyOnWrite();
return list.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
checkCopyOnWrite();
return list.retainAll(c);
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
checkCopyOnWrite();
list.replaceAll(operator);
}
@Override
public boolean removeIf(Predicate<? super E> filter) {
checkCopyOnWrite();
return list.removeIf(filter);
}
@Override
public void sort(Comparator<? super E> c) {
checkCopyOnWrite();
list.sort(c);
}
@Override
public void clear() {
if (!copied) {
list = new ArrayList<>();
copied = true;
}
}
@Override
public boolean equals(Object o) {
return list.equals(o);
}
@Override
public int hashCode() {
return list.hashCode();
}
@Override
public E get(int index) {
return list.get(index);
}
@Override
public E set(int index, E element) {
checkCopyOnWrite();
return list.set(index, element);
}
@Override
public void add(int index, E element) {
checkCopyOnWrite();
list.add(index, element);
}
@Override
public E remove(int index) {
checkCopyOnWrite();
return list.remove(index);
}
@Override
public int indexOf(Object o) {
return list.indexOf(o);
}
@Override
public int lastIndexOf(Object o) {
return list.lastIndexOf(o);
}
private void checkCopyOnWrite() {
if (!copied) {
lock.lock();
try {
if (!copied) {
list = new ArrayList<>(list);
copied = true;
}
} finally {
lock.unlock();
}
}
}
}
@@ -172,6 +172,7 @@ public class DbPlatformType implements ExtraDbTypes {
* </p>
*/
public String renderType(int deployLength, int deployScale, boolean strict) {
int len = deployLength != 0 ? deployLength : defaultLength;
if (len > maxLength) {
return fallback.renderType(deployLength, deployScale, strict);
@@ -181,6 +182,7 @@ public class DbPlatformType implements ExtraDbTypes {
if ((canHaveLength || !strict) && len > 0) {
renderLengthScale(len, deployScale, sb);
}
return sb.toString();
}
}
@@ -157,7 +157,7 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
if (newIds.isEmpty()) {
throw new PersistenceException("Always expecting more than 1 row from " + sql);
}
connection.commit();
return newIds;
} catch (SQLException e) {
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -7,6 +7,8 @@ import java.sql.SQLException;
public interface DataReader {
boolean unmodifiable();
void close() throws SQLException;
boolean next() throws SQLException;
+6 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>14.10.0</version>
<version>14.9.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -46,7 +46,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
</dependency>
<dependency>
@@ -159,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>14.10.0</version>
<version>14.9.0</version>
<scope>test</scope>
</dependency>
@@ -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);
}
@@ -26,7 +26,7 @@ import java.util.stream.Stream;
/**
* Service Provider extension to EbeanServer.
*/
public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {
public interface SpiEbeanServer extends SpiServer, ExtendedServer, BeanCollectionLoader {
/**
* Return the NOW time from the Clock.
@@ -780,9 +780,9 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
CacheMode queryCacheMode();
/**
* Return true if the beans returned by this query should be read only.
* Return true if the beans returned by this query should be unmodifiable.
*/
Boolean isReadOnly();
boolean isUnmodifiable();
/**
* Return the query timeout.
@@ -3,12 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.*;
/**
* Modify aware wrapper of a list.
@@ -52,6 +47,11 @@ public final class ModifyAwareList<E> implements List<E>, ModifyAwareType, Seria
return list.hashCode();
}
@Override
public List<E> freeze() {
return Collections.unmodifiableList(list);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,11 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Map that is wraps an underlying map for the purpose of detecting changes.
@@ -51,6 +47,11 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
return map.hashCode();
}
@Override
public Map<K, V> freeze() {
return Collections.unmodifiableMap(map);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,10 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Wraps a Set for the purposes of detecting modifications.
@@ -33,6 +30,11 @@ public final class ModifyAwareSet<E> implements Set<E>, ModifyAwareType, Seriali
this.set = underlying;
}
@Override
public Set<E> freeze() {
return Collections.unmodifiableSet(set);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.cache;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -48,25 +49,21 @@ public final class CachedBeanDataFromBean {
if (!desc.isCacheSharableBeans() || !beanEbi.isFullyLoadedBean()) {
return null;
}
if (beanEbi.isReadOnly()) {
if (beanEbi instanceof InterceptReadOnly) {
return bean;
}
// create a readOnly sharable instance by copying the data
EntityBean sharableBean = desc.createEntityBean();
EntityBean sharableBean = desc.createEntityBean2(true);
BeanProperty idProp = desc.idProperty();
if (idProp != null) {
Object v = idProp.getValue(bean);
idProp.setValue(sharableBean, v);
}
BeanProperty[] propertiesNonTransient = desc.propertiesNonTransient();
for (BeanProperty aPropertiesNonTransient : propertiesNonTransient) {
Object v = aPropertiesNonTransient.getValue(bean);
aPropertiesNonTransient.setValue(sharableBean, v);
for (BeanProperty nonTransient : desc.propertiesNonTransient()) {
Object v = nonTransient.getValue(bean);
nonTransient.setValue(sharableBean, v);
}
EntityBeanIntercept intercept = sharableBean._ebean_getIntercept();
intercept.setReadOnly(true);
intercept.setLoaded();
desc.freeze(sharableBean);
return sharableBean;
}
@@ -1,8 +1,6 @@
package io.ebeaninternal.server.cache;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
@@ -25,10 +23,11 @@ public final class CachedBeanDataToBean {
for (BeanProperty prop : desc.propertiesNonMany()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
}
final boolean addManyReferences = ebi instanceof InterceptReadWrite;
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
if (prop.isElementCollection()) {
loadProperty(bean, cacheBeanData, ebi, prop, context);
} else {
} else if (addManyReferences) {
prop.createReferenceIfNull(bean);
}
}
@@ -0,0 +1,29 @@
package io.ebeaninternal.server.core;
import java.time.Clock;
/**
* Wraps the Clock such that we can change the Clock for testing purposes.
*/
public class ClockService {
private Clock clock;
public ClockService(Clock clock) {
this.clock = clock;
}
/**
* Change the clock for testing purposes.
*/
public void setClock(Clock clock) {
this.clock = clock;
}
/**
* Return the Clock current timestamp in millis.
*/
public long nowMillis() {
return clock.millis();
}
}
@@ -77,12 +77,7 @@ public class DatabasePlatformFactory {
*/
private DatabasePlatform byDataSource(DataSource dataSource) {
try (Connection connection = dataSource.getConnection()) {
DatabasePlatform platform = byDatabaseMeta(connection.getMetaData(), connection);
if (!connection.getAutoCommit()) {
// we must roll back before close.
connection.rollback();
}
return platform;
return byDatabaseMeta(connection.getMetaData(), connection);
} catch (SQLException ex) {
throw new PersistenceException(ex);
}
@@ -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
@@ -262,8 +262,6 @@ public final class DefaultContainer implements SpiContainer {
try (Connection connection = config.getDataSource().getConnection()) {
if (connection.getAutoCommit()) {
log.log(WARNING, "DataSource [{0}] has autoCommit defaulting to true!", config.getName());
} else {
connection.rollback();
}
return true;
} catch (SQLException ex) {
@@ -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;
@@ -83,7 +82,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final QueryPlanManager queryPlanManager;
private final ExtraMetrics extraMetrics;
private final DataTimeZone dataTimeZone;
private final Clock clock;
private final ClockService clockService;
private final CallOriginFactory callStackFactory;
private final Persister persister;
private final OrmQueryEngine queryEngine;
@@ -151,7 +150,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clock = config.clock();
this.clockService = config.getClockService();
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
@@ -413,9 +412,19 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return serverName;
}
@Override
public ExtendedServer extended() {
return this;
}
@Override
public long clockNow() {
return clock.millis();
return clockService.nowMillis();
}
@Override
public void setClock(Clock clock) {
this.clockService.setClock(clock);
}
@Override
@@ -610,7 +619,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
InheritInfo inheritInfo = desc.inheritInfo();
if (inheritInfo == null || inheritInfo.isConcrete()) {
return (T) desc.contextRef(pc, null, false, id);
return (T) desc.contextRef(pc, id);
}
return referenceFindOne(type, id, desc);
}
@@ -994,7 +1003,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return null;
}
// Hit the L2 bean cache
return desc.cacheBeanGet(id, query.isReadOnly(), pc);
return desc.cacheBeanGet(id, query.isUnmodifiable(), pc);
}
/**
@@ -1205,11 +1214,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiOrmQueryRequest<?> request = createQueryRequest(Type.ID_LIST, query);
Object result = request.getFromQueryCache();
if (result != null) {
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
return new CopyOnFirstWriteList<>((List<A>) result);
} else {
return (List<A>) result;
}
return (List<A>) result;
}
try {
request.initTransIfRequired();
@@ -67,7 +67,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
@Override
protected void setResultSet(ResultSet resultSet, Object queryPlanKey) throws SQLException {
this.resultSet = resultSet;
this.dataReader = new RsetDataReader(server.dataTimeZone(), resultSet);
this.dataReader = new RsetDataReader(false, server.dataTimeZone(), resultSet);
obtainPlan(queryPlanKey);
}
@@ -55,7 +55,6 @@ import io.ebeanservice.docstore.api.DocStoreIntegration;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
import io.ebeanservice.docstore.none.NoneDocStoreFactory;
import java.time.Clock;
import java.util.*;
import static java.lang.System.Logger.Level.*;
@@ -76,7 +75,7 @@ public final class InternalConfiguration {
private final DeployInherit deployInherit;
private final TypeManager typeManager;
private final DtoBeanManager dtoBeanManager;
private final Clock clock;
private final ClockService clockService;
private final DataTimeZone dataTimeZone;
private final Binder binder;
private final DeployCreateProperties deployCreateProperties;
@@ -104,7 +103,7 @@ public final class InternalConfiguration {
this.online = online;
this.config = config;
this.jacksonCorePresent = config.getClassLoadConfig().isJacksonCorePresent();
this.clock = config.settings().getClock();
this.clockService = new ClockService(config.settings().getClock());
this.tableModState = new TableModState();
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(service(DocStoreFactory.class));
@@ -190,8 +189,8 @@ public final class InternalConfiguration {
return docStoreFactory;
}
Clock clock() {
return clock;
ClockService getClockService() {
return clockService;
}
public ExtraMetrics getExtraMetrics() {
@@ -392,7 +391,7 @@ public final class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
tableModState, cacheNotify);
tableModState, cacheNotify, clockService);
if (config.isDocStoreOnly()) {
return new DocStoreTransactionManager(options);
@@ -25,9 +25,9 @@ public interface OrmQueryEngine {
<T> T findId(OrmQueryRequest<T> request);
/**
* Execute the findList, findSet, findMap query returning an appropriate BeanCollection.
* Execute the findList, findSet, findMap query returning an appropriate Collection.
*/
<T> BeanCollection<T> findMany(OrmQueryRequest<T> request);
<T> Object findMany(OrmQueryRequest<T> request);
/**
* Execute the findSingleAttributeCollection query.
@@ -2,11 +2,11 @@ package io.ebeaninternal.server.core;
import io.ebean.*;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.cache.QueryCacheEntry;
import io.ebean.common.BeanList;
import io.ebean.common.BeanMap;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryAdapter;
import io.ebean.text.json.JsonReadOptions;
@@ -546,7 +546,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
if (orderBy != null && !orderBy.isEmpty()) {
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
}
return cacheBeans;
return query.isUnmodifiable() ? Collections.unmodifiableList(cacheBeans) : cacheBeans;
}
@Override
@@ -565,7 +565,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
for (T bean : cacheBeans) {
map.put((K) property.pathGet(bean), bean);
}
return map;
return query.isUnmodifiable() ? Collections.unmodifiableMap(map) : map;
}
private ElPropertyValue mapProperty() {
@@ -583,7 +583,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
if (orderBy != null && !orderBy.isEmpty()) {
beanDescriptor.sort(cacheBeans, orderBy.toStringFormat());
}
return new LinkedHashSet<>(cacheBeans);
var set = new LinkedHashSet<>(cacheBeans);
return query.isUnmodifiable() ? Collections.unmodifiableSet(set) : set;
}
@Override
@@ -600,9 +601,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
//
CacheIdLookup<T> idLookup = query.cacheIdLookup();
if (idLookup != null) {
BeanCacheResult<T> cacheResult = beanDescriptor.cacheIdLookup(persistenceContext, idLookup.idValues());
BeanCacheResult<T> cacheResult = beanDescriptor.cacheIdLookup(persistenceContext, query.isUnmodifiable(), idLookup.idValues());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = idLookup.removeHits(cacheResult);
if (query.isUnmodifiable()) {
unmodifiableFreeze(cacheBeans);
}
return idLookup.allHits();
}
if (!beanDescriptor.isNaturalKeyCaching()) {
@@ -613,7 +617,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
NaturalKeySet naturalKeySet = data.buildKeys();
if (naturalKeySet != null) {
// use the natural keys to lookup Ids to then hit the bean cache
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, naturalKeySet.keys());
BeanCacheResult<T> cacheResult = beanDescriptor.naturalKeyLookup(persistenceContext, query.isUnmodifiable(), naturalKeySet.keys());
// adjust the query (IN clause) based on the cache hits
this.cacheBeans = data.removeHits(cacheResult);
return data.allHits();
@@ -639,31 +643,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return null;
}
Object cached = beanDescriptor.queryCacheGet(cacheKey);
if (cached != null && isAuditReads() && readAuditQueryType()) {
if (cached instanceof BeanCollection) {
// raw sql can't use L2 cache so normal queries only in here
Collection<T> actualDetails = ((BeanCollection<T>) cached).actualDetails();
List<Object> ids = new ArrayList<>(actualDetails.size());
for (T bean : actualDetails) {
ids.add(beanDescriptor.idForJson(bean));
}
beanDescriptor.readAuditMany(queryPlanKey.partialKey(), "l2-query-cache", ids);
}
}
if (Boolean.FALSE.equals(query.isReadOnly())) {
// return shallow copies if readonly is explicitly set to false
if (cached instanceof BeanCollection) {
cached = ((BeanCollection<?>) cached).shallowCopy();
} else if (cached instanceof List) {
cached = new CopyOnFirstWriteList<>((List<?>) cached);
} else if (cached instanceof Set) {
cached = new LinkedHashSet<>((Set<?>) cached);
} else if (cached instanceof Map) {
cached = new LinkedHashMap<>((Map<?, ?>) cached);
}
}
return cached;
return beanDescriptor.queryCacheGet(cacheKey);
}
/**
@@ -780,4 +760,30 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
beanDescriptor.contextClear(transaction.persistenceContext());
}
}
public void unmodifiableFreeze(Collection<T> beans) {
if (query.isUnmodifiable()) {
if (beans != null) {
for (T bean : beans) {
beanDescriptor.freeze((EntityBean) bean);
}
}
}
}
public void unmodifiableFreeze(BeanCollection<T> beanCollection) {
if (query.isUnmodifiable()) {
if (beanCollection != null) {
for (T bean : beanCollection.actualDetails()) {
beanDescriptor.freeze((EntityBean) bean);
}
}
}
}
public void unmodifiableFreeze(EntityBean bean) {
if (query.isUnmodifiable() && bean != null) {
beanDescriptor.freeze(bean);
}
}
}
@@ -14,7 +14,7 @@ import java.sql.SQLException;
abstract class AssocOneHelp {
final BeanPropertyAssocOne<?> property;
private final BeanDescriptor<?> target;
protected final BeanDescriptor<?> target;
private final String path;
AssocOneHelp(BeanPropertyAssocOne<?> property) {
@@ -66,9 +66,8 @@ abstract class AssocOneHelp {
if (existing != null) {
return existing;
}
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = target.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
Object ref = target.contextRef(pc, id, ctx.unmodifiable(), ctx.isDisableLazyLoading());
if (!ctx.unmodifiable() && !ctx.isDisableLazyLoading()) {
ctx.register(path, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
@@ -81,7 +80,6 @@ abstract class AssocOneHelp {
Object val = read(ctx);
if (bean != null) {
property.setValue(bean, val);
ctx.propagateState(val);
}
return val;
}
@@ -32,7 +32,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
@Override
Object read(DataReader reader) throws SQLException {
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
EntityBean embeddedBean = property.targetDescriptor.createEntityBean2(reader.unmodifiable());
boolean notNull = false;
for (BeanProperty property : property.embeddedProps) {
Object value = property.readSet(reader, embeddedBean);
@@ -53,7 +53,6 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
if (bean != null) {
// set back to the parent bean
property.setValue(bean, dbVal);
ctx.propagateState(dbVal);
return dbVal;
} else {
return null;
@@ -62,7 +61,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
@Override
Object read(DbReadContext ctx) throws SQLException {
EntityBean embeddedBean = property.targetDescriptor.createEntityBean();
EntityBean embeddedBean = property.targetDescriptor.createEntityBean2(ctx.unmodifiable());
boolean notNull = false;
for (BeanProperty property : property.embeddedProps) {
Object value = property.readSet(ctx, embeddedBean);
@@ -70,12 +69,7 @@ final class AssocOneHelpEmbedded extends AssocOneHelp {
notNull = true;
}
}
if (notNull) {
ctx.propagateState(embeddedBean);
return embeddedBean;
} else {
return null;
}
return notNull ? embeddedBean : null;
}
@Override
@@ -53,9 +53,8 @@ final class AssocOneHelpRefInherit extends AssocOneHelp {
return existing;
}
// for inheritance hierarchy create the correct type for this row...
boolean disableLazyLoading = ctx.isDisableLazyLoading();
Object ref = desc.contextRef(pc, ctx.isReadOnly(), disableLazyLoading, id);
if (!disableLazyLoading) {
Object ref = desc.contextRef(pc, id, ctx.unmodifiable(), ctx.isDisableLazyLoading());
if (!ctx.unmodifiable() && !ctx.isDisableLazyLoading()) {
ctx.registerBeanInherit(property, ((EntityBean) ref)._ebean_getIntercept());
}
return ref;
@@ -688,6 +688,22 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
@Override
public void freeze(EntityBean entityBean) {
if (entityBean._ebean_getIntercept().freeze()) {
// recursively freeze the graph for this entityBean
for (BeanProperty beanProperty : propertiesMutable) {
beanProperty.freeze(entityBean);
}
for (BeanPropertyAssocOne<?> one : propertiesOne) {
one.freeze(entityBean);
}
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.freeze(entityBean);
}
}
}
public void metricPersistBatch(PersistRequest.Type type, long startNanos, int size) {
iudMetrics.addBatch(type, startNanos, size);
}
@@ -1190,8 +1206,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Try to load the beanCollection from cache return true if successful.
*/
public boolean cacheManyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey, Boolean readOnly) {
return cacheHelp.manyPropLoad(many, bc, parentKey, readOnly);
public boolean cacheManyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey) {
return cacheHelp.manyPropLoad(many, bc, parentKey);
}
/**
@@ -1240,8 +1256,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Load the entity bean as the correct bean type.
*/
EntityBean cacheBeanLoadDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
return cacheHelp.loadBeanDirect(id, readOnly, data, context);
EntityBean cacheBeanLoadDirect(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
return cacheHelp.loadBeanDirect(id, unmodifiable, data, context);
}
/**
@@ -1279,8 +1295,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Return a bean from the bean cache (or null).
*/
public T cacheBeanGet(Object id, Boolean readOnly, PersistenceContext context) {
return cacheHelp.beanCacheGet(cacheKey(id), readOnly, context);
public T cacheBeanGet(Object id, boolean unmodifiable, PersistenceContext context) {
return cacheHelp.beanCacheGet(cacheKey(id), unmodifiable, context);
}
/**
@@ -1309,15 +1325,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return cacheHelp.beanCacheLoad(bean, ebi, cacheKey(id), context);
}
public BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
return cacheHelp.cacheIdLookup(context, ids);
public BeanCacheResult<T> cacheIdLookup(PersistenceContext context, boolean unmodifiable, Collection<?> ids) {
return cacheHelp.cacheIdLookup(context, unmodifiable, ids);
}
/**
* Use natural key lookup to hit the bean cache.
*/
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
return cacheHelp.naturalKeyLookup(context, keys);
public BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, boolean unmodifiable, Set<Object> keys) {
return cacheHelp.naturalKeyLookup(context, unmodifiable, keys);
}
public void cacheNaturalKeyPut(String key, String newKey) {
@@ -1765,10 +1781,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
/**
* Create a reference with a check for the bean in the persistence context.
*/
public EntityBean createReference(Boolean readOnly, Object id, PersistenceContext pc) {
public EntityBean createReference(PersistenceContext pc, Object id) {
Object refBean = contextGet(pc, id);
if (refBean == null) {
refBean = createReference(readOnly, false, id, pc);
refBean = createReference(false, false, id, pc);
}
return (EntityBean) refBean;
}
@@ -1777,8 +1793,8 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Create a reference bean based on the id.
*/
@SuppressWarnings("unchecked")
public T createReference(Boolean readOnly, boolean disableLazyLoad, Object id, PersistenceContext pc) {
if (cacheSharableBeans && !disableLazyLoad && !Boolean.FALSE.equals(readOnly)) {
public T createReference(boolean unmodifiable, boolean disableLazyLoad, Object id, PersistenceContext pc) {
if (cacheSharableBeans && unmodifiable) {
CachedBeanData d = cacheHelp.beanCacheGetData(cacheKey(id));
if (d != null) {
Object shareableBean = d.getSharableBean();
@@ -1794,18 +1810,15 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
EntityBean eb = createEntityBean2(unmodifiable);
id = convertSetId(id, eb);
EntityBeanIntercept ebi = eb._ebean_getIntercept();
if (disableLazyLoad) {
ebi.setDisableLazyLoad(true);
} else {
} else if (!unmodifiable) {
ebi.setBeanLoader(refBeanLoader());
}
ebi.setReference(idPropertyIndex);
if (Boolean.TRUE == readOnly) {
ebi.setReadOnly(true);
}
if (pc != null) {
contextPut(pc, id, eb);
ebi.setPersistenceContext(pc);
@@ -2030,11 +2043,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
return pc.putIfAbsent(rootBeanType, id, localBean);
}
/**
* Create a reference bean and put it in the persistence context (and return it).
*/
public Object contextRef(PersistenceContext pc, Boolean readOnly, boolean disableLazyLoad, Object id) {
return createReference(readOnly, disableLazyLoad, id, pc);
public Object contextRef(PersistenceContext pc, Object id) {
return createReference(false, false, id, pc);
}
public Object contextRef(PersistenceContext pc, Object id, boolean unmodifiable, boolean disableLazyLoad) {
return createReference(unmodifiable, disableLazyLoad, id, pc);
}
/**
@@ -2149,7 +2163,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
* Set the Id value to the bean (without type conversion).
*/
public void setId(Object idValue, EntityBean bean) {
idProperty.setValueIntercept(bean, idValue);
idProperty.setValue(bean, idValue);
}
@Override
@@ -261,7 +261,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Try to load the bean collection from cache return true if successful.
*/
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey, Boolean readOnly) {
boolean manyPropLoad(BeanPropertyAssocMany<?> many, BeanCollection<?> bc, String parentKey) {
if (many.isElementCollection()) {
// held as part of the bean cache so skip
return false;
@@ -280,7 +280,7 @@ final class BeanDescriptorCacheHelp<T> {
bc.checkEmptyLazyLoad();
int i = 0;
for (Object id : idList) {
final EntityBean ref = targetDescriptor.createReference(readOnly, id, persistenceContext);
final EntityBean ref = targetDescriptor.createReference(persistenceContext, id);
if (many.hasOrderColumn()) {
ref._ebean_getIntercept().setSortOrder(++i);
}
@@ -344,7 +344,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Hit the bean cache with the given ids returning the hits.
*/
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, Collection<?> ids) {
BeanCacheResult<T> cacheIdLookup(PersistenceContext context, boolean unmodifiable, Collection<?> ids) {
Set<Object> keys = new HashSet<>(ids.size());
for (Object id : ids) {
keys.add(desc.cacheKey(id));
@@ -359,7 +359,7 @@ final class BeanDescriptorCacheHelp<T> {
BeanCacheResult<T> result = new BeanCacheResult<>();
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(entry.getKey(), false, context, cachedBeanData);
T bean = convertToBean(entry.getKey(), unmodifiable, context, cachedBeanData);
result.add(bean, desc.id(bean));
}
return result;
@@ -368,7 +368,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Use natural keys to hit the bean cache and return resulting hits.
*/
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, Set<Object> keys) {
BeanCacheResult<T> naturalKeyLookup(PersistenceContext context, boolean unmodifiable, Set<Object> keys) {
if (context == null) {
context = new DefaultPersistenceContext();
}
@@ -399,7 +399,7 @@ final class BeanDescriptorCacheHelp<T> {
for (Map.Entry<Object, Object> entry : beanDataMap.entrySet()) {
Object id = entry.getKey();
CachedBeanData cachedBeanData = (CachedBeanData) entry.getValue();
T bean = convertToBean(id, false, context, cachedBeanData);
T bean = convertToBean(id, unmodifiable, context, cachedBeanData);
Object naturalKey = reverseMap.get(id);
result.add(bean, naturalKey);
}
@@ -561,9 +561,9 @@ final class BeanDescriptorCacheHelp<T> {
return (CachedBeanData) getBeanCache().get(key);
}
T beanCacheGet(String key, Boolean readOnly, PersistenceContext context) {
T bean = beanCacheGetInternal(key, readOnly, context);
if (bean != null) {
T beanCacheGet(String key, boolean unmodifiable, PersistenceContext context) {
T bean = beanCacheGetInternal(key, unmodifiable, context);
if (bean != null && !unmodifiable) {
setupContext(bean, context);
}
return bean;
@@ -572,7 +572,7 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Return a bean from the bean cache.
*/
private T beanCacheGetInternal(String key, Boolean readOnly, PersistenceContext context) {
private T beanCacheGetInternal(String key, boolean unmodifiable, PersistenceContext context) {
CachedBeanData data = (CachedBeanData) getBeanCache().get(key);
if (data == null) {
if (beanLog.isLoggable(TRACE)) {
@@ -583,12 +583,12 @@ final class BeanDescriptorCacheHelp<T> {
if (beanLog.isLoggable(TRACE)) {
beanLog.log(TRACE, " GET {0}({1}) - hit", cacheName, key);
}
return convertToBean(key, readOnly, context, data);
return convertToBean(key, unmodifiable, context, data);
}
@SuppressWarnings("unchecked")
private T convertToBean(Object id, Boolean readOnly, PersistenceContext context, CachedBeanData data) {
if (cacheSharableBeans && !Boolean.FALSE.equals(readOnly)) {
private T convertToBean(Object id, boolean unmodifiable, PersistenceContext context, CachedBeanData data) {
if (cacheSharableBeans && unmodifiable) {
Object bean = data.getSharableBean();
if (bean != null) {
if (beanLog.isLoggable(TRACE)) {
@@ -600,18 +600,18 @@ final class BeanDescriptorCacheHelp<T> {
return (T) bean;
}
}
return (T) loadBean(id, readOnly, data, context);
return (T) loadBean(id, unmodifiable, data, context);
}
/**
* Load the entity bean taking into account inheritance.
*/
private EntityBean loadBean(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
private EntityBean loadBean(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
String discValue = data.getDiscValue();
if (discValue == null) {
return loadBeanDirect(id, readOnly, data, context);
return loadBeanDirect(id, unmodifiable, data, context);
} else {
return rootDescriptor(discValue).cacheBeanLoadDirect(id, readOnly, data, context);
return rootDescriptor(discValue).cacheBeanLoadDirect(id, unmodifiable, data, context);
}
}
@@ -625,26 +625,22 @@ final class BeanDescriptorCacheHelp<T> {
/**
* Load the entity bean from cache data given this is the root bean type.
*/
EntityBean loadBeanDirect(Object id, Boolean readOnly, CachedBeanData data, PersistenceContext context) {
EntityBean loadBeanDirect(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
id = desc.convertId(id);
EntityBean bean = null;
if (context == null) {
context = new DefaultPersistenceContext();
} else {
bean = (EntityBean) desc.contextGet(context, id);
}
EntityBean bean = context == null ? null : (EntityBean) desc.contextGet(context, id);;
if (bean == null) {
bean = desc.createEntityBean();
bean = desc.createEntityBean2(unmodifiable);
desc.setId(id, bean);
desc.contextPut(context, id, bean);
EntityBeanIntercept ebi = bean._ebean_getIntercept();
// Not using loadContext here so no batch lazy loading for these beans
ebi.setBeanLoader(desc.l2BeanLoader());
if (Boolean.TRUE.equals(readOnly)) {
ebi.setReadOnly(true);
if (!unmodifiable) {
if (context == null) {
context = new DefaultPersistenceContext();
}
desc.contextPut(context, id, bean);
EntityBeanIntercept ebi = bean._ebean_getIntercept();
ebi.setPersistenceContext(context);
// Not using loadContext here so no batch lazy loading for these beans
ebi.setBeanLoader(desc.l2BeanLoader());
}
ebi.setPersistenceContext(context);
}
CachedBeanDataToBean.load(desc, bean, data, context);
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.DataIntegrityException;
import io.ebean.ModifyAwareType;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
@@ -1519,4 +1520,15 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
desc.registerColumn(dbColumn, path);
}
}
/**
* Freeze mutable types (like DbArray).
*/
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof ModifyAwareType) {
ModifyAwareType bc = (ModifyAwareType) value;
setValue(entityBean, bc.freeze());
}
}
}
@@ -1165,4 +1165,22 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return false;
}
}
@Override
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof BeanCollection) {
BeanCollection<?> beanCollection = (BeanCollection<?>) value;
Collection<?> entities = beanCollection.actualDetails();
if (entities != null) {
for (Object actualEntry : entities) {
targetDescriptor.freeze((EntityBean) actualEntry);
}
}
setValue(entityBean, beanCollection.freeze());
} else if (value == null) {
// make it an error to access the collection (no lazy loading allowed)
entityBean._ebean_getIntercept().setPropertyUnloaded(propertyIndex);
}
}
}
@@ -663,6 +663,14 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
setValue(entityBean, targetDescriptor.createRef(tenantId, null));
}
@Override
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof EntityBean) {
targetDescriptor.freeze((EntityBean) value);
}
}
@Override
public void setValue(EntityBean bean, Object value) {
super.setValue(bean, value);
@@ -14,16 +14,6 @@ import java.util.Map;
*/
public interface DbReadContext {
/**
* Return the state of the object graph.
*/
Boolean isReadOnly();
/**
* Propagate the state to the bean.
*/
void propagateState(Object e);
/**
* Return the DataReader.
*/
@@ -102,4 +92,14 @@ public interface DbReadContext {
*/
void handleLoadError(String fullName, Exception e);
/**
* Return true if this many property should be included in unmodifiable
* query via a secondary query.
*/
boolean includeSecondary(BeanPropertyAssocMany<?> many);
/**
* Return true if we are loading unmodifiable beans.
*/
boolean unmodifiable();
}
@@ -228,8 +228,8 @@ public final class InheritInfo {
/**
* Create an EntityBean for this type.
*/
public EntityBean createEntityBean() {
return descriptor.createEntityBean();
public EntityBean createEntityBean(boolean unmodifiable) {
return descriptor.createEntityBean2(unmodifiable);
}
/**
@@ -20,7 +20,6 @@ public final class TableJoinColumn {
private final String foreignSqlFormula;
private final boolean insertable;
private final boolean updateable;
private final boolean nullable;
/**
* Hash for including in a query plan
*/
@@ -35,8 +34,7 @@ public final class TableJoinColumn {
this.localSqlFormula = InternString.intern(deploy.getLocalSqlFormula());
this.foreignSqlFormula = InternString.intern(deploy.getForeignSqlFormula());
this.insertable = deploy.isInsertable();
this.updateable = deploy.isUpdatable();
this.nullable = deploy.isNullable();
this.updateable = deploy.isUpdateable();
this.queryHash = hash();
}
@@ -47,7 +45,6 @@ public final class TableJoinColumn {
this.foreignSqlFormula = null;
this.insertable = source.isInsertable();
this.updateable = source.isUpdateable();
this.nullable = source.isNullable();
this.queryHash = hash();
}
@@ -118,13 +115,6 @@ public final class TableJoinColumn {
return updateable;
}
/**
* Return if this column is nullable.
*/
public boolean isNullable() {
return nullable;
}
public String getLocalSqlFormula() {
return localSqlFormula;
}
@@ -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;
}
@@ -81,7 +81,7 @@ public final class DeployTableJoin {
// only add the join column details when name or referencedColumnName is specified
String ref = deploy.convertQuotes(jc.referencedColumnName());
String nam = deploy.convertQuotes(jc.name());
addJoinColumn(new DeployTableJoinColumn(order, ref, nam, jc.insertable(), jc.updatable(), jc.nullable(), beanTable));
addJoinColumn(new DeployTableJoinColumn(order, ref, nam, jc.insertable(), jc.updatable(), beanTable));
}
}
@@ -25,8 +25,7 @@ public final class DeployTableJoinColumn {
*/
private String foreignSqlFormula;
private boolean insertable;
private boolean updatable;
private boolean nullable;
private boolean updateable;
/**
* Construct when automatically determining the join.
@@ -35,24 +34,21 @@ public final class DeployTableJoinColumn {
* </p>
*/
public DeployTableJoinColumn(String localDbColumn, String foreignDbColumn) {
this(localDbColumn, foreignDbColumn, true, true, true);
this(localDbColumn, foreignDbColumn, true, true);
}
/**
* Construct with explicit insertable and updateable flags.
*/
public DeployTableJoinColumn(String localDbColumn, String foreignDbColumn,
boolean insertable, boolean updatable, boolean nullable) {
public DeployTableJoinColumn(String localDbColumn, String foreignDbColumn, boolean insertable, boolean updateable) {
this.localDbColumn = nullEmptyString(localDbColumn);
this.foreignDbColumn = nullEmptyString(foreignDbColumn);
this.insertable = insertable;
this.updatable = updatable;
this.nullable = nullable;
this.updateable = updateable;
}
public DeployTableJoinColumn(boolean order, String ref, String name,
boolean insertable, boolean updatable, boolean nullable, BeanTable beanTable) {
this(ref, name, insertable, updatable, nullable);
public DeployTableJoinColumn(boolean order, String ref, String name, boolean insertable, boolean updateable, BeanTable beanTable) {
this(ref, name, insertable, updateable);
setReferencedColumn(beanTable);
if (!order) {
reverse();
@@ -64,7 +60,7 @@ public final class DeployTableJoinColumn {
this.localSqlFormula = localSqlFormula;
this.localDbColumn = null;
this.insertable = false;
this.updatable = false;
this.updateable = false;
}
}
@@ -77,7 +73,7 @@ public final class DeployTableJoinColumn {
this.foreignSqlFormula = foreignSqlFormula;
this.foreignDbColumn = null;
this.insertable = false;
this.updatable = false;
this.updateable = false;
}
}
@@ -125,12 +121,12 @@ public final class DeployTableJoinColumn {
// but will leave it like this for now
DeployTableJoinColumn ret;
if (reverse) {
ret = new DeployTableJoinColumn(foreignDbColumn, localDbColumn, insertable, updatable, nullable);
ret = new DeployTableJoinColumn(foreignDbColumn, localDbColumn, insertable, updateable);
ret.setLocalSqlFormula(foreignSqlFormula);
ret.setForeignSqlFormula(localSqlFormula);
} else {
ret = new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updatable, nullable);
ret = new DeployTableJoinColumn(localDbColumn, foreignDbColumn, insertable, updateable);
ret.setLocalSqlFormula(localSqlFormula);
ret.setForeignSqlFormula(foreignSqlFormula);
}
@@ -152,8 +148,8 @@ public final class DeployTableJoinColumn {
/**
* Return true if this column should be updateable.
*/
public boolean isUpdatable() {
return updatable;
public boolean isUpdateable() {
return updateable;
}
/**
@@ -177,7 +173,4 @@ public final class DeployTableJoinColumn {
this.localDbColumn = localDbColumn;
}
public boolean isNullable() {
return nullable;
}
}
@@ -223,7 +223,7 @@ final class AnnotationAssocOnes extends AnnotationAssoc {
BeanTable baseBeanTable = factory.beanTable(info.getDescriptor().getBeanType());
String localPrimaryKey = baseBeanTable.getIdColumn();
String foreignColumn = getBeanTable(prop).getIdColumn();
prop.getTableJoin().addJoinColumn(new DeployTableJoinColumn(localPrimaryKey, foreignColumn, false, false, prop.isNullable()));
prop.getTableJoin().addJoinColumn(new DeployTableJoinColumn(localPrimaryKey, foreignColumn, false, false));
}
private void readEmbedded(DeployBeanPropertyAssocOne<?> prop, Embedded embedded) {
@@ -11,9 +11,7 @@ import io.ebeaninternal.server.el.ElPropertyValue;
import io.ebeaninternal.server.querydefn.OrmQueryProperties;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Default implementation of LoadContext.
@@ -27,7 +25,7 @@ public final class DLoadContext implements LoadContext {
private final DLoadBeanContext rootBeanContext;
private final boolean asDraft;
private final Timestamp asOf;
private final Boolean readOnly;
private final boolean unmodifiable;
private final CacheMode useBeanCache;
private final int defaultBatchSize;
private final boolean disableLazyLoading;
@@ -48,6 +46,7 @@ public final class DLoadContext implements LoadContext {
boolean useReferences;
private List<OrmQueryProperties> secQuery;
private Object tenantId;
private final Set<BeanProperty> secondaryProperties;
/**
* Construct for use with JSON marshalling (doc store).
@@ -62,7 +61,7 @@ public final class DLoadContext implements LoadContext {
this.useBeanCache = CacheMode.OFF;
this.asDraft = false;
this.asOf = null;
this.readOnly = false;
this.unmodifiable = false;
this.disableLazyLoading = false;
this.disableReadAudit = false;
this.includeSoftDeletes = false;
@@ -71,6 +70,7 @@ public final class DLoadContext implements LoadContext {
this.profileLocation = null;
this.profilingListener = null;
this.rootBeanContext = new DLoadBeanContext(this, rootDescriptor, null, null);
this.secondaryProperties = null;
}
private ObjectGraphOrigin initOrigin() {
@@ -90,13 +90,14 @@ public final class DLoadContext implements LoadContext {
this.asOf = query.getAsOf();
this.asDraft = query.isAsDraft();
this.includeSoftDeletes = query.isIncludeSoftDeletes() && query.mode() == SpiQuery.Mode.NORMAL;
this.readOnly = query.isReadOnly();
this.unmodifiable = query.isUnmodifiable();
this.disableReadAudit = query.isDisableReadAudit();
this.disableLazyLoading = query.isDisableLazyLoading();
this.useBeanCache = query.beanCacheMode();
this.profilingListener = query.profilingListener();
this.planLabel = query.planLabel();
this.profileLocation = query.profileLocation();
this.secondaryProperties = query.isUnmodifiable() ? new HashSet<>() : null;
ObjectGraphNode parentNode = query.parentNode();
if (parentNode != null) {
@@ -156,6 +157,14 @@ public final class DLoadContext implements LoadContext {
ElPropertyValue elGetValue = rootDescriptor.elGetValue(props.getPath());
boolean many = elGetValue.beanProperty().containsMany();
registerSecondaryNode(many, props);
if (many && secondaryProperties != null) {
secondaryProperties.add(elGetValue.beanProperty());
}
}
@Override
public boolean includeSecondary(BeanPropertyAssocMany<?> many) {
return secondaryProperties != null && secondaryProperties.contains(many);
}
boolean isBeanCacheGet() {
@@ -230,14 +239,6 @@ public final class DLoadContext implements LoadContext {
return ebeanServer;
}
/**
* Return the parent state which defines the sharedInstance and readOnly status
* which needs to be propagated to other beans and collections.
*/
Boolean isReadOnly() {
return readOnly;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
@@ -320,9 +321,7 @@ public final class DLoadContext implements LoadContext {
if (useDocStore && docStoreMapped) {
query.setUseDocStore(true);
}
if (readOnly != null) {
query.setReadOnly(readOnly);
}
query.setUnmodifiable(unmodifiable);
query.setDisableLazyLoading(disableLazyLoading);
query.asOf(asOf);
if (asDraft) {
@@ -198,7 +198,7 @@ final class DLoadManyContext extends DLoadBaseContext implements LoadManyContext
BeanDescriptor<?> parentDesc = context.desc.descriptor(ownerBean.getClass());
Object parentId = parentDesc.getId(ownerBean);
final String parentKey = parentDesc.cacheKey(parentId);
if (parentDesc.cacheManyPropLoad(context.property, bc, parentKey, context.parent.isReadOnly())) {
if (parentDesc.cacheManyPropLoad(context.property, bc, parentKey)) {
// we loaded the bean collection from cache so remove it from the buffer
if (removeFromBuffer(bc)) {
bc.setLoader(context.parent.server());
@@ -401,7 +401,7 @@ public final class Binder {
return new DataBind(dataTimeZone, stmt, connection);
}
public DataReader createDataReader(ResultSet resultSet) {
return new RsetDataReader(dataTimeZone, resultSet);
public DataReader createDataReader(boolean unmodifiable, ResultSet resultSet) {
return new RsetDataReader(unmodifiable, dataTimeZone, resultSet);
}
}
@@ -44,6 +44,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
private final ReentrantLock lock = new ReentrantLock();
private final boolean loadContextBean;
private final boolean unmodifiable;
/**
* The resultSet rows read.
@@ -152,8 +153,6 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
private final ProfilingListener profilingListener;
private final Boolean readOnly;
private long profileOffset;
private long startNano;
@@ -186,7 +185,6 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
this.queryMode = query.mode();
this.loadContextBean = queryMode.isLoadContextBean() || query.getForUpdateLockType() != null;
this.lazyLoadManyProperty = query.lazyLoadMany();
this.readOnly = query.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.parentNode();
this.profilingListener = query.profilingListener();
@@ -207,6 +205,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
} else {
this.help = createHelp(request);
}
this.unmodifiable = request.query().isUnmodifiable();
this.collection = (help != null ? help.createEmptyNoParent() : null);
}
@@ -239,17 +238,8 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
}
@Override
public Boolean isReadOnly() {
return readOnly;
}
@Override
public void propagateState(Object e) {
if (Boolean.TRUE.equals(readOnly)) {
if (e instanceof EntityBean) {
((EntityBean) e)._ebean_getIntercept().setReadOnly(true);
}
}
public boolean unmodifiable() {
return unmodifiable;
}
@Override
@@ -299,7 +289,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
if (resultSet == null) {
return false;
}
dataReader = queryPlan.createDataReader(resultSet);
dataReader = queryPlan.createDataReader(query.isUnmodifiable(), resultSet);
return true;
}
@@ -474,6 +464,14 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
return result;
}
EntityBean nextBean() {
EntityBean bean = next();
if (unmodifiable) {
request.unmodifiableFreeze(bean);
}
return bean;
}
EntityBean next() {
if (audit) {
auditNextBean();
@@ -603,6 +601,11 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
request.loadContext().register(path(many.name()), many, bc);
}
@Override
public boolean includeSecondary(BeanPropertyAssocMany<?> many) {
return request.loadContext().includeSecondary(many);
}
/**
* Return true if this is a raw sql query as opposed to Ebean generated sql.
*/
@@ -107,15 +107,9 @@ public final class CQueryEngine {
if (collection instanceof List) {
collection = (A) Collections.unmodifiableList((List<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new ArrayList<>(collection);
}
} else if (collection instanceof Set) {
collection = (A) Collections.unmodifiableSet((Set<?>) collection);
request.putToQueryCache(collection);
if (Boolean.FALSE.equals(request.query().isReadOnly())) {
collection = (A) new LinkedHashSet<>(collection);
}
}
}
return collection;
@@ -358,6 +352,7 @@ public final class CQueryEngine {
if (request.isQueryCachePut()) {
request.addDependentTables(cquery.dependentTables());
}
request.unmodifiableFreeze(beanCollection);
return beanCollection;
} catch (SQLException e) {
@@ -390,6 +385,7 @@ public final class CQueryEngine {
cquery.auditFind(bean);
}
request.executeSecondaryQueries(false);
request.unmodifiableFreeze(bean);
return (T) bean;
} catch (SQLException e) {
throw cquery.createPersistenceException(e);
@@ -137,7 +137,7 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca
} finally {
lock.unlock();
}
dataReader = new RsetDataReader(request.dataTimeZone(), pstmt.executeQuery());
dataReader = new RsetDataReader(query.isUnmodifiable(), request.dataTimeZone(), pstmt.executeQuery());
query.checkCancelled();
}
@@ -37,7 +37,7 @@ final class CQueryIteratorSimple<T> implements QueryIterator<T> {
@Override
@SuppressWarnings("unchecked")
public T next() {
return (T) cquery.next();
return (T) cquery.nextBean();
}
@Override
@@ -44,6 +44,7 @@ final class CQueryIteratorWithBuffer<T> implements QueryIterator<T> {
}
request.executeSecondaryQueries(true);
}
request.unmodifiableFreeze(buffer);
ret = !buffer.isEmpty();
return ret;
} catch (SQLException e) {
@@ -205,8 +205,8 @@ public class CQueryPlan implements SpiQueryPlan {
return new DQueryPlanOutput(beanType(), name, hash, sql, profileLocation, bind, planString);
}
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReader(dataTimeZone, rset);
public DataReader createDataReader(boolean unmodifiable, ResultSet rset) {
return new RsetDataReader(unmodifiable, dataTimeZone, rset);
}
/**
@@ -63,13 +63,6 @@ public final class CQueryPlanManager implements QueryPlanManager {
while (req.hasNext()) {
req.nextCapture();
}
if (!connection.getAutoCommit()) {
// CHECKME: commit or rollback here?
// arguments for rollback: the collecting should never modify data.
// if there are collectors that may copy the plan into tables, it's up to the collector to
// commit the transaction.
connection.rollback();
}
return req.plans();
} catch (SQLException e) {
CoreLog.log.log(ERROR, "Error during query plan collection", e);
@@ -22,8 +22,8 @@ final class CQueryPlanRawSql extends CQueryPlan {
}
@Override
public DataReader createDataReader(ResultSet rset) {
return new RsetDataReaderIndexed(dataTimeZone, rset, rsetIndexPositions);
public DataReader createDataReader(boolean unmodifiable, ResultSet rset) {
return new RsetDataReaderIndexed(unmodifiable, dataTimeZone, rset, rsetIndexPositions);
}
private int[] createIndexPositions(OrmQueryRequest<?> request, SqlTree sqlTree) {
@@ -504,7 +504,7 @@ final class DefaultFetchGroupQuery<T> implements SpiFetchGroupQuery<T>, SpiQuery
}
@Override
public Query<T> setReadOnly(boolean readOnly) {
public Query<T> setUnmodifiable(boolean unmodifiable) {
throw new RuntimeException("EB102: Only select() and fetch() clause is allowed on FetchGroup");
}
@@ -114,7 +114,7 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
}
@Override
public <T> BeanCollection<T> findMany(OrmQueryRequest<T> request) {
public <T> Object findMany(OrmQueryRequest<T> request) {
flushJdbcBatchOnQuery(request);
BeanFindController finder = request.finder();
@@ -129,22 +129,21 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
result = finder.postProcessMany(request, result);
}
SpiQuery<T> query = request.query();
if (result != null && request.isBeanCachePutMany()) {
// load the individual beans into the bean cache
request.descriptor().cacheBeanPutAll(result.actualDetails());
}
request.mergeCacheHits(result);
if (request.isQueryCachePut()) {
// load the query result into the query cache
result.setReadOnly(true);
request.putToQueryCache(result);
if (Boolean.FALSE.equals(query.isReadOnly())) {
result = result.shallowCopy();
Object finalResult = result;
if (request.query().isUnmodifiable()) {
finalResult = result == null ? null : result.freeze();
if (request.isQueryCachePut()) {
// load the query result into the query cache
request.putToQueryCache(finalResult);
}
}
return result;
return finalResult;
}
/**
@@ -147,7 +147,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
T value = null;
if (dataReader.next()) {
value = scalarType.read(dataReader);
@@ -172,7 +172,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindList);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
List<T> rows = new ArrayList<>();
while (dataReader.next()) {
rows.add(scalarType.read(dataReader));
@@ -197,7 +197,7 @@ public final class DefaultRelationalQueryEngine implements RelationalQueryEngine
request.setDefaultFetchBuffer(defaultFetchSizeFindEach);
}
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
final DataReader dataReader = binder.createDataReader(request.resultSet());
final DataReader dataReader = binder.createDataReader(false, request.resultSet());
while (dataReader.next()) {
consumer.accept(scalarType.read(dataReader));
}
@@ -34,7 +34,7 @@ public interface STreePropertyAssocMany extends STreePropertyAssoc {
BeanCollection<?> createReference(EntityBean localBean, boolean forceNewReference);
/**
* Populate the collection for read-only disabled lazy loading (aka Java Collections non mutable empty collection).
* Populate the collection for read-only disabled lazy loading (aka Java Collections non-mutable empty collection).
*/
void createEmptyReference(EntityBean localBean);
@@ -99,6 +99,11 @@ public interface STreeType {
*/
void postLoad(Object localBean);
/**
* Freeze the properties of the entity.
*/
void freeze(EntityBean entityBean);
/**
* Return the base table to use given the temporalMode.
*/
@@ -45,12 +45,12 @@ public final class SqlTreeBuilder {
*/
private final boolean rawNoId;
private final boolean disableLazyLoad;
private final boolean readOnly;
private final SpiQuery.TemporalMode temporalMode;
private final SqlTreeNode rootNode;
private boolean sqlDistinct;
private final boolean platformDistinctNoLobs;
private final SqlTreeCommon common;
private final boolean unmodifiable;
/**
* Construct for RawSql query.
@@ -60,7 +60,7 @@ public final class SqlTreeBuilder {
this.desc = request.descriptor();
this.rawNoId = rawNoId;
this.disableLazyLoad = request.query().isDisableLazyLoading();
this.readOnly = Boolean.TRUE.equals(request.query().isReadOnly());
this.unmodifiable = request.query().isUnmodifiable();
this.query = null;
this.subQuery = false;
this.distinctOnPlatform = false;
@@ -72,7 +72,7 @@ public final class SqlTreeBuilder {
this.manyWhereJoins = null;
this.alias = null;
this.ctx = null;
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, readOnly, null);
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, unmodifiable, null);
this.rootNode = buildRootNode(desc);
}
@@ -88,7 +88,7 @@ public final class SqlTreeBuilder {
this.query = request.query();
this.temporalMode = SpiQuery.TemporalMode.of(query);
this.disableLazyLoad = query.isDisableLazyLoading();
this.readOnly = Boolean.TRUE.equals(query.isReadOnly());
this.unmodifiable = query.isUnmodifiable();
this.subQuery = Type.SQ_EXISTS == query.type()
|| Type.SQ_EX == query.type()
|| Type.ID_LIST == query.type()
@@ -101,7 +101,7 @@ public final class SqlTreeBuilder {
this.alias = new SqlTreeAlias(request.baseTableAlias(), temporalMode);
this.distinctOnPlatform = builder.isPlatformDistinctOn();
this.platformDistinctNoLobs = builder.isPlatformDistinctNoLobs();
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, readOnly, includeJoin);
this.common = new SqlTreeCommon(temporalMode, disableLazyLoad, unmodifiable, includeJoin);
this.rootNode = buildRootNode(desc);
String fromForUpdate = builder.fromForUpdate(query);
CQueryHistorySupport historySupport = builder.historySupport(query);
@@ -7,13 +7,13 @@ final class SqlTreeCommon {
private final SpiQuery.TemporalMode temporalMode;
private final boolean disableLazyLoad;
private final boolean readOnly;
private final boolean unmodifiable;
private final TableJoin includeJoin;
SqlTreeCommon(SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean readOnly, TableJoin includeJoin) {
SqlTreeCommon(SpiQuery.TemporalMode temporalMode, boolean disableLazyLoad, boolean unmodifiable, TableJoin includeJoin) {
this.temporalMode = temporalMode;
this.disableLazyLoad = disableLazyLoad;
this.readOnly = readOnly;
this.unmodifiable = unmodifiable;
this.includeJoin = includeJoin;
}
@@ -25,8 +25,8 @@ final class SqlTreeCommon {
return disableLazyLoad;
}
boolean readOnly() {
return readOnly;
boolean unmodifiable() {
return unmodifiable;
}
TableJoin includeJoin() {
@@ -31,7 +31,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
private final boolean readOnlyNoIntercept;
private final boolean unmodifiable;
private final InheritInfo inheritInfo;
final String prefix;
private final Map<String, String> pathMap;
@@ -54,7 +54,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;
this.readOnlyNoIntercept = disableLazyLoad && node.readOnly;
this.unmodifiable = node.unmodifiable;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
@@ -111,7 +111,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
localIdBinder = idBinder;
localDesc = desc;
} else {
localBean = localInfo.createEntityBean();
localBean = localInfo.createEntityBean(unmodifiable);
localType = localInfo.getType();
localIdBinder = localInfo.getIdBinder();
localDesc = localInfo.desc();
@@ -162,7 +162,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
void initBeanType() throws SQLException {
localDesc = desc;
localBean = desc.createEntityBean2(readOnlyNoIntercept);
localBean = desc.createEntityBean2(unmodifiable);
localIdBinder = idBinder;
}
@@ -224,7 +224,6 @@ class SqlTreeLoadBean implements SqlTreeLoad {
private void initSqlLoadBean() {
ctx.setCurrentPrefix(prefix, pathMap);
ctx.propagateState(localBean);
sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);
}
@@ -302,9 +301,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != loadingChildProperty) {
if (readOnlyNoIntercept) {
many.createEmptyReference(localBean);
} else {
if (!unmodifiable || ctx.includeSecondary(many.asMany())) {
// create a proxy for the many (deferred fetching)
BeanCollection<?> ref = many.createReference(localBean, forceNewReference);
if (ref != null) {
@@ -38,7 +38,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
final boolean readId;
final boolean readIdNormal;
final boolean disableLazyLoad;
final boolean readOnly;
final boolean unmodifiable;
final InheritInfo inheritInfo;
final String prefix;
final Map<String, String> pathMap;
@@ -92,7 +92,7 @@ class SqlTreeNodeBean implements SqlTreeNode {
this.readId = !aggregationRoot && withId && desc.hasId();
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = common.disableLazyLoad() || !readIdNormal || desc.isRawSqlBased();
this.readOnly = common.readOnly();
this.unmodifiable = common.unmodifiable();
this.partialObject = props.isPartialObject();
this.properties = props.props();
this.children = myChildren == null ? Collections.emptyList() : myChildren;
@@ -132,7 +132,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private boolean usageProfiling = true;
private CacheMode useBeanCache = CacheMode.AUTO;
private CacheMode useQueryCache = CacheMode.OFF;
private Boolean readOnly;
private boolean unmodifiable;
private PersistenceContextScope persistenceContextScope;
/**
@@ -514,6 +514,10 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
if (!useDocStore) {
createExtraJoinsToSupportManyWhereClause();
}
if (unmodifiable) {
disableLazyLoading = true;
persistenceContextScope = PersistenceContextScope.QUERY;
}
return markQueryJoins();
}
@@ -763,7 +767,7 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
copy.nativeSql = nativeSql;
copy.useBeanCache = useBeanCache;
copy.useQueryCache = useQueryCache;
copy.readOnly = readOnly;
copy.unmodifiable = unmodifiable;
if (detail != null) {
copy.detail = detail.copy();
}
@@ -1113,7 +1117,9 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
if (allowLoadErrors) {
sb.append("/ae");
}
if (disableLazyLoading) {
if (unmodifiable) {
sb.append("/um");
} else if (disableLazyLoading) {
sb.append("/dl");
}
if (baseTable != null) {
@@ -1277,14 +1283,14 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
}
@Override
public final Boolean isReadOnly() {
return readOnly;
public Query<T> setUnmodifiable(boolean unmodifiable) {
this.unmodifiable = unmodifiable;
return this;
}
@Override
public final Query<T> setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
public boolean isUnmodifiable() {
return unmodifiable;
}
@Override
@@ -1328,6 +1334,9 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
@Override
public final Query<T> setUseQueryCache(CacheMode useQueryCache) {
this.useQueryCache = useQueryCache;
if (CacheMode.OFF != useQueryCache) {
unmodifiable = true;
}
return this;
}
@@ -7,6 +7,7 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.core.ClockService;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
@@ -29,11 +30,13 @@ public final class TransactionManagerOptions {
final SpiLogManager logManager;
final TableModState tableModState;
final ServerCacheNotify cacheNotify;
final ClockService clockService;
public TransactionManagerOptions(SpiServer server, boolean notifyL2CacheInForeground, DatabaseBuilder.Settings config, TransactionScopeManager scopeManager,
ClusterManager clusterManager, BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler,
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify) {
SpiLogManager logManager, TableModState tableModState, ServerCacheNotify cacheNotify, ClockService clockService) {
this.server = server;
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
@@ -47,6 +50,7 @@ public final class TransactionManagerOptions {
this.logManager = logManager;
this.tableModState = tableModState;
this.cacheNotify = cacheNotify;
this.clockService = clockService;
}
}
@@ -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());
}

Some files were not shown because too many files have changed in this diff Show More