Compare commits

..
Author SHA1 Message Date
Rob Bygrave 93ae5f2b7c ToMany collections that are not loaded on freeze are marked unloaded
They are marked as unloaded which means accessing the collection will
throw a PersistenceException.

e.g. customer.getOrders() will throw PersistenceException rather than
return an empty list.
2024-01-30 22:32:49 +13:00
Rob Bygrave bc302b19db Experimental Feature - Freeze and detach beans (and attach)
- Frozen beans as ReadOnly + Unmodifiable collections
- Add Transaction.freezeAndDetach() returning FrozenBeans
- Add Transaction.attach( FrozenBeans )

- InterceptReadOnly now supports lazy loading
- Query.setReadOnly(true) now uses InterceptReadOnly [when it used to ALSO require Query.setDisableLazyLoading(true)}
- Add EntityBeanIntercept.errorOnLazyLoad (such that lazy loading on frozen bean is an error)

- BeanList, BeanSet, BeanMap are now freezable
- ModifyAwareList Set and Map are now freezable
2024-01-30 20:40:18 +13:00
125 changed files with 1277 additions and 3257 deletions
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
java_version: [21]
java_version: [11]
os: [ubuntu-latest]
steps:
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<artifactId>composites</artifactId>
+1 -48
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean api</name>
@@ -126,53 +126,6 @@
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>11</release>
</configuration>
</execution>
<execution>
<id>compile-21</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>21</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/src/main/java21</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
<!-- <manifest>-->
<!-- <addDefaultImplementationEntries>true</addDefaultImplementationEntries>-->
<!-- </manifest>-->
</plugin>
</plugins>
</build>
</project>
@@ -1,104 +0,0 @@
package io.ebean;
final class DInsertOptionsBuilder implements InsertOptions.Builder {
private Boolean getGeneratedKeys;
private boolean onConflictUpdate;
private boolean onConflictNothing;
private String constraint;
private String uniqueColumns;
private String updateSet;
@Override
public InsertOptions.Builder onConflictNothing() {
this.onConflictNothing = true;
return this;
}
@Override
public InsertOptions.Builder onConflictUpdate() {
this.onConflictUpdate = true;
return this;
}
@Override
public InsertOptions.Builder constraint(String constraint) {
this.constraint = constraint;
return this;
}
@Override
public InsertOptions.Builder uniqueColumns(String uniqueColumns) {
this.uniqueColumns = uniqueColumns;
return this;
}
@Override
public InsertOptions.Builder updateSet(String updateSet) {
this.updateSet = updateSet;
return this;
}
@Override
public InsertOptions.Builder getGeneratedKeys(boolean getGeneratedKeys) {
this.getGeneratedKeys = getGeneratedKeys;
return this;
}
@Override
public InsertOptions build() {
return new Options(constraint, uniqueColumns, updateSet, onConflictUpdate, onConflictNothing, getGeneratedKeys);
}
static final class Options implements InsertOptions {
private static final String UPDATE = "U";
private static final String NOTHING = "N";
private static final String NORMAL = "_";
private final String key;
private final Boolean getGeneratedKeys;
private final String constraint;
private final String uniqueColumns;
private final String updateSet;
Options(String constraint, String uniqueColumns, String updateSet, boolean onConflictUpdate, boolean onConflictNothing, Boolean getGeneratedKeys) {
this.constraint = constraint;
this.uniqueColumns = uniqueColumns;
this.updateSet = updateSet;
this.getGeneratedKeys = getGeneratedKeys;
this.key = (onConflictUpdate ? UPDATE : onConflictNothing ? NOTHING : NORMAL)
+ '+' + plus(constraint)
+ '+' + plus(uniqueColumns)
+ '+' + plus(updateSet);
}
private String plus(String val) {
return val == null ? "" : val;
}
@Override
public String key() {
return key;
}
@Override
public String constraint() {
return constraint;
}
@Override
public String uniqueColumns() {
return uniqueColumns;
}
@Override
public String updateSet() {
return updateSet;
}
@Override
public Boolean getGetGeneratedKeys() {
return getGeneratedKeys;
}
}
}
@@ -1227,53 +1227,22 @@ public interface Database {
*/
void insert(Object bean);
/**
* Insert the bean with options (ON CONFLICT DO UPDATE | DO NOTHING).
* <p>
* Currently, this is limited to use with Postgres only,
* <p>
* When using this ebean will look to determine the unique columns by looking at
* the mapping like {@code @Column(unique=true} and {@code @Index(unique=true}.
*/
void insert(Object bean, InsertOptions insertOptions);
/**
* Insert the bean with a transaction.
*/
void insert(Object bean, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insert(Object bean, InsertOptions insertOptions, Transaction transaction);
/**
* Insert a collection of beans. If there is no current transaction one is created and used to
* insert all the beans in the collection.
*/
void insertAll(Collection<?> beans);
/**
* Insert the beans with options - typically ON CONFLICT DO UPDATE | DO NOTHING.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options);
/**
* Insert a collection of beans with an explicit transaction.
*/
void insertAll(Collection<?> beans, Transaction transaction);
/**
* Insert the beans with options (ON CONFLICT DO UPDATE | DO NOTHING) and transaction.
* <p>
* Currently, this is limited to use with Postgres only,
*/
void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction);
/**
* Execute explicitly passing a transaction.
*/
@@ -1,119 +0,0 @@
package io.ebean;
import io.avaje.lang.Nullable;
/**
* Options to be used with insert such as ON CONFLICT DO UPDATE | NOTHING.
*/
public interface InsertOptions {
/**
* Use ON CONFLICT UPDATE with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_UPDATE = InsertOptions.builder()
.onConflictUpdate()
.build();
/**
* Use ON CONFLICT DO NOTHING with automatic determination of the unique columns to conflict on.
* <p>
* Uses mapping to determine the unique columns - {@code @Column(unique=true)} and {@code @Index(unique=true)} .
*/
InsertOptions ON_CONFLICT_NOTHING = InsertOptions.builder()
.onConflictNothing()
.build();
/**
* Return a builder for InsertOptions.
*/
static Builder builder() {
return new DInsertOptionsBuilder();
}
/**
* Return the constraint name that is used for ON CONFLICT.
*/
@Nullable
String constraint();
/**
* Return the unique columns that is used for ON CONFLICT.
* <p>
* When not explicitly set will use mapping like {@code @Column(unique=true)} to determine the
* non-unique columns.
*/
@Nullable
String uniqueColumns();
/**
* Return the ON CONFLICT UPDATE SET clause.
* <p>
* When not set will use the non-unique columns.
*/
@Nullable
String updateSet();
/**
* Return if GetGeneratedKeys should be used to fetch the generated keys after insert.
*/
@Nullable
Boolean getGetGeneratedKeys();
/**
* Return the key for these build options.
*/
String key();
/**
* The builder for InsertOptions.
*/
interface Builder {
/**
* Use a ON CONFLICT UPDATE automatically determining the unique columns.
*/
Builder onConflictUpdate();
/**
* Use a ON CONFLICT DO NOTHING automatically determining the unique columns.
*/
Builder onConflictNothing();
/**
* Specify an explicit conflict constraint name.
* <p>
* When this is used then unique columns will not be used.
*/
Builder constraint(String constraint);
/**
* Specify the unique columns for the conflict target.
* <p>
* When not specified and constraint is also not specified then
* it will automatically determine the unique columns
* based on mapping like {@code @Column(unique=true)} and
* {@code @Index(unique=true)} .
*/
Builder uniqueColumns(String uniqueColumns);
/**
* Specify the ON CONFLICT DO UPDATE SET clause.
* <p>
* When not specified ebean will include all the non-unique columns.
*/
Builder updateSet(String updateSet);
/**
* Specify if GetGeneratedKeys should be used to return generated keys.
*/
Builder getGeneratedKeys(boolean getGeneratedKeys);
/**
* Build and return the insert options.
*/
InsertOptions build();
}
}
@@ -16,4 +16,9 @@ public interface ModifyAwareType {
*/
void setMarkedDirty(boolean markedDirty);
/**
* Return a unmodifiable version of this type.
*/
Object freeze();
}
@@ -2,6 +2,7 @@ package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.bean.FrozenBeans;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.DocStoreConfig;
@@ -54,6 +55,24 @@ public interface Transaction extends AutoCloseable {
*/
int SERIALIZABLE = java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* Experimental Feature - Freeze the beans in the persistence context and detach them.
*/
@Deprecated(since = "Experimental Feature")
default FrozenBeans freezeAndDetach() {
throw new UnsupportedOperationException();
}
/**
* Experimental Feature - Attach frozen beans to the persistence context so that they can be
* used with ORM queries (with the persistence context acting like a cache).
*/
@Deprecated(since = "Experimental Feature")
default void attach(FrozenBeans frozenBeans) {
throw new UnsupportedOperationException();
}
/**
* Register a TransactionCallback with this transaction.
*/
@@ -35,6 +35,11 @@ public interface BeanCollection<E> extends Serializable, ToStringAware {
ALL
}
/**
* Return a unmodifiable collection of the underlying entities.
*/
Object freeze();
/**
* Set the disableLazyLoad state.
*/
@@ -552,4 +552,16 @@ public interface EntityBeanIntercept extends Serializable {
* Update the 'next' mutable info returning the content that was obtained via dirty detection.
*/
String mutableNext(int propertyIndex);
/**
* Set that lazy loading if invoked throws a PersistenceException.
*/
void errorOnLazyLoad(boolean lazyLoadAsError);
/**
* Freeze the intercept so that it is readOnly.
* Lazy loading and mutation of the bean is not allowed.
*/
void freeze();
}
@@ -0,0 +1,9 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Holds entity beans that are frozen.
*/
public interface FrozenBeans extends Serializable {
}
@@ -0,0 +1,343 @@
package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceException;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Base EntityBeanIntercept that supports partial loaded state and lazy loading.
*/
abstract class InterceptBase implements EntityBeanIntercept {
/**
* Flag for loaded property.
*/
static final byte FLAG_LOADED_PROP = 1;
/**
* The actual entity bean that 'owns' this intercept.
*/
protected final EntityBean owner;
protected final byte[] flags;
protected boolean readOnly;
protected boolean disableLazyLoad;
protected boolean errorOnLazyLoad;
private final ReentrantLock lock = new ReentrantLock();
private String ebeanServerName;
private transient BeanLoader beanLoader;
private transient PersistenceContext persistenceContext;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
protected boolean lazyLoadFailure;
protected boolean fullyLoadedBean;
protected int lazyLoadProperty = -1;
protected Object ownerId;
/**
* Create with a given entity.
*/
InterceptBase(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.flags = new byte[owner._ebean_getPropertyNames().length];
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
InterceptBase() {
this.owner = null;
this.flags = null;
}
public void freeze() {
this.errorOnLazyLoad = true;
this.readOnly = true;
this.beanLoader = null;
this.persistenceContext = null;
}
@Override
public final EntityBean owner() {
return owner;
}
@Override
public final Object ownerId() {
return ownerId;
}
@Override
public final void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
@Override
public final void setBeanLoader(BeanLoader beanLoader) {
this.beanLoader = beanLoader;
this.ebeanServerName = beanLoader.name();
}
@Override
public final void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
this.beanLoader = beanLoader;
this.persistenceContext = ctx;
this.ebeanServerName = beanLoader.name();
}
@Override
public final PersistenceContext persistenceContext() {
return persistenceContext;
}
@Override
public final void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
}
@Override
public final boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public final void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public final boolean isPartial() {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
return false;
}
@Override
public final boolean hasIdOnly(int idIndex) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
return true;
}
@Override
public final boolean isReadOnly() {
return readOnly;
}
@Override
public final void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public final void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
@Override
public final boolean isLazyLoadFailure() {
return lazyLoadFailure;
}
@Override
public final boolean isDisableLazyLoad() {
return disableLazyLoad;
}
@Override
public final void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
}
@Override
public final void errorOnLazyLoad(boolean errorOnLazyLoad) {
this.errorOnLazyLoad = errorOnLazyLoad;
}
@Override
public final void setEmbeddedLoaded(Object embeddedBean) {
if (embeddedBean instanceof EntityBean) {
((EntityBean) embeddedBean)._ebean_getIntercept().setLoaded();
}
}
@Override
public final int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public final String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public final int propertyLength() {
return flags.length;
}
@Override
public final void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
@Override
public final void setPropertyUnloaded(int propertyIndex) {
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
@Override
public final void initialisedMany(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public final void setLoadedProperty(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public final void setLoadedPropertyAll() {
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@Override
public final boolean isLoadedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
@Override
public final Set<String> loadedPropertyNames() {
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(property(i));
}
}
return props;
}
@Override
public final StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
@Override
public final boolean[] loaded() {
final boolean[] ret = new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
@Override
public final int lazyLoadPropertyIndex() {
return lazyLoadProperty;
}
@Override
public final String lazyLoadProperty() {
return property(lazyLoadProperty);
}
@Override
public final void loadBean(int loadProperty) {
lock.lock();
try {
if (errorOnLazyLoad) {
throw new PersistenceException("Lazy loading not allowed on this bean");
}
if (beanLoader == null) {
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException(ebeanServerName == null ? "No registered default server" : "Database [" + ebeanServerName + "] is not registered");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
}
} finally {
lock.unlock();
}
final Lock lock = beanLoader.lock();
try {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
loadBeanInternal(loadProperty, beanLoader);
} finally {
lock.unlock();
}
}
@Override
public final void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
if (lazyLoadProperty == -1) {
lazyLoadProperty = loadProperty;
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
}
}
@@ -10,55 +10,110 @@ import java.util.Set;
* EntityBeanIntercept optimised for read only use.
* <p>
* For the read only use this intercept doesn't need to hold any state that is normally
* required for updates such as per property changed, loaded, dirty state, original values
* required for updates such as per property changed, dirty state, original values
* bean state etc.
*/
public class InterceptReadOnly implements EntityBeanIntercept {
private final EntityBean owner;
public final class InterceptReadOnly extends InterceptBase {
/**
* Create with a given entity.
*/
public InterceptReadOnly(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
super(ownerBean);
}
@Override
public String toString() {
return "InterceptReadOnly{" + owner + '}';
public void preGetId() {
// do nothing
}
@Override
public EntityBean owner() {
return owner;
public void preGetter(int propertyIndex) {
if (disableLazyLoad) {
return;
}
if (!isLoadedProperty(propertyIndex)) {
loadBean(propertyIndex);
}
}
private void preSetter(int propertyIndex) {
if (readOnly) {
throw new IllegalStateException("This bean is readOnly");
}
setLoadedProperty(propertyIndex);
}
@Override
public PersistenceContext persistenceContext() {
return null;
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
preSetter(propertyIndex);
}
@Override
public void setPersistenceContext(PersistenceContext persistenceContext) {
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
preSetter(propertyIndex);
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
preSetter(propertyIndex);
}
// ------------------------------------------------------------------------------------
// The following method have "Do Nothing" behaviour as not interested in mutation state
// ------------------------------------------------------------------------------------
@Override
public void setNodeUsageCollector(NodeUsageCollector usageCollector) {
}
@Override
public Object ownerId() {
return null;
}
@Override
public void setOwnerId(Object ownerId) {
}
@Override
public Object embeddedOwner() {
return null;
@@ -84,31 +139,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
}
@Override
public void setBeanLoader(BeanLoader beanLoader) {
}
@Override
public boolean isFullyLoadedBean() {
return false;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
}
@Override
public boolean isPartial() {
return false;
}
@Override
public boolean isDirty() {
return false;
@@ -134,11 +164,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return false;
}
@Override
public boolean hasIdOnly(int idIndex) {
return false;
}
@Override
public boolean isReference() {
return false;
@@ -159,16 +184,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return false;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean readOnly) {
}
@Override
public void setForceUpdate(boolean forceUpdate) {
@@ -199,31 +214,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void setLazyLoadFailure(Object ownerId) {
}
@Override
public boolean isLazyLoadFailure() {
return false;
}
@Override
public boolean isDisableLazyLoad() {
return false;
}
@Override
public void setDisableLazyLoad(boolean disableLazyLoad) {
}
@Override
public void setEmbeddedLoaded(Object embeddedBean) {
}
@Override
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
return false;
@@ -234,45 +224,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
return null;
}
@Override
public int findProperty(String propertyName) {
return 0;
}
@Override
public String property(int propertyIndex) {
return null;
}
@Override
public int propertyLength() {
return 0;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
}
@Override
public void setLoadedProperty(int propertyIndex) {
}
@Override
public void setLoadedPropertyAll() {
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return false;
}
@Override
public boolean isChangedProperty(int propertyIndex) {
@@ -319,11 +270,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public Set<String> loadedPropertyNames() {
return Collections.emptySet();
}
@Override
public boolean[] dirtyProperties() {
return new boolean[0];
@@ -369,61 +315,11 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public StringBuilder loadedPropertyKey() {
return null;
}
@Override
public boolean[] loaded() {
return new boolean[0];
}
@Override
public int lazyLoadPropertyIndex() {
return 0;
}
@Override
public String lazyLoadProperty() {
return null;
}
@Override
public void loadBean(int loadProperty) {
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
}
@Override
public void initialisedMany(int propertyIndex) {
}
@Override
public void preGetterCallback(int propertyIndex) {
}
@Override
public void preGetId() {
}
@Override
public void preGetter(int propertyIndex) {
}
@Override
public void preSetterMany(boolean interceptField, int propertyIndex, Object oldValue, Object newValue) {
}
@Override
public void setChangedPropertyValue(int propertyIndex, boolean setDirtyState, Object origValue) {
@@ -434,61 +330,6 @@ public class InterceptReadOnly implements EntityBeanIntercept {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, Object oldValue, Object newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, boolean oldValue, boolean newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, int oldValue, int newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, long oldValue, long newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, double oldValue, double newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, float oldValue, float newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, short oldValue, short newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char oldValue, char newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte oldValue, byte newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, char[] oldValue, char[] newValue) {
}
@Override
public void preSetter(boolean intercept, int propertyIndex, byte[] oldValue, byte[] newValue) {
}
@Override
public void setOldValue(int propertyIndex, Object oldValue) {
@@ -1,11 +1,7 @@
package io.ebean.bean;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.ValuePair;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.PersistenceException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -13,8 +9,6 @@ import java.io.InputStream;
import java.math.BigDecimal;
import java.net.URL;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* This is the object added to every entity bean using byte code enhancement.
@@ -22,7 +16,7 @@ import java.util.concurrent.locks.ReentrantLock;
* This provides the mechanisms to support deferred fetching of reference beans
* and oldValues generation for concurrency checking.
*/
public final class InterceptReadWrite implements EntityBeanIntercept {
public final class InterceptReadWrite extends InterceptBase {
private static final long serialVersionUID = -3664031775464862649L;
@@ -30,10 +24,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
private static final int STATE_REFERENCE = 1;
private static final int STATE_LOADED = 2;
/**
* Used when a bean is partially loaded.
*/
private static final byte FLAG_LOADED_PROP = 1;
private static final byte FLAG_CHANGED_PROP = 2;
private static final byte FLAG_CHANGEDLOADED_PROP = 3;
/**
@@ -47,43 +37,21 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
*/
private static final byte FLAG_MUTABLE_HASH_SET = 16;
private final ReentrantLock lock = new ReentrantLock();
private transient NodeUsageCollector nodeUsageCollector;
private transient PersistenceContext persistenceContext;
private transient BeanLoader beanLoader;
private transient PreGetterCallback preGetterCallback;
private String ebeanServerName;
private boolean deletedFromCollection;
/**
* The actual entity bean that 'owns' this intercept.
*/
private final EntityBean owner;
private EntityBean embeddedOwner;
private int embeddedOwnerIndex;
/**
* One of NEW, REF, UPD.
* One of NEW, REF, LOADED.
*/
private int state;
private boolean deletedFromCollection;
private boolean forceUpdate;
private boolean readOnly;
private boolean dirty;
/**
* Flag set to disable lazy loading.
*/
private boolean disableLazyLoad;
/**
* Flag set when lazy loading failed due to the underlying bean being deleted in the DB.
*/
private boolean lazyLoadFailure;
private boolean fullyLoadedBean;
private boolean loadedFromCache;
private final byte[] flags;
private Object[] origValues;
private Exception[] loadErrors;
private int lazyLoadProperty = -1;
private Object ownerId;
private int sortOrder;
/**
@@ -101,49 +69,14 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
* Create with a given entity.
*/
public InterceptReadWrite(Object ownerBean) {
this.owner = (EntityBean) ownerBean;
this.flags = new byte[owner._ebean_getPropertyNames().length];
super(ownerBean);
}
/**
* EXPERIMENTAL - Constructor only for use by serialization frameworks.
*/
public InterceptReadWrite() {
this.owner = null;
this.flags = null;
}
@Override
public String toString() {
return "InterceptReadWrite@" + hashCode() + "{state=" + state +
(dirty ? " dirty;" : "") +
(forceUpdate ? " forceUpdate;" : "") +
(readOnly ? " readOnly;" : "") +
(disableLazyLoad ? " disableLazyLoad;" : "") +
(lazyLoadFailure ? " lazyLoadFailure;" : "") +
(fullyLoadedBean ? " fullyLoadedBean;" : "") +
(loadedFromCache ? " loadedFromCache;" : "") +
", pc=" + System.identityHashCode(persistenceContext) +
", flags=" + Arrays.toString(flags) +
(lazyLoadProperty > -1 ? (", lazyLoadProperty=" + lazyLoadProperty) : "") +
", loader=" + beanLoader +
(ownerId != null ? (", ownerId=" + ownerId) : "") +
'}';
}
@Override
public EntityBean owner() {
return owner;
}
@Override
public PersistenceContext persistenceContext() {
return persistenceContext;
}
@Override
public void setPersistenceContext(PersistenceContext persistenceContext) {
this.persistenceContext = persistenceContext;
super();
}
@Override
@@ -151,16 +84,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.nodeUsageCollector = usageCollector;
}
@Override
public Object ownerId() {
return ownerId;
}
@Override
public void setOwnerId(Object ownerId) {
this.ownerId = ownerId;
}
@Override
public Object embeddedOwner() {
return embeddedOwner;
@@ -187,39 +110,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.embeddedOwnerIndex = embeddedOwnerIndex;
}
@Override
public void setBeanLoader(BeanLoader beanLoader, PersistenceContext ctx) {
this.beanLoader = beanLoader;
this.persistenceContext = ctx;
this.ebeanServerName = beanLoader.name();
}
@Override
public void setBeanLoader(BeanLoader beanLoader) {
this.beanLoader = beanLoader;
this.ebeanServerName = beanLoader.name();
}
@Override
public boolean isFullyLoadedBean() {
return fullyLoadedBean;
}
@Override
public void setFullyLoadedBean(boolean fullyLoadedBean) {
this.fullyLoadedBean = fullyLoadedBean;
}
@Override
public boolean isPartial() {
for (byte flag : flags) {
if ((flag & FLAG_LOADED_PROP) == 0) {
return true;
}
}
return false;
}
@Override
public boolean isDirty() {
if (dirty) {
@@ -257,18 +147,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return isNew() || isDirty();
}
@Override
public boolean hasIdOnly(int idIndex) {
for (int i = 0; i < flags.length; i++) {
if (i == idIndex) {
if ((flags[i] & FLAG_LOADED_PROP) == 0) return false;
} else if ((flags[i] & FLAG_LOADED_PROP) != 0) {
return false;
}
}
return true;
}
@Override
public boolean isReference() {
return state == STATE_REFERENCE;
@@ -298,16 +176,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return loadedFromCache;
}
@Override
public boolean isReadOnly() {
return readOnly;
}
@Override
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
@Override
public void setForceUpdate(boolean forceUpdate) {
this.forceUpdate = forceUpdate;
@@ -356,34 +224,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
this.lazyLoadProperty = -1;
}
@Override
public void setLazyLoadFailure(Object ownerId) {
this.lazyLoadFailure = true;
this.ownerId = ownerId;
}
@Override
public boolean isLazyLoadFailure() {
return lazyLoadFailure;
}
@Override
public boolean isDisableLazyLoad() {
return disableLazyLoad;
}
@Override
public void setDisableLazyLoad(boolean disableLazyLoad) {
this.disableLazyLoad = disableLazyLoad;
}
@Override
public void setEmbeddedLoaded(Object embeddedBean) {
if (embeddedBean instanceof EntityBean) {
((EntityBean) embeddedBean)._ebean_getIntercept().setLoaded();
}
}
@Override
public boolean isEmbeddedNewOrDirty(Object embeddedBean) {
if (embeddedBean == null) {
@@ -411,65 +251,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
return origValues[propertyIndex];
}
@Override
public int findProperty(String propertyName) {
final String[] names = owner._ebean_getPropertyNames();
for (int i = 0; i < names.length; i++) {
if (names[i].equals(propertyName)) {
return i;
}
}
return -1;
}
@Override
public String property(int propertyIndex) {
if (propertyIndex == -1) {
return null;
}
return owner._ebean_getPropertyName(propertyIndex);
}
@Override
public int propertyLength() {
return flags.length;
}
@Override
public void setPropertyLoaded(String propertyName, boolean loaded) {
final int position = findProperty(propertyName);
if (position == -1) {
throw new IllegalArgumentException("Property " + propertyName + " not found");
}
if (loaded) {
flags[position] |= FLAG_LOADED_PROP;
} else {
flags[position] &= ~FLAG_LOADED_PROP;
}
}
@Override
public void setPropertyUnloaded(int propertyIndex) {
flags[propertyIndex] &= ~FLAG_LOADED_PROP;
}
@Override
public void setLoadedProperty(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public void setLoadedPropertyAll() {
for (int i = 0; i < flags.length; i++) {
flags[i] |= FLAG_LOADED_PROP;
}
}
@Override
public boolean isLoadedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_LOADED_PROP) != 0;
}
@Override
public boolean isChangedProperty(int propertyIndex) {
return (flags[propertyIndex] & FLAG_CHANGED_PROP) != 0;
@@ -530,20 +311,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
setDirty(true);
}
@Override
public Set<String> loadedPropertyNames() {
if (fullyLoadedBean) {
return null;
}
final Set<String> props = new LinkedHashSet<>();
for (int i = 0; i < flags.length; i++) {
if ((flags[i] & FLAG_LOADED_PROP) != 0) {
props.add(property(i));
}
}
return props;
}
@Override
public boolean[] dirtyProperties() {
final int len = propertyLength();
@@ -668,86 +435,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public StringBuilder loadedPropertyKey() {
final StringBuilder sb = new StringBuilder();
final int len = propertyLength();
for (int i = 0; i < len; i++) {
if (isLoadedProperty(i)) {
sb.append(i).append(',');
}
}
return sb;
}
@Override
public boolean[] loaded() {
final boolean[] ret = new boolean[flags.length];
for (int i = 0; i < ret.length; i++) {
ret[i] = (flags[i] & FLAG_LOADED_PROP) != 0;
}
return ret;
}
@Override
public int lazyLoadPropertyIndex() {
return lazyLoadProperty;
}
@Override
public String lazyLoadProperty() {
return property(lazyLoadProperty);
}
@Override
public void loadBean(int loadProperty) {
lock.lock();
try {
if (beanLoader == null) {
final Database database = DB.byName(ebeanServerName);
if (database == null) {
throw new PersistenceException(ebeanServerName == null ? "No registered default server" : "Database [" + ebeanServerName + "] is not registered");
}
// For stand alone reference bean or after deserialisation lazy load
// using the ebeanServer. Synchronise only on the bean.
loadBeanInternal(loadProperty, database.pluginApi().beanLoader());
return;
}
} finally {
lock.unlock();
}
final Lock lock = beanLoader.lock();
try {
// Lazy loading using LoadBeanContext which supports batch loading
// Synchronise on the beanLoader (a 'node' of the LoadBeanContext 'tree')
loadBeanInternal(loadProperty, beanLoader);
} finally {
lock.unlock();
}
}
@Override
public void loadBeanInternal(int loadProperty, BeanLoader loader) {
if ((flags[loadProperty] & FLAG_LOADED_PROP) != 0) {
// race condition where multiple threads calling preGetter concurrently
return;
}
if (lazyLoadFailure) {
// failed when batch lazy loaded by another bean in the batch
throw new EntityNotFoundException("(Lazy) loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
if (lazyLoadProperty == -1) {
lazyLoadProperty = loadProperty;
loader.loadBean(this);
if (lazyLoadFailure) {
// failed when lazy loading this bean
throw new EntityNotFoundException("Lazy loading failed on type:" + owner.getClass().getName() + " id:" + ownerId + " - Bean has been deleted. BeanLoader: " + beanLoader);
}
// bean should be loaded and intercepting now. setLoaded() has
// been called by the lazy loading mechanism
}
}
/**
* Helper method to check if two objects are equal.
*/
@@ -808,11 +495,6 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
}
}
@Override
public void initialisedMany(int propertyIndex) {
flags[propertyIndex] |= FLAG_LOADED_PROP;
}
@Override
public void preGetterCallback(int propertyIndex) {
PreGetterCallback preGetterCallback = this.preGetterCallback;
@@ -1076,4 +758,16 @@ public final class InterceptReadWrite implements EntityBeanIntercept {
final MutableValueNext next = mutableNext[propertyIndex];
return next != null ? next.content() : null;
}
@Override
public void freeze() {
super.freeze();
// not allowed to be used for updates
this.dirty = false;
this.forceUpdate = false;
this.deletedFromCollection = false;
this.origValues = null;
this.nodeUsageCollector = null;
this.embeddedOwner = null;
}
}
@@ -44,6 +44,12 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
builder.addCollection(list);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return list == null ? null : Collections.unmodifiableList(list);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
@@ -141,16 +147,14 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
}
}
public BeanCollectionAdd collectionAdd() {
if (list == null) {
list = new ArrayList<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanList<E> newList) {
setModifyListening(modifyListenMode);
this.list = newList.actualList();
/**
* Set the actual underlying list.
* <p>
* This is primarily for the deferred fetching function.
*/
@SuppressWarnings("unchecked")
public void setActualList(List<?> list) {
this.list = (List<E>) list;
}
/**
@@ -1,6 +1,9 @@
package io.ebean.common;
import io.ebean.bean.*;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ToStringBuilder;
import java.util.*;
@@ -14,12 +17,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
/**
* The underlying map implementation.
*/
private LinkedHashMap<K, E> map;
private Map<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(LinkedHashMap<K, E> map) {
public BeanMap(Map<K, E> map) {
this.map = map;
}
@@ -34,6 +37,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
super(ebeanServer, ownerBean, propertyName);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return map == null ? null : Collections.unmodifiableMap(map);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
@@ -157,23 +166,18 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
}
}
public LinkedHashMap<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
/**
* Set the actual underlying map. Used for performing lazy fetch.
*/
@SuppressWarnings("unchecked")
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
public void setActualMap(Map<?, ?> map) {
this.map = (Map<K, E>) map;
}
/**
* Return the actual underlying map.
*/
public LinkedHashMap<K, E> actualMap() {
public Map<K, E> actualMap() {
return map;
}
@@ -15,12 +15,12 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
/**
* The underlying Set implementation.
*/
private LinkedHashSet<E> set;
private Set<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(LinkedHashSet<E> set) {
public BeanSet(Set<E> set) {
this.set = set;
}
@@ -40,6 +40,12 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
builder.addCollection(set);
}
@Override
public Object freeze() {
// null -> illegal to access reference collection
return set == null ? null : Collections.unmodifiableSet(set);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
@@ -141,22 +147,18 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
}
}
public BeanCollectionAdd collectionAdd() {
if (set == null) {
set = new LinkedHashSet<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanSet<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
/**
* Set the underlying set (used for lazy fetch).
*/
@SuppressWarnings("unchecked")
public void setActualSet(Set<?> set) {
this.set = (Set<E>) set;
}
/**
* Return the actual underlying set.
*/
public LinkedHashSet<E> actualSet() {
public Set<E> actualSet() {
return set;
}
@@ -1,450 +0,0 @@
package io.ebean.common;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.ToStringBuilder;
import java.util.*;
/**
* Map capable of lazy loading and modification aware.
*/
public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements SequencedMap<K, E> {
private static final long serialVersionUID = 1L;
/**
* The underlying map implementation.
*/
private LinkedHashMap<K, E> map;
/**
* Create with a given Map.
*/
public BeanMap(LinkedHashMap<K, E> map) {
this.map = map;
}
/**
* Create using a underlying LinkedHashMap.
*/
public BeanMap() {
this(new LinkedHashMap<>());
}
public BeanMap(BeanCollectionLoader ebeanServer, EntityBean ownerBean, String propertyName) {
super(ebeanServer, ownerBean, propertyName);
}
@Override
public void toString(ToStringBuilder builder) {
if (map == null || map.isEmpty()) {
builder.addRaw("{}");
} else {
builder.addRaw("{");
for (Entry<K, E> entry : map.entrySet()) {
builder.add(String.valueOf(entry.getKey()), entry.getValue());
}
builder.addRaw("}");
}
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.map = null;
}
@Override
public boolean isSkipSave() {
return map == null || (map.isEmpty() && !holdsModifications());
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
BeanMap<K, E> otherMap = (BeanMap<K, E>) other;
internalPutNull();
map.putAll(otherMap.actualMap());
}
public void internalPutNull() {
if (map == null) {
map = new LinkedHashMap<>();
}
}
@SuppressWarnings("unchecked")
public void internalPut(Object key, Object bean) {
if (map == null) {
map = new LinkedHashMap<>();
}
if (key != null) {
map.put((K) key, (E) bean);
}
}
public void internalPutWithCheck(Object key, Object bean) {
if (map == null || key == null || !map.containsKey(key)) {
internalPut(key, bean);
}
}
@Override
public void internalAddWithCheck(Object bean) {
throw new RuntimeException("Not allowed for map");
}
@Override
public void internalAdd(Object bean) {
throw new RuntimeException("Not allowed for map");
}
/**
* Return true if the underlying map has been populated. Returns false if it
* has a deferred fetch pending.
*/
@Override
public boolean isPopulated() {
return map != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
@Override
public boolean isReference() {
return map == null;
}
@Override
public boolean checkEmptyLazyLoad() {
if (map == null) {
map = new LinkedHashMap<>();
return true;
} else {
return false;
}
}
private void initClear() {
lock.lock();
try {
if (map == null) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(true);
} else {
map = new LinkedHashMap<>();
}
}
} finally {
lock.unlock();
}
}
private void init() {
lock.lock();
try {
if (map == null) {
if (disableLazyLoad) {
map = new LinkedHashMap<>();
} else {
lazyLoadCollection(false);
}
}
} finally {
lock.unlock();
}
}
public LinkedHashMap<K, E> collectionAdd() {
if (map == null) {
map = new LinkedHashMap<>();
}
return map;
}
@SuppressWarnings("unchecked")
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
setModifyListening(modifyListenMode);
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
}
/**
* Return the actual underlying map.
*/
public LinkedHashMap<K, E> actualMap() {
return map;
}
/**
* Returns the collection of beans (map values).
*/
@Override
public Collection<E> actualDetails() {
return map.values();
}
/**
* Returns the map entrySet.
*/
@Override
public Collection<?> actualEntries() {
return map.entrySet();
}
@Override
public String toString() {
if (map == null) {
return "BeanMap<deferred>";
} else {
return map.toString();
}
}
/**
* Equal if object is a Map and equal in a Map sense.
*/
@Override
public boolean equals(Object object) {
init();
return map.equals(object);
}
@Override
public int hashCode() {
init();
return map.hashCode();
}
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
// add all beans to the removal list
for (E bean : map.values()) {
modifyRemoval(bean);
}
}
map.clear();
}
@Override
public boolean containsKey(Object key) {
init();
return map.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
init();
return map.containsValue(value);
}
@Override
public Set<Entry<K, E>> entrySet() {
init();
if (readOnly) {
return Collections.unmodifiableSet(map.entrySet());
}
return modifyListening ? new ModifyEntrySet<>(this, map.entrySet()) : map.entrySet();
}
@Override
public E get(Object key) {
init();
return map.get(key);
}
@Override
public boolean isEmpty() {
init();
return map.isEmpty();
}
@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);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.put(key, value);
}
}
@Override
public void putAll(Map<? extends K, ? extends E> puts) {
checkReadOnly();
init();
if (modifyListening) {
for (Entry<? extends K, ? extends E> entry : puts.entrySet()) {
Object oldBean = map.put(entry.getKey(), entry.getValue());
if (entry.getValue() != oldBean) {
modifyAddition(entry.getValue());
modifyRemoval(oldBean);
}
}
} else {
map.putAll(puts);
}
}
@Override
public void addBean(E bean) {
throw new IllegalStateException("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.");
}
@Override
public E remove(Object key) {
checkReadOnly();
init();
if (modifyListening) {
E o = map.remove(key);
modifyRemoval(o);
return o;
}
return map.remove(key);
}
@Override
public int size() {
init();
return map.size();
}
@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;
}
@Override
public SequencedMap<K, E> reversed() {
init();
if (modifyListening) {
throw new UnsupportedOperationException("Not supported on modify listening map");
}
return map.reversed();
}
@Override
public Entry<K, E> firstEntry() {
init();
return map.firstEntry();
}
@Override
public Entry<K, E> lastEntry() {
init();
return map.lastEntry();
}
@Override
public Entry<K, E> pollFirstEntry() {
checkReadOnly();
init();
var entry = map.pollFirstEntry();
if (modifyListening && entry != null) {
modifyRemoval(entry.getValue());
}
return entry;
}
@Override
public Entry<K, E> pollLastEntry() {
checkReadOnly();
init();
var entry = map.pollLastEntry();
if (modifyListening && entry != null) {
modifyRemoval(entry.getValue());
}
return entry;
}
@Override
public E putFirst(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.putFirst(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.putFirst(key, value);
}
}
@Override
public E putLast(K key, E value) {
checkReadOnly();
init();
if (modifyListening) {
E oldBean = map.putLast(key, value);
if (value != oldBean) {
// register the add of the new and the removal of the old
modifyAddition(value);
modifyRemoval(oldBean);
}
return oldBean;
} else {
return map.putLast(key, value);
}
}
@Override
public SequencedSet<K> sequencedKeySet() {
init();
return map.sequencedKeySet();
}
@Override
public SequencedCollection<E> sequencedValues() {
init();
return map.sequencedValues();
}
@Override
public SequencedSet<Entry<K, E>> sequencedEntrySet() {
init();
return map.sequencedEntrySet();
}
}
@@ -1,460 +0,0 @@
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.SequencedSet;
/**
* Set capable of lazy loading and modification aware.
*/
public final class BeanSet<E> extends AbstractBeanCollection<E> implements SequencedSet<E>, BeanCollectionAdd {
private static final long serialVersionUID = 1L;
/**
* The underlying Set implementation.
*/
private LinkedHashSet<E> set;
/**
* Create with a specific Set implementation.
*/
public BeanSet(LinkedHashSet<E> set) {
this.set = set;
}
/**
* Create using an underlying LinkedHashSet.
*/
public BeanSet() {
this(new LinkedHashSet<>());
}
public BeanSet(BeanCollectionLoader loader, EntityBean ownerBean, String propertyName) {
super(loader, ownerBean, propertyName);
}
@Override
public void toString(ToStringBuilder builder) {
builder.addCollection(set);
}
@Override
public void reset(EntityBean ownerBean, String propertyName) {
this.ownerBean = ownerBean;
this.propertyName = propertyName;
this.set = null;
}
@Override
public boolean isSkipSave() {
return set == null || (set.isEmpty() && !holdsModifications());
}
@Override
@SuppressWarnings("unchecked")
public void addEntityBean(EntityBean bean) {
set.add((E) bean);
}
@Override
@SuppressWarnings("unchecked")
public void loadFrom(BeanCollection<?> other) {
if (set == null) {
set = new LinkedHashSet<>();
}
set.addAll((Collection<? extends E>) other.actualDetails());
}
@Override
public void internalAddWithCheck(Object bean) {
// set add() already de-dups so just add it
internalAdd(bean);
}
@Override
@SuppressWarnings("unchecked")
public void internalAdd(Object bean) {
if (set == null) {
set = new LinkedHashSet<>();
}
if (bean != null) {
set.add((E) bean);
}
}
/**
* Returns true if the underlying set has its data.
*/
@Override
public boolean isPopulated() {
return set != null;
}
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
@Override
public boolean isReference() {
return set == null;
}
@Override
public boolean checkEmptyLazyLoad() {
if (set == null) {
set = new LinkedHashSet<>();
return true;
} else {
return false;
}
}
private void initClear() {
lock.lock();
try {
if (set == null) {
if (!disableLazyLoad && modifyListening) {
lazyLoadCollection(false);
} else {
set = new LinkedHashSet<>();
}
}
} finally {
lock.unlock();
}
}
private void init() {
lock.lock();
try {
if (set == null) {
if (disableLazyLoad) {
set = new LinkedHashSet<>();
} else {
lazyLoadCollection(false);
}
}
} finally {
lock.unlock();
}
}
public BeanCollectionAdd collectionAdd() {
if (set == null) {
set = new LinkedHashSet<>();
}
return this;
}
public void refresh(ModifyListenMode modifyListenMode, BeanSet<E> newSet) {
setModifyListening(modifyListenMode);
this.set = newSet.actualSet();
}
/**
* Return the actual underlying set.
*/
public LinkedHashSet<E> actualSet() {
return set;
}
@Override
public Collection<E> actualDetails() {
return set;
}
@Override
public Collection<?> actualEntries() {
return set;
}
@Override
public String toString() {
if (set == null) {
return "BeanSet<deferred>";
} else {
return set.toString();
}
}
/**
* Equal if obj is a Set and equal in a Set sense.
*/
@Override
public boolean equals(Object obj) {
init();
return set.equals(obj);
}
@Override
public int hashCode() {
init();
return set.hashCode();
}
@Override
public void addBean(E bean) {
add(bean);
}
@Override
public void removeBean(E bean) {
if (set.remove(bean)) {
getModifyHolder().modifyRemoval(bean);
}
}
// -----------------------------------------------------//
// proxy method for map
// -----------------------------------------------------//
@Override
public boolean add(E bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.add(bean)) {
modifyAddition(bean);
return true;
} else {
return false;
}
}
return set.add(bean);
}
@Override
public boolean addAll(Collection<? extends E> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
for (E bean : beans) {
if (set.add(bean)) {
// register the addition of the bean
modifyAddition(bean);
changed = true;
}
}
return changed;
}
return set.addAll(beans);
}
@Override
public void clear() {
checkReadOnly();
initClear();
if (modifyListening) {
for (E bean : set) {
modifyRemoval(bean);
}
}
set.clear();
}
@Override
public boolean contains(Object bean) {
init();
return set.contains(bean);
}
@Override
public boolean containsAll(Collection<?> beans) {
init();
return set.containsAll(beans);
}
@Override
public boolean isEmpty() {
init();
return set.isEmpty();
}
@Override
public Iterator<E> iterator() {
init();
if (readOnly) {
return new ReadOnlyIterator<>(set.iterator());
}
if (modifyListening) {
return new ModifyIterator<>(this, set.iterator());
}
return set.iterator();
}
@Override
public boolean remove(Object bean) {
checkReadOnly();
init();
if (modifyListening) {
if (set.remove(bean)) {
modifyRemoval(bean);
return true;
}
return false;
}
return set.remove(bean);
}
@Override
public boolean removeAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
for (Object bean : beans) {
if (set.remove(bean)) {
modifyRemoval(bean);
changed = true;
}
}
return changed;
}
return set.removeAll(beans);
}
@Override
public boolean retainAll(Collection<?> beans) {
checkReadOnly();
init();
if (modifyListening) {
boolean changed = false;
Iterator<?> it = set.iterator();
while (it.hasNext()) {
Object bean = it.next();
if (!beans.contains(bean)) {
// not retaining this bean so add it to the removal list
it.remove();
modifyRemoval(bean);
changed = true;
}
}
return changed;
}
return set.retainAll(beans);
}
@Override
public int size() {
init();
return set.size();
}
@Override
public Object[] toArray() {
init();
return set.toArray();
}
@Override
public <T> T[] toArray(T[] array) {
init();
//noinspection SuspiciousToArrayCall
return set.toArray(array);
}
@Override
public SequencedSet<E> reversed() {
init();
if (modifyListening) {
throw new UnsupportedOperationException("Not supported on modify listening set");
}
return set.reversed();
}
@Override
public void addFirst(E bean) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(bean);
}
set.addFirst(bean);
}
@Override
public void addLast(E bean) {
checkReadOnly();
init();
if (modifyListening) {
modifyAddition(bean);
}
set.addLast(bean);
}
@Override
public E getFirst() {
init();
return set.getFirst();
}
@Override
public E getLast() {
init();
return set.getLast();
}
@Override
public E removeFirst() {
checkReadOnly();
init();
if (modifyListening) {
var bean = set.removeFirst();
modifyRemoval(bean);
return bean;
}
return set.removeFirst();
}
@Override
public E removeLast() {
checkReadOnly();
init();
if (modifyListening) {
var bean = set.removeLast();
modifyRemoval(bean);
return bean;
}
return set.removeLast();
}
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;
}
}
@@ -136,13 +136,13 @@ class ToStringBuilderTest {
@Test
void beanSet_null_empty() {
assertThat(toStringFor(new BeanSet<String>(null))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(new LinkedHashSet<>()))).isEqualTo("[]");
assertThat(toStringFor(new BeanSet<String>(Collections.emptySet()))).isEqualTo("[]");
}
@Test
void beanMap_null_empty() {
assertThat(toStringFor(new BeanMap<String, String>(null))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(new LinkedHashMap<>()))).isEqualTo("{}");
assertThat(toStringFor(new BeanMap<String, String>(Collections.emptyMap()))).isEqualTo("{}");
}
@Test
@@ -159,7 +159,7 @@ class ToStringBuilderTest {
@Test
void beanMap_some() {
var under = new LinkedHashMap<String, Recurse>();
Map<String, Recurse> under = new LinkedHashMap<>();
under.put("a", new Recurse(1, "a"));
under.put("b", new Recurse(2, "b"));
BeanMap<String, Recurse> list = new BeanMap<>(under);
+26 -26
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<!-- platforms -->
@@ -193,79 +193,79 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
+6 -6
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,7 +22,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -46,7 +46,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -159,21 +159,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -1,8 +1,11 @@
package io.ebeaninternal.api;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import java.util.List;
import java.util.Map;
/**
* SPI extension to PersistenceContext.
@@ -14,4 +17,14 @@ public interface SpiPersistenceContext extends PersistenceContext {
*/
List<Object> dirtyBeans(SpiBeanTypeManager manager);
/**
* Return all the entities from this persistence context.
*/
Map<Class<?>, List<EntityBean>> detach();
/**
* Attach all the cached entities into this persistence context.
*/
void attach(FrozenBeans cachedBeans);
}
@@ -654,16 +654,6 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
*/
ObjectGraphNode parentNode();
/**
* Set that this is a future query that will execute in the background.
*/
void usingFuture();
/**
* Return true if this is a future query.
*/
boolean isUsingFuture();
/**
* Return false when this is a lazy load or refresh query for a bean.
* <p>
@@ -3,6 +3,7 @@ package io.ebeaninternal.api;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.FrozenBeans;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
import io.ebeaninternal.server.core.PersistDeferredRelationship;
@@ -223,6 +224,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
transaction.register(callback);
}
@Override
public FrozenBeans freezeAndDetach() {
return transaction.freezeAndDetach();
}
@Override
public void attach(FrozenBeans frozenBeans) {
transaction.attach(frozenBeans);
}
@Override
public boolean isReadOnly() {
return transaction.isReadOnly();
@@ -23,4 +23,8 @@ public final class ModifyAwareFlag implements ModifyAwareType, Serializable {
this.markedDirty = markedDirty;
}
@Override
public Object freeze() {
throw new UnsupportedOperationException();
}
}
@@ -3,12 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Objects;
import java.util.*;
/**
* Modify aware wrapper of a list.
@@ -52,6 +47,11 @@ public final class ModifyAwareList<E> implements List<E>, ModifyAwareType, Seria
return list.hashCode();
}
@Override
public List<E> freeze() {
return Collections.unmodifiableList(list);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,11 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Map that is wraps an underlying map for the purpose of detecting changes.
@@ -51,6 +47,11 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
return map.hashCode();
}
@Override
public Map<K, V> freeze() {
return Collections.unmodifiableMap(map);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -3,10 +3,7 @@ package io.ebeaninternal.json;
import io.ebean.ModifyAwareType;
import java.io.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
import java.util.*;
/**
* Wraps a Set for the purposes of detecting modifications.
@@ -33,6 +30,11 @@ public final class ModifyAwareSet<E> implements Set<E>, ModifyAwareType, Seriali
this.set = underlying;
}
@Override
public Set<E> freeze() {
return Collections.unmodifiableSet(set);
}
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
@@ -1273,7 +1273,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureRowCount<T> findFutureCount(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
copy.usingFuture();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
@@ -1292,7 +1291,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureIds<T> findFutureIds(SpiQuery<T> query) {
SpiQuery<T> copy = query.copy();
copy.usingFuture();
boolean createdTransaction = false;
SpiTransaction transaction = query.transaction();
if (transaction == null) {
@@ -1311,7 +1309,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> FutureList<T> findFutureList(SpiQuery<T> query) {
SpiQuery<T> spiQuery = query.copy();
spiQuery.usingFuture();
// FutureList query always run in it's own persistence content
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
if (!spiQuery.isDisableReadAudit()) {
@@ -1654,50 +1651,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}, transaction);
}
/**
* Insert the bean.
*/
@Override
public void insert(Object bean) {
persister.insert(checkEntityBean(bean), null, null);
}
@Override
public void insert(Object bean, @Nullable InsertOptions insertOptions) {
persister.insert(checkEntityBean(bean), insertOptions, null);
insert(bean, null);
}
/**
* Insert the bean with a transaction.
*/
@Override
public void insert(Object bean, @Nullable Transaction transaction) {
persister.insert(checkEntityBean(bean), null, transaction);
}
@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
persister.insert(checkEntityBean(bean), insertOptions, transaction);
persister.insert(checkEntityBean(bean), transaction);
}
/**
* Insert all beans in the collection.
*/
@Override
public void insertAll(Collection<?> beans) {
insertAll(beans, null, null);
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
insertAll(beans, options, null);
insertAll(beans, null);
}
/**
* Insert all beans in the collection with a transaction.
*/
@Override
public void insertAll(@Nullable Collection<?> beans, @Nullable Transaction transaction) {
insertAll(beans, null, transaction);
}
@Override
public void insertAll(@Nullable Collection<?> beans, InsertOptions options, @Nullable Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), options, txn);
persister.insert(checkEntityBean(bean), txn);
}
return 0;
}, transaction);
@@ -35,6 +35,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private final OrmQueryEngine queryEngine;
private final SpiQuery<T> query;
private final BeanFindController finder;
private final Boolean readOnly;
private LoadContext loadContext;
private PersistenceContext persistenceContext;
private HashQuery cacheKey;
@@ -51,6 +52,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
this.finder = beanDescriptor.beanFinder();
this.queryEngine = queryEngine;
this.query = query;
this.readOnly = query.isReadOnly();
this.persistenceContext = query.persistenceContext();
}
@@ -133,6 +135,13 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
return loadContext.secondaryQueriesMinBatchSize();
}
/**
* Return the Normal, sharedInstance, ReadOnly state of this query.
*/
public Boolean isReadOnly() {
return readOnly;
}
/**
* Return the BeanDescriptor for the associated bean.
*/
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.core;
import io.ebean.InsertOptions;
import io.ebean.ValuePair;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.EntityBean;
@@ -125,7 +124,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
* Many-to-many intersection table changes that are held for later batch processing.
*/
private List<SaveMany> saveMany;
private InsertOptions insertOptions;
public PersistRequestBean(SpiEbeanServer server, T bean, Object parentBean, BeanManager<T> mgr, SpiTransaction t,
PersistExecute persistExecute, PersistRequest.Type type, int flags) {
@@ -382,14 +380,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
return intercept.dirtyPropertyNames();
}
public boolean isChangedProperty(int propertyIndex) {
if (dirtyProperties == null) {
return intercept.isChangedProperty(propertyIndex);
} else {
return dirtyProperties[propertyIndex];
}
}
/**
* Return the dirty properties on this request.
*/
@@ -875,8 +865,8 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
setNotifyCache();
boolean isChangeLog = beanDescriptor.isChangeLog();
if (type == Type.UPDATE) {
// get the dirty properties for notify cache & orphanRemoval of vanilla collection detection
if (type == Type.UPDATE && (isChangeLog || notifyCache || docStoreMode == DocStoreMode.UPDATE)) {
// get the dirty properties for update notification to the doc store
dirtyProperties = intercept.dirtyProperties();
}
if (isChangeLog) {
@@ -1418,12 +1408,4 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
private void setGeneratedId() {
beanDescriptor.setGeneratedId(entityBean, transaction);
}
public void setInsertOptions(InsertOptions insertOptions) {
this.insertOptions = insertOptions;
}
public InsertOptions insertOptions() {
return insertOptions;
}
}
@@ -1,7 +1,11 @@
package io.ebeaninternal.server.core;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.CallableSql;
import io.ebean.MergeOptions;
import io.ebean.Query;
import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
@@ -27,9 +31,9 @@ public interface Persister {
void update(EntityBean entityBean, Transaction t);
/**
* Perform an Insert using the given bean.
* Force an Insert using the given bean.
*/
void insert(EntityBean entityBean, @Nullable InsertOptions insertOptions, @Nullable Transaction t);
void insert(EntityBean entityBean, Transaction t);
/**
* Insert or update the bean depending on its state.
@@ -40,6 +44,7 @@ public interface Persister {
* Delete a bean given it's type and id value.
* <p>
* This will also cascade delete one level of children.
* </p>
*/
int delete(Class<?> beanType, Object id, Transaction transaction, boolean permanent);
@@ -1,9 +1,12 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import io.ebeaninternal.server.query.CQueryCollectionAdd;
@@ -31,7 +34,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
* For Map's this needs to take the mapKey.
* </p>
*/
BeanCollectionAdd collectionAdd(Object bc, String mapKey);
BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
/**
* Create an empty collection of the correct type without a parent bean.
@@ -687,6 +687,16 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
}
}
public void freeze(EntityBean entityBean) {
for (BeanProperty beanProperty : propertiesMutable) {
beanProperty.freeze(entityBean);
}
for (BeanPropertyAssocMany<?> many : propertiesMany) {
many.freeze(entityBean);
}
entityBean._ebean_getIntercept().freeze();
}
public void metricPersistBatch(PersistRequest.Type type, long startNanos, int size) {
iudMetrics.addBatch(type, startNanos, size);
}
@@ -1,12 +1,16 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanList;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -19,11 +23,19 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
super(many);
}
BeanListHelp() {
super();
}
@Override
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanList<?>) {
BeanList<?> bl = (BeanList<?>) bc;
return bl.collectionAdd();
if (bl.actualList() == null) {
bl.setActualList(new ArrayList<>());
}
return bl;
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -55,20 +67,20 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
return beanList;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanList<T> newBeanList = (BeanList<T>) bc;
BeanList<?> newBeanList = (BeanList<?>) bc;
List<?> currentList = (List<?>) many.getValue(parentBean);
newBeanList.setModifyListening(many.modifyListenMode());
if (currentList == null) {
// the currentList is null? Not really expecting this...
many.setValue(parentBean, newBeanList);
} else if (currentList instanceof BeanList) {
} else if (currentList instanceof BeanList<?>) {
// normally this case, replace just the underlying list
BeanList<T> currentBeanList = (BeanList<T>) currentList;
currentBeanList.refresh(many.modifyListenMode(), newBeanList);
BeanList<?> currentBeanList = (BeanList<?>) currentList;
currentBeanList.setActualList(newBeanList.actualList());
currentBeanList.setModifyListening(many.modifyListenMode());
} else {
// replace the entire list with the BeanList
@@ -1,13 +1,17 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanMap;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -33,14 +37,20 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
@Override
@SuppressWarnings("unchecked")
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (mapKey == null) {
mapKey = many.mapKey();
}
BeanProperty beanProp = targetDescriptor.beanProperty(mapKey);
if (bc instanceof BeanMap<?, ?>) {
BeanMap<Object, Object> bm = (BeanMap<Object, Object>) bc;
return new Adder(beanProp, bm.collectionAdd());
Map<Object, Object> actualMap = bm.actualMap();
if (actualMap == null) {
actualMap = new LinkedHashMap<>();
bm.setActualMap(actualMap);
}
return new Adder(beanProp, actualMap);
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -116,7 +126,8 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
} else if (current instanceof BeanMap<?, ?>) {
// normally this case, replace just the underlying list
BeanMap<?, ?> currentBeanMap = (BeanMap<?, ?>) current;
currentBeanMap.refresh(many.modifyListenMode(), newBeanMap);
currentBeanMap.setActualMap(newBeanMap.actualMap());
currentBeanMap.setModifyListening(many.modifyListenMode());
} else {
// replace the entire set
@@ -1,11 +1,9 @@
package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ModifyAwareType;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.MutableValueInfo;
import io.ebean.bean.PersistenceContext;
import io.ebean.bean.*;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbEncryptFunction;
import io.ebean.config.dbplatform.DbPlatformType;
@@ -1498,4 +1496,15 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
desc.registerColumn(dbColumn, path);
}
}
/**
* Freeze mutable types (like DbArray).
*/
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof ModifyAwareType) {
ModifyAwareType bc = (ModifyAwareType) value;
setValue(entityBean, bc.freeze());
}
}
}
@@ -655,7 +655,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
}
private BeanCollectionAdd beanCollectionAdd(Object bc) {
return help.collectionAdd(bc, null);
return help.getBeanCollectionAdd(bc, null);
}
public Object parentId(EntityBean parentBean) {
@@ -1133,4 +1133,17 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return false;
}
}
@Override
public void freeze(EntityBean entityBean) {
Object value = getValue(entityBean);
if (value instanceof BeanCollection) {
BeanCollection<?> bc = (BeanCollection<?>) value;
setValue(entityBean, bc.freeze());
if (bc.isReference()) {
// make it an error to access the collection (no lazy loading allowed)
entityBean._ebean_getIntercept().setPropertyUnloaded(propertyIndex);
}
}
}
}
@@ -1,13 +1,17 @@
package io.ebeaninternal.server.deploy;
import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.EntityBean;
import io.ebean.common.BeanSet;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonWriter;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
/**
@@ -22,11 +26,21 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
super(many);
}
/**
* For a query that returns a set.
*/
BeanSetHelp() {
super();
}
@Override
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
if (bc instanceof BeanSet<?>) {
BeanSet<?> beanSet = (BeanSet<?>) bc;
return beanSet.collectionAdd();
if (beanSet.actualSet() == null) {
beanSet.setActualSet(new LinkedHashSet<>());
}
return beanSet;
} else {
throw new RuntimeException("Unhandled type " + bc);
}
@@ -58,10 +72,9 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
return beanSet;
}
@SuppressWarnings("unchecked")
@Override
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
BeanSet<T> newBeanSet = (BeanSet<T>) bc;
BeanSet<?> newBeanSet = (BeanSet<?>) bc;
Set<?> current = (Set<?>) many.getValue(parentBean);
newBeanSet.setModifyListening(many.modifyListenMode());
if (current == null) {
@@ -70,8 +83,9 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
} else if (current instanceof BeanSet<?>) {
// normally this case, replace just the underlying list
BeanSet<T> currentBeanSet = (BeanSet<T>) current;
currentBeanSet.refresh(many.modifyListenMode(), newBeanSet);
BeanSet<?> currentBeanSet = (BeanSet<?>) current;
currentBeanSet.setActualSet(newBeanSet.actualSet());
currentBeanSet.setModifyListening(many.modifyListenMode());
} else {
// replace the entire set
@@ -4,6 +4,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import java.util.LinkedHashMap;
import java.util.Map;
final class ElementHelpMap implements ElementHelp {
@@ -14,7 +15,7 @@ final class ElementHelpMap implements ElementHelp {
private static class Collector implements ElementCollector {
private final LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
private final Map<Object, Object> map = new LinkedHashMap<>();
@Override
public void addElement(Object element) {
@@ -4,6 +4,7 @@ import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import java.util.LinkedHashSet;
import java.util.Set;
final class ElementHelpSet implements ElementHelp {
@@ -14,7 +15,7 @@ final class ElementHelpSet implements ElementHelp {
private static class Collector implements ElementCollector {
private final LinkedHashSet<Object> set = new LinkedHashSet<>();
private final Set<Object> set = new LinkedHashSet<>();
@Override
public void addElement(Object element) {
@@ -122,7 +122,7 @@ public final class DeployCreateProperties {
return new DeployBeanProperty(desc, propertyType, field.getGenericType());
}
// check for Collection type (list, set or map)
ManyType manyType = determineManyType.manyType(propertyType);
ManyType manyType = determineManyType.getManyType(propertyType);
if (manyType != null) {
// List, Set or Map based object
Class<?> targetType = determineTargetType(field);
@@ -11,14 +11,14 @@ import java.util.Set;
*/
final class DetermineManyType {
ManyType manyType(Class<?> type) {
ManyType getManyType(Class<?> type) {
if (type.equals(List.class)) {
return ManyType.LIST;
}
if (type.equals(Set.class) || type.getCanonicalName().equals("java.util.SequencedSet")) {
if (type.equals(Set.class)) {
return ManyType.SET;
}
if (type.equals(Map.class) || type.getCanonicalName().equals("java.util.SequencedMap")) {
if (type.equals(Map.class)) {
return ManyType.MAP;
}
return null;
@@ -411,7 +411,7 @@ public final class DefaultPersister implements Persister {
if (bean._ebean_getIntercept().isUpdate()) {
update(bean, t);
} else {
insert(bean, null, t);
insert(bean, t);
}
}
@@ -419,15 +419,12 @@ public final class DefaultPersister implements Persister {
* Insert this bean.
*/
@Override
public void insert(EntityBean bean, InsertOptions insertOptions, Transaction t) {
public void insert(EntityBean bean, Transaction t) {
PersistRequestBean<?> req = createRequest(bean, t, PersistRequest.Type.INSERT);
if (req.isSkipReference()) {
// skip insert on reference bean
return;
}
if (insertOptions != null) {
req.setInsertOptions(insertOptions);
}
try {
req.initTransIfRequiredWithBatchCascade();
insert(req);
@@ -328,7 +328,7 @@ final class SaveManyBeans extends SaveManyBase {
}
private boolean isChangedProperty() {
return request.isChangedProperty(many.propertyIndex());
return parentBean._ebean_getIntercept().isChangedProperty(many.propertyIndex());
}
private void removeAssocManyOrphans() {
@@ -1,15 +1,11 @@
package io.ebeaninternal.server.persist.dml;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to support the generation of DML statements.
*/
public final class GenerateDmlRequest {
private final StringBuilder sb = new StringBuilder(100);
private final List<String> columns = new ArrayList<>();
private StringBuilder insertBindBuffer;
private String prefix;
private String prefix2;
@@ -30,7 +26,7 @@ public final class GenerateDmlRequest {
++bindColumnCount;
sb.append(prefix);
sb.append(column);
columns.add(column);
//sb.append(expr);
if (insertMode > 0) {
if (insertMode++ > 1) {
insertBindBuffer.append(',');
@@ -79,8 +75,4 @@ public final class GenerateDmlRequest {
public boolean isUpdate() {
return insertMode == 0;
}
public List<String> columns() {
return columns;
}
}
@@ -69,7 +69,7 @@ public final class InsertHandler extends DmlHandler {
SpiTransaction t = persistRequest.transaction();
// get the appropriate sql
sql = meta.sql(withId, persistRequest.isPublish(), persistRequest.insertOptions());
sql = meta.getSql(withId, persistRequest.isPublish());
PreparedStatement pstmt;
if (persistRequest.isBatched()) {
pstmt = pstmtBatch(t, sql, persistRequest, useGeneratedKeys);
@@ -134,8 +134,7 @@ public final class InsertHandler extends DmlHandler {
if (idValue != null) {
persistRequest.setGeneratedKey(idValue);
}
} else if (persistRequest.insertOptions() == null) {
// insert on conflict do nothing can not return generated key
} else {
throw new PersistenceException("Autoincrement getGeneratedKeys() returned no rows?");
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebean.bean.EntityBean;
import io.ebean.config.dbplatform.DatabasePlatform;
@@ -37,11 +36,9 @@ final class InsertMeta {
private final Bindable shadowFKey;
private final String[] identityDbColumns;
private final Platform platform;
private final InsertMetaOptions options;
InsertMeta(DatabasePlatform dbPlatform, BeanDescriptor<?> desc, Bindable shadowFKey, BindableId id, BindableList all) {
this.platform = dbPlatform.platform();
this.options = InsertMetaPlatform.create(platform, desc, this);
this.discriminator = discriminator(desc);
this.id = id;
this.all = all;
@@ -50,8 +47,8 @@ final class InsertMeta {
String tableName = desc.baseTable();
String draftTableName = desc.draftTable();
this.sqlWithId = sql(false, tableName, false);
this.sqlDraftWithId = desc.isDraftable() ? sql(false, draftTableName, true) : sqlWithId;
this.sqlWithId = genSql(false, tableName, false);
this.sqlDraftWithId = desc.isDraftable() ? genSql(false, draftTableName, true) : sqlWithId;
// only available for single Id property
if (id.isConcatenated()) {
@@ -75,8 +72,8 @@ final class InsertMeta {
this.supportsGetGeneratedKeys = dbPlatform.dbIdentity().isSupportsGetGeneratedKeys();
this.supportsSelectLastInsertedId = desc.supportsSelectLastInsertedId();
}
this.sqlNullId = sql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? sql(true, draftTableName, true) : sqlNullId;
this.sqlNullId = genSql(true, tableName, false);
this.sqlDraftNullId = desc.isDraftable() ? genSql(true, draftTableName, true) : sqlNullId;
}
}
@@ -140,20 +137,9 @@ final class InsertMeta {
}
/**
* Return the sql for the given options.
* get the sql based whether the id value(s) are null.
*/
public String sql(boolean withId, boolean publish, InsertOptions insertOptions) {
if (insertOptions == null) {
return sql(withId, publish);
}
return options.sql(withId, insertOptions);
}
String sqlFor(boolean withId) {
return withId ? sqlWithId : sqlNullId;
}
private String sql(boolean withId, boolean publish) {
public String getSql(boolean withId, boolean publish) {
if (withId) {
return publish ? sqlWithId : sqlDraftWithId;
} else {
@@ -161,18 +147,12 @@ final class InsertMeta {
}
}
private String sql(boolean nullId, String table, boolean draftTable) {
private String genSql(boolean nullId, String table, boolean draftTable) {
GenerateDmlRequest request = new GenerateDmlRequest();
sql(request, nullId, table, draftTable);
return request.toString();
}
void sql(GenerateDmlRequest request, boolean nullId, String table, boolean draftTable) {
request.setInsertSetMode();
request.append("insert into ").append(table);
if (nullId && noColumnsForInsert(draftTable)) {
request.append(defaultValues());
return;
return request.append(defaultValues()).toString();
}
request.append(" (");
if (!nullId) {
@@ -192,6 +172,7 @@ final class InsertMeta {
request.append(") values (");
request.append(request.insertBindBuffer());
request.append(")");
return request.toString();
}
private String defaultValues() {
@@ -215,4 +196,5 @@ final class InsertMeta {
&& discriminator == null
&& (draftTable ? all.isEmpty() : allExcludeDraftOnly.isEmpty());
}
}
@@ -1,14 +0,0 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
/**
* Generator for insert SQL with options.
*/
interface InsertMetaOptions {
/**
* Generate the SQL for the given insert options.
*/
String sql(boolean withId, InsertOptions insertOptions);
}
@@ -1,113 +0,0 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
* Postgres specific generation of insert on conflict.
*/
final class InsertMetaOptionsPostgres implements InsertMetaOptions {
private final InsertMeta meta;
private final BeanDescriptor<?> desc;
private final String baseTable;
private final Map<String, String> sqlCache = new ConcurrentHashMap<>();
InsertMetaOptionsPostgres(InsertMeta meta, BeanDescriptor<?> desc) {
this.meta = meta;
this.desc = desc;
this.baseTable = desc.baseTable();
}
@Override
public String sql(boolean withId, InsertOptions options) {
String key = withId + options.key();
return sqlCache.computeIfAbsent(key, k -> generate(withId, options));
}
private String generate(boolean withId, InsertOptions options) {
char type = options.key().charAt(0);
switch (type) {
case 'U':
return generate(withId, false, options);
case 'N':
return generate(withId, true, options);
default:
return meta.sqlFor(withId);
}
}
private String generate(boolean withId, boolean doNothing, InsertOptions options) {
GenerateDmlRequest request = new GenerateDmlRequest();
meta.sql(request, !withId, baseTable, false);
request.append(" on conflict ");
List<String> uniqueColumns = desc.uniqueProps().stream()
.flatMap(Arrays::stream)
.map(BeanProperty::dbColumn)
.collect(Collectors.toList());
String constraintName = options.constraint();
if (constraintName != null) {
request.append("on constraint ").append(constraintName);
} else {
request.append("(");
String cols = options.uniqueColumns();
if (cols != null) {
request.append(cols);
} else {
appendUniqueColumns(uniqueColumns, request);
}
request.append(")");
}
if (doNothing) {
request.append(" do nothing");
return request.toString();
}
request.append(" do update set ");
String updateSet = options.updateSet();
if (updateSet != null) {
request.append(updateSet);
} else {
setColumns(withId, request, uniqueColumns);
}
return request.toString();
}
private void setColumns(boolean withId, GenerateDmlRequest request, List<String> uniqueColumns) {
List<String> columns = request.columns();
columns.removeAll(uniqueColumns);
if (withId) {
BeanProperty idProperty = desc.idProperty();
if (idProperty != null && !idProperty.isEmbedded()) {
columns.remove(idProperty.dbColumn());
}
}
for (int i = 0; i < columns.size(); i++) {
if (i > 0) {
request.append(", ");
}
String col = columns.get(i);
request.append(col).append("=excluded.").append(col);
}
}
private static void appendUniqueColumns(List<String> uniqueColumns, GenerateDmlRequest request) {
if (uniqueColumns.isEmpty()) {
throw new IllegalStateException("Unable to identify unique columns for INSERT ON CONFLICT - Add mapping like @Column(unique=true) or @Index(unique=true)");
}
for (int i = 0; i < uniqueColumns.size(); i++) {
if (i > 0) {
request.append(", ");
}
request.append(uniqueColumns.get(i));
}
}
}
@@ -1,29 +0,0 @@
package io.ebeaninternal.server.persist.dml;
import io.ebean.InsertOptions;
import io.ebean.annotation.Platform;
import io.ebeaninternal.server.deploy.BeanDescriptor;
final class InsertMetaPlatform {
private static final NotSupported NOT_SUPPORTED = new NotSupported();
static InsertMetaOptions create(Platform platform, BeanDescriptor<?> desc, InsertMeta meta) {
switch (platform.base()) {
case POSTGRES:
case YUGABYTE:
case COCKROACH:
return new InsertMetaOptionsPostgres(meta, desc);
default:
return NOT_SUPPORTED;
}
}
static final class NotSupported implements InsertMetaOptions {
@Override
public String sql(boolean withId, InsertOptions insertOptions) {
throw new UnsupportedOperationException("InsertOptions not supported on this database platform");
}
}
}
@@ -186,7 +186,7 @@ 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.readOnly = request.isReadOnly();
this.disableLazyLoading = query.isDisableLazyLoading();
this.objectGraphNode = query.parentNode();
this.profilingListener = query.profilingListener();
@@ -51,10 +51,6 @@ public final class DefaultOrmQueryEngine implements OrmQueryEngine {
* Flushes the jdbc batch by default unless explicitly turned off on the transaction.
*/
private <T> void flushJdbcBatchOnQuery(OrmQueryRequest<T> request) {
if (request.query().isUsingFuture()) {
// future queries never invoke a flush
return;
}
SpiTransaction t = request.transaction();
if (t.isFlushOnQuery()) {
// before we perform a query, we need to flush any
@@ -31,7 +31,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
final boolean readId;
private final boolean readIdNormal;
private final boolean disableLazyLoad;
private final boolean readOnlyNoIntercept;
private final boolean readOnly;
private final InheritInfo inheritInfo;
final String prefix;
private final Map<String, String> pathMap;
@@ -54,7 +54,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
this.readId = node.readId;
this.readIdNormal = readId && !temporalVersions;
this.disableLazyLoad = node.disableLazyLoad;
this.readOnlyNoIntercept = disableLazyLoad && node.readOnly;
this.readOnly = node.readOnly;
this.partialObject = node.partialObject;
this.properties = node.properties;
this.pathMap = node.pathMap;
@@ -162,7 +162,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
void initBeanType() throws SQLException {
localDesc = desc;
localBean = desc.createEntityBean2(readOnlyNoIntercept);
localBean = desc.createEntityBean2(readOnly);
localIdBinder = idBinder;
}
@@ -302,7 +302,7 @@ class SqlTreeLoadBean implements SqlTreeLoad {
boolean forceNewReference = queryMode == Mode.REFRESH_BEAN;
for (STreePropertyAssocMany many : localDesc.propsMany()) {
if (many != loadingChildProperty) {
if (readOnlyNoIntercept) {
if (disableLazyLoad && readOnly) {
many.createEmptyReference(localBean);
} else {
// create a proxy for the many (deferred fetching)
@@ -58,7 +58,6 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
private Type type;
private String label;
private Mode mode = Mode.NORMAL;
private boolean usingFuture;
private Object tenantId;
/**
* Holds query in structured form.
@@ -972,16 +971,6 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
this.mode = mode;
}
@Override
public final void usingFuture() {
this.usingFuture = true;
}
@Override
public final boolean isUsingFuture() {
return usingFuture;
}
@Override
public final boolean isUsageProfiling() {
return usageProfiling;
@@ -0,0 +1,23 @@
package io.ebeaninternal.server.transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
final class DFrozenBeans implements FrozenBeans {
private static final long serialVersionUID = 1L;
private final Map<Class<?>, Map<Object, EntityBean>> beans;
DFrozenBeans(Map<Class<?>, Map<Object, EntityBean>> beans) {
this.beans = Collections.unmodifiableMap(beans);
}
Set<Map.Entry<Class<?>, Map<Object, EntityBean>>> entries() {
return beans.entrySet();
}
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiBeanType;
import io.ebeaninternal.api.SpiBeanTypeManager;
@@ -44,6 +45,33 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
public DefaultPersistenceContext() {
}
public void attach(FrozenBeans other) {
lock.lock();
try {
DFrozenBeans otherContext = (DFrozenBeans) other;
for (Map.Entry<Class<?>, Map<Object, EntityBean>> entry : otherContext.entries()) {
classContext(entry.getKey()).attach(entry.getValue());
}
} finally {
lock.unlock();
}
}
@Override
public Map<Class<?>, List<EntityBean>> detach() {
lock.lock();
try {
expungeStaleEntries();
Map<Class<?>, List<EntityBean>> result = new LinkedHashMap<>();
for (Map.Entry<Class<?>, ClassContext> entry : typeCache.entrySet()) {
result.put(entry.getKey(), entry.getValue().beans());
}
return result;
} finally {
lock.unlock();
}
}
@Override
public void beginIterate() {
lock.lock();
@@ -247,6 +275,23 @@ public final class DefaultPersistenceContext implements SpiPersistenceContext {
return "size:" + map.size() + " (" + weakCount + " weak)";
}
List<EntityBean> beans() {
List<EntityBean> beans = new ArrayList<>(map.size());
for (Object value : map.values()) {
if (value instanceof BeanRef) {
value = ((BeanRef) value).get();
}
if (value != null) {
beans.add((EntityBean) value);
}
}
return beans;
}
void attach(Map<Object, EntityBean> value) {
map.putAll(value);
}
private Object get(Object id) {
Object ret = map.get(id);
if (ret instanceof BeanRef) {
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.TransactionCallback;
import io.ebean.annotation.DocStoreMode;
import io.ebean.bean.FrozenBeans;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.changelog.BeanChange;
import io.ebean.event.changelog.ChangeSet;
@@ -130,6 +131,16 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
}
@Override
public FrozenBeans freezeAndDetach() {
return manager.freezeAndDetach(persistenceContext);
}
@Override
public void attach(FrozenBeans frozenBeans) {
persistenceContext.attach(frozenBeans);
}
@Override
public final void setLabel(String label) {
this.label = label;
@@ -6,6 +6,8 @@ import io.ebean.ProfileLocation;
import io.ebean.TxScope;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxType;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.cache.ServerCacheNotification;
import io.ebean.cache.ServerCacheNotify;
import io.ebean.config.CurrentTenantProvider;
@@ -22,6 +24,7 @@ import io.ebeaninternal.api.*;
import io.ebeaninternal.api.TransactionEventTable.TableIUD;
import io.ebeaninternal.server.cache.CacheChangeSet;
import io.ebeaninternal.server.cluster.ClusterManager;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.profile.TimedProfileLocation;
import io.ebeaninternal.server.profile.TimedProfileLocationRegistry;
@@ -33,7 +36,9 @@ import jakarta.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -637,4 +642,21 @@ public class TransactionManager implements SpiTransactionManager {
}
}
public FrozenBeans freezeAndDetach(SpiPersistenceContext persistenceContext) {
Map<Class<?>, Map<Object, EntityBean>> allBeans = new HashMap<>();
for (Map.Entry<Class<?>, List<EntityBean>> entry : persistenceContext.detach().entrySet()) {
allBeans.put(entry.getKey(), freezeBeanMap(entry));
}
return new DFrozenBeans(allBeans);
}
private Map<Object, EntityBean> freezeBeanMap(Map.Entry<Class<?>, List<EntityBean>> entry) {
BeanDescriptor<?> descriptor = beanDescriptorManager.descriptor(entry.getKey());
Map<Object, EntityBean> beanMap = new HashMap<>();
for (EntityBean entityBean : entry.getValue()) {
descriptor.freeze(entityBean);
beanMap.put(descriptor.getId(entityBean), entityBean);
}
return beanMap;
}
}
@@ -1,6 +0,0 @@
package io.ebeaninternal.server.type;
public interface ModifyAwareType {
boolean isDirty();
}
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,7 +14,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -63,14 +63,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -84,7 +84,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -22,28 +22,28 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -57,7 +57,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>ebean test</name>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -3,7 +3,6 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.ArrayList;
import java.util.Comparator;
@@ -14,22 +13,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanListTest {
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private final Object object1 = new Object();
private final Object object2 = new Object();
private final Object object3 = new Object();
private List<Object> all() {
List<Object> all = new ArrayList<>();
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
all.add(object1);
all.add(object2);
all.add(object3);
return all;
}
private List<Object> some() {
List<Object> some = new ArrayList<>();
some.add(new Product(2));
some.add(new Product(3));
some.add(object2);
some.add(object3);
return some;
}
@@ -164,15 +163,14 @@ public class BeanListTest {
@Test
public void testRemove_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all);
list.addAll(all());
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.remove(all.get(1));
list.remove(all.get(2));
list.remove(object2);
list.remove(object3);
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -181,14 +179,13 @@ public class BeanListTest {
@Test
public void testRemoveAll_given_beansInAdditions() {
List<Object> all = all();
BeanList<Object> list = new BeanList<>();
list.setModifyListening(BeanCollection.ModifyListenMode.ALL);
list.addAll(all);
list.addAll(all());
assertThat(list.modifyAdditions()).containsOnly(object1, object2, object3);
// act
list.removeAll(List.of(all.get(1), all.get(2)));
list.removeAll(some());
assertThat(list.modifyAdditions()).containsOnly(object1);
assertThat(list.modifyRemovals()).isEmpty();
@@ -3,7 +3,7 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanMap;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import org.tests.model.basic.EBasic;
import java.util.*;
@@ -14,24 +14,24 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
public class BeanMapTest {
private final Product object1 = new Product(1);
private final Product object2 = new Product(2);
private final Product object3 = new Product(3);
private final Product object4 = new Product(4);
private final Product object5 = new Product(5);
private final EBasic object1 = new EBasic("o1");
private final EBasic object2 = new EBasic("o2");
private final EBasic object3 = new EBasic("o3");
private final EBasic object4 = new EBasic("o4");
private final EBasic object5 = new EBasic("o5");
private Map<String, Object> all() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("1", new Product(1));
all.put("2", new Product(2));
all.put("3", new Product(3));
all.put("1", object1);
all.put("2", object2);
all.put("3", object3);
return all;
}
private Map<String, Object> some() {
Map<String, Object> all = new LinkedHashMap<>();
all.put("2", new Product(2));
all.put("3", new Product(3));
all.put("2", object2);
all.put("3", object3);
return all;
}
@@ -73,8 +73,8 @@ public class BeanMapTest {
@Test
public void testAdd_given_someAlreadyIn() {
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
BeanMap<String, Object> map = new BeanMap<>(some());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
@@ -90,13 +90,11 @@ public class BeanMapTest {
@Test
public void testAddSome_given_someAlreadyIn() {
Map<String, Object> some = new LinkedHashMap<>(Map.of("2", object2, "3", object3));
BeanMap<String, Object> map = new BeanMap<>(some);
BeanMap<String, Object> map = new BeanMap<>(some());
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
// act
Map<String, Object> all = Map.of("1", object1, "2", object2, "3", object3);
map.putAll(all);
map.putAll(all());
assertThat(map.modifyAdditions()).containsOnly(object1);
assertThat(map.modifyRemovals()).isEmpty();
@@ -181,7 +179,7 @@ public class BeanMapTest {
@Test
public void testClear_given_someBeansInAdditions() {
BeanMap<String, Product> map = newModifyListeningMap();
BeanMap<String, EBasic> map = newModifyListeningMap();
map.put("2", object2);
map.put("3", object3);
@@ -195,7 +193,7 @@ public class BeanMapTest {
@Test
public void keySet_add_whenModifyListening() {
BeanMap<String, Product> map = newModifyListeningMap();
BeanMap<String, EBasic> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().add("3"));
}
@@ -207,7 +205,7 @@ public class BeanMapTest {
@Test
public void keySet_addAll_whenModifyListening() {
BeanMap<String, Product> map = newModifyListeningMap();
BeanMap<String, EBasic> map = newModifyListeningMap();
assertThrows(UnsupportedOperationException.class, () -> map.keySet().addAll(asList("3", "4")));
}
@@ -319,13 +317,13 @@ public class BeanMapTest {
@Test
public void values_add() {
BeanMap<String, Product> map = new BeanMap<>();
BeanMap<String, EBasic> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().add(object3));
}
@Test
public void values_addAll() {
BeanMap<String, Product> map = new BeanMap<>();
BeanMap<String, EBasic> map = new BeanMap<>();
assertThrows(UnsupportedOperationException.class, () -> map.values().addAll(asList(object3, object5)));
}
@@ -339,8 +337,8 @@ public class BeanMapTest {
@Test
public void entrySet_clear() {
final BeanMap<String, Product> map = newModifyListeningMap();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final BeanMap<String, EBasic> map = newModifyListeningMap();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
entries.clear();
assertThat(entries).isEmpty();
@@ -350,8 +348,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove() {
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -367,8 +365,8 @@ public class BeanMapTest {
@Test
public void entrySet_remove_whenNotEqualValue() {
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
assertThat(map).hasSize(5);
@@ -381,11 +379,11 @@ public class BeanMapTest {
@Test
public void entrySet_iterator_remove() {
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final Iterator<Map.Entry<String, Product>> iterator = entries.iterator();
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
final Iterator<Map.Entry<String, EBasic>> iterator = entries.iterator();
while (iterator.hasNext()) {
final Map.Entry<String, Product> entry = iterator.next();
final Map.Entry<String, EBasic> entry = iterator.next();
if (entry.getKey().equals("2") || entry.getKey().equals("5")) {
iterator.remove();
}
@@ -397,8 +395,8 @@ public class BeanMapTest {
@Test
public void entrySet_removeAll() {
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
entries.removeAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(3);
@@ -408,8 +406,8 @@ public class BeanMapTest {
@Test
public void entrySet_retainAll() {
final BeanMap<String, Product> map = newModifyListeningMap5();
final Set<Map.Entry<String, Product>> entries = map.entrySet();
final BeanMap<String, EBasic> map = newModifyListeningMap5();
final Set<Map.Entry<String, EBasic>> entries = map.entrySet();
entries.retainAll(asList(new AbstractMap.SimpleEntry<>("1", object1), new AbstractMap.SimpleEntry<>("3", object4), new AbstractMap.SimpleEntry<>("4", object4)));
assertThat(map).hasSize(2);
@@ -417,15 +415,15 @@ public class BeanMapTest {
assertThat(map.modifyRemovals()).containsOnly(object2, object3, object5);
}
private BeanMap<String, Product> newModifyListeningMap() {
BeanMap<String, Product> map = new BeanMap<>();
private BeanMap<String, EBasic> newModifyListeningMap() {
BeanMap<String, EBasic> map = new BeanMap<>();
map.put("1", object1);
map.setModifyListening(BeanCollection.ModifyListenMode.ALL);
return map;
}
private BeanMap<String, Product> newModifyListeningMap5() {
BeanMap<String, Product> map = new BeanMap<>();
private BeanMap<String, EBasic> newModifyListeningMap5() {
BeanMap<String, EBasic> map = new BeanMap<>();
map.put("1", object1);
map.put("2", object2);
map.put("3", object3);
@@ -3,7 +3,6 @@ package io.ebean.xtest.common;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanSet;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Product;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -13,22 +12,22 @@ import static org.assertj.core.api.Assertions.assertThat;
public class BeanSetTest {
Product object1 = new Product(1);
Product object2 = new Product(2);
Product object3 = new Product(3);
Object object1 = new Object();
Object object2 = new Object();
Object object3 = new Object();
private Set<Object> all() {
Set<Object> all = new LinkedHashSet<>();
all.add(new Product(1));
all.add(new Product(2));
all.add(new Product(3));
all.add(object1);
all.add(object2);
all.add(object3);
return all;
}
private Set<Object> some() {
Set<Object> some = new LinkedHashSet<>();
some.add(new Product(2));
some.add(new Product(3));
some.add(object2);
some.add(object3);
return some;
}
@@ -100,9 +99,7 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
set.addAll(all());
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
@@ -118,13 +115,11 @@ public class BeanSetTest {
BeanSet<Object> set = new BeanSet<>();
set.setModifyListening(BeanCollection.ModifyListenMode.ALL);
Set<Object> all = Set.of(object1, object2, object3);
set.addAll(all);
set.addAll(all());
assertThat(set.modifyAdditions()).containsOnly(object1, object2, object3);
// act
set.removeAll(Set.of(object2, object3));
set.removeAll(some());
assertThat(set.modifyAdditions()).containsOnly(object1);
assertThat(set.modifyRemovals()).isEmpty();
@@ -453,41 +453,21 @@ public class TDSpiServer implements SpiServer {
}
@Override
public void insert(Object bean, InsertOptions insertOptions) {
}
@Override
public void insert(Object bean, Transaction transaction) {
}
@Override
public void insert(Object bean, InsertOptions insertOptions, Transaction transaction) {
}
@Override
public void insertAll(Collection<?> beans) {
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options) {
}
@Override
public void insertAll(Collection<?> beans, Transaction transaction) {
}
@Override
public void insertAll(Collection<?> beans, InsertOptions options, Transaction transaction) {
}
@Override
public int execute(SqlUpdate updSql, Transaction transaction) {
return 0;
@@ -27,14 +27,14 @@ public class TestQueryForUpdate extends BaseTestCase {
try (final Transaction transaction = DB.beginTransaction()) {
query = DB.find(Customer.class)
.forUpdate()
.orderBy().desc("id");
.order().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
} else {
} else if (!isDb2()){
assertThat(sqlOf(query)).contains("for update");
}
}
@@ -48,21 +48,21 @@ public class TestQueryForUpdate extends BaseTestCase {
query = DB.find(Customer.class)
.forUpdate()
.setMaxRows(3)
.orderBy().desc("id");
.order().desc("id");
query.findList();
}
if (isSqlServer()) {
assertThat(sqlOf(query)).contains("with (updlock)");
} else if (!isOracle()) {
} else if (!isOracle() && !isDb2()) {
// Oracle does not support FOR UPDATE with FETCH
assertThat(sqlOf(query)).contains("for update");
}
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testForUpdate_when_alreadyInPCAsReference() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -96,7 +96,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testForUpdate_when_alreadyInPCAsReference_usingLock() {
ResetBasicData.reset();
Order o0 = DB.find(Order.class).orderBy("id").setMaxRows(1).findOne();
@@ -131,7 +131,7 @@ public class TestQueryForUpdate extends BaseTestCase {
}
@Test
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB, Platform.DB2})
@ForPlatform({Platform.H2, Platform.ORACLE, Platform.POSTGRES, Platform.SQLSERVER, Platform.MYSQL, Platform.MARIADB})
public void testForUpdate_when_alreadyInPC() {
EBasic basic = new EBasic("initialValue");
@@ -193,7 +193,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query<Customer> query = DB.find(Customer.class)
.forUpdateNoWait()
.orderBy().desc("id");
.order().desc("id");
query.findList();
if (isOracle()) {
@@ -219,7 +219,7 @@ public class TestQueryForUpdate extends BaseTestCase {
Query<Customer> query = DB.find(Customer.class)
.forUpdateNoWait()
.setMaxRows(1)
.orderBy().desc("id");
.order().desc("id");
List<Customer> list = query.findList();
Customer first = list.get(0);
@@ -1,297 +0,0 @@
package org.tests.insert;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.InsertOptions;
import io.ebean.Transaction;
import io.ebean.annotation.Platform;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import org.junit.jupiter.api.Test;
import org.tests.update.EPersonOnline;
import java.util.List;
import static io.ebean.InsertOptions.ON_CONFLICT_NOTHING;
import static io.ebean.InsertOptions.ON_CONFLICT_UPDATE;
import static org.assertj.core.api.Assertions.assertThat;
class TestInsertOnConflict extends BaseTestCase {
InsertOptions onConflictDoUpdateAndGetGeneratedKeys = InsertOptions.builder()
.onConflictUpdate()
.getGeneratedKeys(true)
.build();
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdateExplicitTransaction() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
try (Transaction txn = DB.createTransaction()) {
db.insert(bean, ON_CONFLICT_UPDATE, txn);
txn.commit();
}
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
try (Transaction txn = DB.createTransaction()) {
db.insert(bean2, ON_CONFLICT_UPDATE, txn);
txn.commit();
}
assertThat(bean2.getId()).isEqualTo(bean.getId());
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdate_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
db.insert(bean, onConflictDoUpdateAndGetGeneratedKeys);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
assertThat(bean2.getId()).isEqualTo(bean.getId());
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictUpdate_when_idValueSupplied() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
bean.setId(40_042L);
db.insert(bean, ON_CONFLICT_UPDATE);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setId(40_043L); // not expected but can be different
bean2.setOnlineStatus(false);
db.insert(bean2, onConflictDoUpdateAndGetGeneratedKeys);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains("insert into e_person_online (id, email, online_status, when_updated) values (?,?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean2.getWhenUpdated());
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertAll_onConflictUpdate_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_UPDATE);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
var bean4 = newBean("a1@b.com");
var bean5 = newBean("a5@b.com");
db.insertAll(List.of(bean4, bean5), ON_CONFLICT_UPDATE);
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(4);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertAll_onConflictUpdate_explicitTransaction() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
try (Transaction txn = DB.createTransaction()) {
txn.setBatchSize(3);
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var bean4 = newBean("a4@b.com");
db.insertAll(List.of(bean, bean2, bean3, bean4), ON_CONFLICT_UPDATE, txn);
txn.commit();
}
var sql = LoggedSql.stop();
assertThat(sql).hasSize(8);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
assertThat(sql.get(5)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(6)).contains(" -- bind");
assertThat(sql.get(7)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(4);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitConstraint() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.constraint("uq_e_person_online_email")
.build();
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict on constraint uq_e_person_online_email do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(3);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitUniqueColumns() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var bean3 = newBean("a3@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.uniqueColumns(" email ")
.build();
db.insertAll(List.of(bean, bean2, bean3), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(5);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict ( email ) do update set online_status=excluded.online_status, when_updated=excluded.when_updated");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- bind");
assertThat(sql.get(4)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(3);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void explicitUpdateSet() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a1@b.com");
var bean2 = newBean("a2@b.com");
var ON_CONFLICT_ = InsertOptions.builder()
.onConflictUpdate()
.updateSet("when_updated=excluded.when_updated, online_status=true")
.build();
db.insertAll(List.of(bean, bean2), ON_CONFLICT_);
var sql = LoggedSql.stop();
assertThat(sql).hasSize(4);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do update set when_updated=excluded.when_updated, online_status=true");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- executeBatch()");
List<EPersonOnline> list = db.find(EPersonOnline.class).orderBy("id").findList();
assertThat(list).hasSize(2);
}
@ForPlatform({Platform.POSTGRES, Platform.YUGABYTE})
@Test
void insertOnConflictNothing_when_noIdValue() {
Database db = DB.getDefault();
db.truncate(EPersonOnline.class);
LoggedSql.start();
var bean = newBean("a@b.com");
db.insert(bean, ON_CONFLICT_NOTHING);
assertThat(bean.getId()).isNotNull();
var bean2 = newBean("a@b.com");
bean2.setOnlineStatus(false);
db.insert(bean2, ON_CONFLICT_NOTHING);
assertThat(bean2.getId()).isNull();
var sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
assertThat(sql.get(1)).contains("insert into e_person_online (email, online_status, when_updated) values (?,?,?) on conflict (email) do nothing");
List<EPersonOnline> list = db.find(EPersonOnline.class).findList();
assertThat(list).hasSize(1);
assertThat(list.get(0).getWhenUpdated()).isEqualTo(bean.getWhenUpdated());
}
private static EPersonOnline newBean(String email) {
EPersonOnline bean = new EPersonOnline();
bean.setEmail(email);
bean.setOnlineStatus(true);
return bean;
}
}
@@ -39,13 +39,6 @@ public class Product implements Serializable {
@Version
Timestamp updtime;
public Product(Integer id) {
this.id = id;
}
public Product() {
}
/**
* Return id.
*/
@@ -35,6 +35,11 @@ public class PlainBeanDirtyAware implements ModifyAwareType {
return markedDirty;
}
@Override
public Object freeze() {
return this;
}
@Override
public void setMarkedDirty(boolean markedDirty) {
this.markedDirty = markedDirty;
@@ -28,11 +28,6 @@ public class OmBeanListChild extends Model {
public Long getId() {
return id;
}
public OmBeanListChild setId(Long id) {
this.id = id;
return this;
}
}
@@ -2,14 +2,10 @@ package org.tests.model.orphanremoval;
import io.ebean.Model;
import io.ebean.annotation.WhenCreated;
import io.ebean.annotation.WhenModified;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Version;
import java.time.Instant;
import java.util.List;
import static jakarta.persistence.CascadeType.ALL;
@@ -23,13 +19,6 @@ public class OmBeanListParent extends Model {
@Version
private long version;
private String name;
@WhenCreated
private Instant whenCreated;
@WhenModified
private Instant whenModified;
@OneToMany(cascade = ALL, mappedBy = "parent", orphanRemoval = true)
private List<OmBeanListChild> children;
@@ -46,42 +35,6 @@ public class OmBeanListParent extends Model {
this.children.clear();
this.children.addAll(children);
}
public void setChildren2(List<OmBeanListChild> children) {
this.children = children;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
public Instant getWhenModified() {
return whenModified;
}
public void setWhenModified(Instant whenModified) {
this.whenModified = whenModified;
}
public Instant getWhenCreated() {
return whenCreated;
}
public void setWhenCreated(Instant whenCreated) {
this.whenCreated = whenCreated;
}
}
@@ -1,20 +1,17 @@
package org.tests.model.orphanremoval;
import io.ebean.DB;
import io.ebean.test.LoggedSql;
import org.junit.jupiter.api.Test;
import java.util.List;
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
class TestOrphanRemovalOverwrite {
public class TestOrphanRemovalOverwrite {
@Test
void testOverwritingMapping() {
public void testOverwritingMapping() {
OmBeanListParent parent = new OmBeanListParent();
parent.save();
@@ -32,45 +29,8 @@ class TestOrphanRemovalOverwrite {
// Refreshing here generates new objects for the associated children that are referred to by the parent.
parent.refresh();
OmBeanListChild child = childList.get(0);
assertNotNull(child.getId());
OmBeanListChild refreshedChild = parent.getChildren().get(0);
assertEquals(child.getId(), refreshedChild.getId());
assertEquals(child, refreshedChild);
assertNotNull(childList.get(0).getId());
assertEquals(childList.get(0).getId(), parent.getChildren().get(0).getId());
assertEquals(childList, parent.getChildren());
}
@Test
void clearAddAll() {
OmBeanListChild c1 = new OmBeanListChild("c1");
OmBeanListChild c2 = new OmBeanListChild("c2");
OmBeanListParent parent = new OmBeanListParent();
parent.getChildren().add(c1);
parent.getChildren().add(c2);
parent.save();
OmBeanListChild c3 = new OmBeanListChild("c3");
c3.setId(c1.getId());
OmBeanListParent p1 = DB.find(OmBeanListParent.class, parent.getId());
List<OmBeanListChild> children = p1.getChildren();
children.clear();
children.add(c3);
LoggedSql.start();
DB.save(p1);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(7);
assertThat(sql.get(0)).contains("delete from om_bean_list_child where id=?");
assertThat(sql.get(1)).contains(" -- bind");
assertThat(sql.get(2)).contains(" -- bind");
assertThat(sql.get(3)).contains(" -- executeBatch()");
assertThat(sql.get(4)).contains(" insert into om_bean_list_child");
assertThat(sql.get(5)).contains(" -- bind");
assertThat(sql.get(6)).contains(" -- executeBatch()");
}
}
@@ -1,114 +0,0 @@
package org.tests.o2m;
import io.ebean.DB;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.orphanremoval.OmBeanListChild;
import org.tests.model.orphanremoval.OmBeanListParent;
import java.time.Instant;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import static org.assertj.core.api.Assertions.assertThat;
class OneToManyListMarkAsDirtyTest extends BaseTestCase {
@Test
void usingNewListWithNonDirtyParent_expect_orphanDeleted_works() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE - setting a new ArrayList rather than using clear()
var a_c = new OmBeanListChild("b");
secondParent.getChildren().add(a_c);
// working here as secondParent itself is not dirty
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
// Q: is this expected? A: Rob Bygrave - Yes it is expected
assertThat(refreshedParent.getVersion()).isEqualTo(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isEqualTo(parent.getWhenModified());
}
@Test
void usingNewListWithParentDirty_expect_orphanDeleted_fails2() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE: using new ArrayList with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
// force version increase
DB.markAsDirty(secondParent);
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
@Test
void usingClearWithParentDirty_expect_orphanDeleted_works() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.getChildren().clear(); // <!-- HERE: Using clear() with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
// force version increase
DB.markAsDirty(secondParent);
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
@Test
void usingNewListWithParentDirty_expect_orphanDeleted_fails() {
// setup
var parent = new OmBeanListParent();
var a_b = new OmBeanListChild("b");
parent.getChildren().add(a_b);
DB.save(parent);
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
// act
var secondParent = DB.find(OmBeanListParent.class, parent.getId());
secondParent.setChildren2(new ArrayList<>()); // <!-- HERE: new ArrayList with orphanRemoval
var a_c = new OmBeanListChild("c");
secondParent.getChildren().add(a_c);
secondParent.setWhenCreated(Instant.now());
DB.save(secondParent);
var refreshedParent = DB.find(OmBeanListParent.class, parent.getId());
assertThat(refreshedParent.getChildren().size()).isEqualTo(1);
assertThat(refreshedParent.getChildren().get(0).getId()).isEqualTo(a_c.getId());
assertThat(refreshedParent.getVersion()).isGreaterThan(parent.getVersion());
assertThat(refreshedParent.getWhenModified()).isAfter(parent.getWhenModified());
}
}
@@ -6,87 +6,11 @@ import org.junit.jupiter.api.Test;
import org.tests.model.basic.EBasic;
import java.util.List;
import java.util.concurrent.ExecutionException;
import static org.assertj.core.api.Assertions.assertThat;
class TestFindFutureRowCount extends BaseTestCase {
@Test
void futureCount_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("count_batched");
DB.save(basic);
FutureRowCount<EBasic> futureCount = DB.find(EBasic.class)
.where().eq("name", "count_batched")
.findFutureCount();
assertThat(futureCount.get()).isEqualTo(0);
transaction.flush();
FutureRowCount<EBasic> futureCountAfter = DB.find(EBasic.class)
.where().eq("name", "count_batched")
.findFutureCount();
assertThat(futureCountAfter.get()).isEqualTo(1);
}
}
@Test
void futureList_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("list_batched");
DB.save(basic);
FutureList<EBasic> futureList = DB.find(EBasic.class)
.where().eq("name", "list_batched")
.findFutureList();
assertThat(futureList.get()).isEmpty();
transaction.flush();
FutureList<EBasic> futureListAfter = DB.find(EBasic.class)
.where().eq("name", "list_batched")
.findFutureList();
assertThat(futureListAfter.get()).hasSize(1);
}
}
@Test
void futureIds_doesNotTriggerFlush() throws ExecutionException, InterruptedException {
try (Transaction transaction = DB.beginTransaction()) {
transaction.setBatchMode(true);
transaction.setBatchSize(100);
EBasic basic = new EBasic("ids_batched");
DB.save(basic);
FutureIds<EBasic> futureIds = DB.find(EBasic.class)
.where().eq("name", "ids_batched")
.findFutureIds();
assertThat(futureIds.get()).isEmpty();
transaction.flush();
FutureIds<EBasic> futureIdsAfter = DB.find(EBasic.class)
.where().eq("name", "ids_batched")
.findFutureIds();
assertThat(futureIdsAfter.get()).hasSize(1);
}
}
@Test
void count_when_inTransaction() throws Exception {
try (Transaction transaction = DB.beginTransaction()) {
@@ -11,7 +11,6 @@ import org.tests.model.basic.Order;
import org.tests.model.basic.ResetBasicData;
import jakarta.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@@ -270,25 +269,23 @@ public class TestQueryFindPagedList extends BaseTestCase {
@Test
void test_forUpdate() {
if (!isDb2()) {
ResetBasicData.reset();
ResetBasicData.reset();
try (Transaction txn = DB.beginTransaction()) {
PagedList<Order> pagedList = DB.find(Order.class).forUpdate().setMaxRows(2).findPagedList();
try (Transaction txn = DB.beginTransaction()) {
PagedList<Order> pagedList = DB.find(Order.class).forUpdate().setMaxRows(2).findPagedList();
LoggedSql.start();
int totalCount = pagedList.getTotalCount();
assertThat(totalCount).isGreaterThan(2);
LoggedSql.start();
int totalCount = pagedList.getTotalCount();
assertThat(totalCount).isGreaterThan(2);
List<Order> list = pagedList.getList();
assertThat(list).hasSize(2);
List<Order> list = pagedList.getList();
assertThat(list).hasSize(2);
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select count(*) from o_order t0;");
if (isH2() || isPostgresCompatible()) {
assertThat(sql.get(1)).contains(" limit 2 for update;");
}
List<String> sql = LoggedSql.stop();
assertThat(sql).hasSize(2);
assertThat(sql.get(0)).contains("select count(*) from o_order t0;");
if (isH2() || isPostgresCompatible()) {
assertThat(sql.get(1)).contains(" limit 2 for update;");
}
}
}
@@ -1,5 +1,7 @@
package org.tests.query;
import io.ebean.Transaction;
import io.ebean.bean.FrozenBeans;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.InterceptReadOnly;
@@ -7,23 +9,150 @@ import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Query;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import org.tests.model.basic.ResetBasicData;
import org.tests.model.basic.*;
import java.io.*;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
public class TestQueryOrderById extends BaseTestCase {
private Set<Integer> cachedCustomerIds;
@Test
void cachedBeanContext_attach() {
ResetBasicData.reset();
FrozenBeans cachedBeanContext = testSerialiseCachedBeans(buildCachedBeans());
testSerialiseCustomer(cachedCustomers.get(0));
try (Transaction txn = DB.beginTransaction()) {
txn.attach(cachedBeanContext);
List<Order> orders = DB.find(Order.class)
.findList();
for (Order order : orders) {
Customer customer = order.getCustomer();
if (cachedCustomerIds.contains(customer.getId())) {
// Illegal to access customer.orders as that was not loaded and no lazy loading is allowed
// List<Order> orders1 = customer.getOrders();
// assertThat(orders1).isEmpty();
// assertThat(orders1).isSameAs(Collections.EMPTY_LIST);
List<Contact> contacts = customer.getContacts();
assertThat(contacts).isNotNull();
// customer.setContacts(new ArrayList<>());
// customer.setName("modified");
Address billingAddress = customer.getBillingAddress();
if (billingAddress != null) {
String line1 = billingAddress.getLine1();
Country country = billingAddress.getCountry();
}
Address shippingAddress = customer.getShippingAddress();
// if (shippingAddress != null) {
// // shippingAddress.getCountry();
// }
}
}
}
}
private static FrozenBeans testSerialiseCachedBeans(FrozenBeans source) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(source);
oos.close();
byte[] byteArray = os.toByteArray();
var is = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new ObjectInputStream(is);
FrozenBeans result = (FrozenBeans)ois.readObject();
assertThat(result).isNotNull();
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void testSerialiseCustomer(Customer source) {
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(source);
oos.close();
byte[] byteArray = os.toByteArray();
var is = new ByteArrayInputStream(byteArray);
ObjectInputStream ois = new ObjectInputStream(is);
Customer customer = (Customer)ois.readObject();
assertThat(customer.getId()).isEqualTo(source.getId());
assertThat(customer.getName()).isEqualTo(source.getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<Customer> cachedCustomers;
private FrozenBeans buildCachedBeans() {
try (Transaction txn = DB.beginTransaction()) {
List<Customer> list = DB.find(Customer.class)
.setReadOnly(true)
//.setDisableLazyLoading(true)
.orderBy("id")
.setMaxRows(3)
.findList();
// perform some lazy loading if we desire
for (Customer customer : list) {
customer.getContacts().size();
Address billingAddress = customer.getBillingAddress();
if (billingAddress != null) {
Country country = billingAddress.getCountry();
if (country != null) {
country.getName();
}
}
}
// can selectively use DisableLazyLoad which is "lazy loading is NO-operation"
//for (Customer customer : list) {
// DB.beanState(customer).setDisableLazyLoad(true);
//}
cachedCustomerIds = list.stream()
.map(BasicDomain::getId)
.collect(Collectors.toSet());
cachedCustomers = list;
return txn.freezeAndDetach();
}
}
@Test
public void orderById_default_expectNotOrderById() {
ResetBasicData.reset();
Query<Customer> query = DB.find(Customer.class)
.select("id,name")
.order("id")
.orderBy("id")
.setFirstRow(1)
.setMaxRows(5);
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
</parent>
<name>kotlin querybean generator</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
@@ -56,14 +56,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>13.26.1-jakarta</version>
<version>13.26.0-jakarta</version>
<scope>test</scope>
</dependency>

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