mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-20 11:17:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d2cc2d4af | ||
|
|
c73330e9de | ||
|
|
a317d669f0 | ||
|
|
291966cea9 | ||
|
|
ad05ed051b | ||
|
|
01b8c3dbcb | ||
|
|
443b68a3b0 | ||
|
|
98f0d42b7e | ||
|
|
959951203d | ||
|
|
c47fbd411b | ||
|
|
4b75820fd6 | ||
|
|
97bd0e1bb8 | ||
|
|
0f5c7e4390 | ||
|
|
d7d040d031 | ||
|
|
6ec7c61594 | ||
|
|
0126470391 | ||
|
|
d5f32690ea | ||
|
|
d7a3417fe4 | ||
|
|
141f98f5d7 | ||
|
|
fd74fed34f | ||
|
|
274411b8fa | ||
|
|
691f153d89 | ||
|
|
dd1acf58a0 | ||
|
|
7bf8f7fbce | ||
|
|
490c70a7b7 | ||
|
|
ca19a78c23 | ||
|
|
66d599faa2 | ||
|
|
e3dad5bd44 | ||
|
|
f50d56faee |
@@ -17,7 +17,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
@@ -40,5 +40,7 @@ jobs:
|
||||
# - name: Maven single test
|
||||
# run: mvn --batch-mode clean verify -Dtest="io.ebeaninternal.server.core.DefaultServer_getReferenceTest" -DfailIfNoTests=false
|
||||
- name: Build with Maven
|
||||
run: mvn -T 1C clean test -Pdefault
|
||||
run: mvn -T 1C clean install -Pdefault
|
||||
- name: Test SequencedSet and SequencedMap (requires installed MR-JAR)
|
||||
run: cd tests/test-java16 && mvn test
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11, 17, 21]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [11]
|
||||
java_version: [21]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-clickhouse</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-db2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-hana</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mariadb</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mysql</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-net-postgis-types</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-nuodb</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-oracle</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlite</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -60,13 +60,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-all</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>composites</artifactId>
|
||||
|
||||
@@ -14,6 +14,7 @@ Key guides (fetch and follow when performing the relevant task):
|
||||
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
|
||||
- Migrate JSON APIs from Jackson core to avaje-json-core: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-json-jackson-core-to-avaje-json-core.md
|
||||
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
|
||||
- Derived / formula properties (`@Formula`, `@Formula2`): https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/derived-formula-properties.md
|
||||
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
|
||||
- Query metrics and naming: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-metrics.md
|
||||
- Query plan capture: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-plan-capture.md
|
||||
|
||||
@@ -40,6 +40,7 @@ existing Maven project. Complete the steps in order.
|
||||
| [Entity Bean Creation](entity-bean-creation.md) | How to generate clean, idiomatic Ebean entity beans for AI agents; patterns and anti-patterns; field visibility and accessor guidance; minimal boilerplate |
|
||||
| [Lombok with Ebean entity beans](lombok-with-ebean-entity-beans.md) | Which Lombok annotations to use and avoid on entity beans; why `@Data` is incompatible with Ebean; how to use `@Getter` + `@Setter` + `@Accessors(chain = true)` |
|
||||
| [`@DbJson` mapping support (built-in vs Jackson)](dbjson-mapping-support.md) | Which `@DbJson` / `@DbJsonB` property types are handled by the built-in avaje-json-core support versus which require `ebean-jackson-mapper` (Jackson `ObjectMapper`); supported `String`/`List`/`Set`/`Map` matrix; enum-key and `@DbArray` notes |
|
||||
| [Derived / formula properties (`@Formula`, `@Formula2`)](derived-formula-properties.md) | Read-only computed properties: physical-SQL `@Formula` (with `${ta}` and hand-written joins) versus logical path-based `@Formula2` (auto-resolved joins); use in `select`/`where`/`orderBy`; default inclusion and the `@Transient` opt-out |
|
||||
|
||||
## Querying
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
# Guide: Derived / formula properties — `@Formula` and `@Formula2`
|
||||
|
||||
## Purpose
|
||||
|
||||
A *formula property* is a read-only entity property whose value is computed by a SQL
|
||||
expression at query time rather than stored in its own column. Ebean has two
|
||||
annotations for this:
|
||||
|
||||
- **`@Formula`** — you write the **physical SQL** for the `select` (and any `join`),
|
||||
using the `${ta}` placeholder for the base table alias. Maximum control; verbose.
|
||||
- **`@Formula2`** — you write a **logical expression** using dot-notation property
|
||||
paths (e.g. `parent.familyName`). Ebean translates the paths to the correct table
|
||||
aliases and **adds the required JOINs automatically**.
|
||||
|
||||
`@Formula2` is intended as the easier, path-based replacement for `@Formula`. Both
|
||||
produce read-only properties and behave the same way with respect to default
|
||||
inclusion (see [Default inclusion](#default-inclusion-and-transient)).
|
||||
|
||||
---
|
||||
|
||||
## Quick comparison
|
||||
|
||||
| | `@Formula` | `@Formula2` |
|
||||
|---|---|---|
|
||||
| Expression | Physical SQL columns + aliases | Logical property paths |
|
||||
| Table alias | `${ta}` placeholder you write | Resolved automatically |
|
||||
| Joins | You write the `join` clause | Added automatically from the paths |
|
||||
| Read only | ✅ | ✅ |
|
||||
| Included by default | ✅ (use `@Transient` to opt out) | ✅ (use `@Transient` to opt out) |
|
||||
| Usable in `select` / `where` / `orderBy` / `having` | ✅ | ✅ |
|
||||
| Creates a DB column (DDL) | ❌ | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## `@Formula` — physical SQL
|
||||
|
||||
You supply the SQL `select` fragment, and an optional `join`. Use `${ta}` wherever you
|
||||
need the base table alias of the entity.
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class ParentPerson {
|
||||
|
||||
// aggregation via a derived join; ${ta} is the base table alias
|
||||
@Formula(select = "coalesce(f2.child_count, 0)",
|
||||
join = "left join (select parent_id, count(*) as child_count"
|
||||
+ " from child group by parent_id) f2 on f2.parent_id = ${ta}.id")
|
||||
Integer childCount;
|
||||
|
||||
// coalesce across a joined table using an explicit join alias (j1)
|
||||
@Formula(select = "coalesce(${ta}.family_name, j1.family_name)",
|
||||
join = "join parent_person j1 on j1.id = ${ta}.parent_id")
|
||||
String effectiveFamilyName;
|
||||
}
|
||||
```
|
||||
|
||||
Notes:
|
||||
- The `join` string must start with `join` or `left join`.
|
||||
- You manage the join aliases (`j1`, `f2`, …) yourself and reference them in `select`.
|
||||
- `@Formula` is `@Repeatable` and supports a `platforms()` restriction.
|
||||
|
||||
---
|
||||
|
||||
## `@Formula2` — logical property paths
|
||||
|
||||
Write the expression using property paths. Ebean resolves each path to the right table
|
||||
alias and adds the joins it needs.
|
||||
|
||||
```java
|
||||
@Entity
|
||||
public class ParentPerson {
|
||||
|
||||
@ManyToOne
|
||||
GrandParentPerson parent;
|
||||
|
||||
String familyName;
|
||||
|
||||
// Ebean automatically left joins 'parent' and resolves the aliases
|
||||
@Formula2("coalesce(familyName, parent.familyName)")
|
||||
String derivedFamilyName;
|
||||
}
|
||||
```
|
||||
|
||||
A query selecting `derivedFamilyName` produces (roughly):
|
||||
|
||||
```sql
|
||||
select t0.id, coalesce(t0.family_name, t1.family_name)
|
||||
from parent_person t0
|
||||
left join grand_parent_person t1 on t1.id = t0.parent_id
|
||||
```
|
||||
|
||||
Multi-level paths join through each step:
|
||||
|
||||
```java
|
||||
// joins parent and parent.parent automatically
|
||||
@Formula2("coalesce(familyName, parent.familyName, parent.parent.familyName)")
|
||||
String deepFamilyName;
|
||||
```
|
||||
|
||||
`@Formula2` works wherever a normal property does — the required joins are added
|
||||
automatically in each case:
|
||||
|
||||
```java
|
||||
// selected explicitly
|
||||
DB.find(ParentPerson.class).select("derivedFamilyName").findList();
|
||||
|
||||
// used in where (auto-joins even when not selected)
|
||||
DB.find(ParentPerson.class).where().eq("derivedFamilyName", "Smith").findList();
|
||||
|
||||
// used in order by
|
||||
DB.find(ParentPerson.class).orderBy("derivedFamilyName").findList();
|
||||
|
||||
// referenced via a path from another bean
|
||||
DB.find(ChildPerson.class).where().eq("parent.derivedFamilyName", "Smith").findList();
|
||||
```
|
||||
|
||||
It also resolves correctly inside nested `fetch()` joins, so a `@Formula2` on a fetched
|
||||
association is computed with its own joins relative to that association.
|
||||
|
||||
Notes:
|
||||
- The expression supports any SQL function whose arguments are logical property paths.
|
||||
- `@Formula2` supports a `platforms()` restriction.
|
||||
- No `${ta}` and no hand-written join — that is the point of `@Formula2`.
|
||||
|
||||
---
|
||||
|
||||
## Default inclusion and `@Transient`
|
||||
|
||||
Both annotations are **included in queries by default** (just like a normal mapped
|
||||
property). When no explicit `select()`/`fetch()` is given, the formula — and for
|
||||
`@Formula2` the joins it requires — are added to the query.
|
||||
|
||||
Add `@Transient` to make the formula **opt-in**: it is then **not** selected by default
|
||||
and must be requested explicitly via `select()` or `fetch()`. Do this when the formula
|
||||
(or the joins it needs) is relatively expensive.
|
||||
|
||||
```java
|
||||
// not selected by default; must be requested explicitly
|
||||
@Transient
|
||||
@Formula2("coalesce(familyName, parent.familyName)")
|
||||
String lazyDerivedFamilyName;
|
||||
```
|
||||
|
||||
```java
|
||||
DB.find(ParentPerson.class)
|
||||
.select("lazyDerivedFamilyName") // explicitly included, join auto-added
|
||||
.findList();
|
||||
```
|
||||
|
||||
This is the same `@Transient` opt-out mechanism used by `@Formula`.
|
||||
|
||||
---
|
||||
|
||||
## Which should I use?
|
||||
|
||||
- Prefer **`@Formula2`** for expressions over property paths (coalesce/case/functions
|
||||
across associations). It is shorter, refactor-friendly, and the joins stay correct as
|
||||
the model changes.
|
||||
- Use **`@Formula`** when you need raw SQL that does not map cleanly to property paths —
|
||||
for example a derived aggregate sub-select / dynamic view, or vendor-specific SQL.
|
||||
|
||||
For read models that exist only to carry computed values, also consider projecting to a
|
||||
DTO instead of mapping the formula onto the entity — see
|
||||
[writing-ebean-query-beans.md](writing-ebean-query-beans.md).
|
||||
+48
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -103,6 +103,53 @@
|
||||
</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>
|
||||
|
||||
@@ -61,6 +61,10 @@ public interface DatabaseBuilder {
|
||||
|
||||
/**
|
||||
* Build and return the Database instance.
|
||||
* <p>
|
||||
* When {@link #setRegister(boolean)} is set to true, and a database with the same
|
||||
* name is already registered, this may return the existing registered database
|
||||
* rather than creating a new one.
|
||||
*/
|
||||
Database build();
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import jakarta.persistence.PersistenceException;
|
||||
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static java.lang.System.Logger.Level.WARNING;
|
||||
|
||||
/**
|
||||
* Low-level factory for creating {@link Database} instances.
|
||||
* <p>
|
||||
@@ -71,18 +73,28 @@ public final class DatabaseFactory {
|
||||
lock.lock();
|
||||
try {
|
||||
var config = builder.settings();
|
||||
if (config.getName() == null) {
|
||||
var name = config.getName();
|
||||
if (name == null) {
|
||||
throw new PersistenceException("The name is null (it is required)");
|
||||
}
|
||||
if (config.isRegister()) {
|
||||
// We're explicitly creating a database to be registered, so avoid
|
||||
// triggering DbContext static initialisation to auto-create a default one.
|
||||
DbPrimary.setSkip(true);
|
||||
Database existing = DbContext.getInstance().getRegistered(name);
|
||||
if (existing != null) {
|
||||
EbeanVersion.log.log(WARNING, "Using existing database with name:{0}", name);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
Database server = createInternal(config);
|
||||
if (config.isRegister()) {
|
||||
if (config.isDefaultServer()) {
|
||||
if (defaultServerName != null && !defaultServerName.equals(config.getName())) {
|
||||
throw new IllegalStateException("Registering [" + config.getName() + "] as the default server but [" + defaultServerName + "] is already registered as the default");
|
||||
if (defaultServerName != null && !defaultServerName.equals(name)) {
|
||||
throw new IllegalStateException("Registering [" + name + "] as the default server but [" + defaultServerName + "] is already registered as the default");
|
||||
}
|
||||
defaultServerName = config.getName();
|
||||
defaultServerName = name;
|
||||
}
|
||||
DbPrimary.setSkip(true);
|
||||
DbContext.getInstance().register(server, config.isDefaultServer());
|
||||
}
|
||||
return server;
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.ebean.config.BeanNotEnhancedException;
|
||||
import io.ebean.datasource.DataSourceConfigurationException;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@@ -75,6 +77,11 @@ final class DbContext {
|
||||
return defaultDatabase;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Database getRegistered(String name) {
|
||||
return concMap.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the database by name.
|
||||
*/
|
||||
|
||||
@@ -260,6 +260,27 @@ public interface Transaction extends AutoCloseable {
|
||||
*/
|
||||
void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties);
|
||||
|
||||
/**
|
||||
* Set to false to disable auto-generation of {@code @WhenCreated}, {@code @WhenModified},
|
||||
* {@code @WhoCreated} and {@code @WhoModified} values for this transaction.
|
||||
* <p>
|
||||
* When disabled, Ebean will only set a generated property value if the property currently
|
||||
* has a null value (for inserts) or is a {@code @Version} property. Any value already set
|
||||
* on the bean is preserved.
|
||||
* <p>
|
||||
* This is useful in backup and restore scenarios where you need to retain the original
|
||||
* audit timestamps and user values rather than have them overwritten.
|
||||
* <pre>{@code
|
||||
* try (Transaction txn = DB.beginTransaction()) {
|
||||
* txn.setGeneratedPropertiesEnabled(false);
|
||||
* bean.setWhenCreated(originalTimestamp);
|
||||
* DB.save(bean);
|
||||
* txn.commit();
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
void setGeneratedPropertiesEnabled(boolean enable);
|
||||
|
||||
/**
|
||||
* Set if the L2 cache should be skipped for "find by id" and "find by natural key" queries.
|
||||
* <p>
|
||||
|
||||
@@ -151,14 +151,16 @@ public final class BeanList<E> extends AbstractBeanCollection<E> implements List
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
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 io.ebean.bean.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -17,12 +14,12 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
/**
|
||||
* The underlying map implementation.
|
||||
*/
|
||||
private Map<K, E> map;
|
||||
private LinkedHashMap<K, E> map;
|
||||
|
||||
/**
|
||||
* Create with a given Map.
|
||||
*/
|
||||
public BeanMap(Map<K, E> map) {
|
||||
public BeanMap(LinkedHashMap<K, E> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
@@ -165,18 +162,23 @@ public final class BeanMap<K, E> extends AbstractBeanCollection<E> implements Ma
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the actual underlying map. Used for performing lazy fetch.
|
||||
*/
|
||||
public LinkedHashMap<K, E> collectionAdd() {
|
||||
if (map == null) {
|
||||
map = new LinkedHashMap<>();
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setActualMap(Map<?, ?> map) {
|
||||
this.map = (Map<K, E>) map;
|
||||
public void refresh(ModifyListenMode modifyListenMode, BeanMap<?, ?> newMap) {
|
||||
setModifyListening(modifyListenMode);
|
||||
this.map = (LinkedHashMap<K, E>) newMap.actualMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the actual underlying map.
|
||||
*/
|
||||
public Map<K, E> actualMap() {
|
||||
public LinkedHashMap<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 Set<E> set;
|
||||
private LinkedHashSet<E> set;
|
||||
|
||||
/**
|
||||
* Create with a specific Set implementation.
|
||||
*/
|
||||
public BeanSet(Set<E> set) {
|
||||
public BeanSet(LinkedHashSet<E> set) {
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
@@ -146,18 +146,22 @@ public final class BeanSet<E> extends AbstractBeanCollection<E> implements Set<E
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the underlying set (used for lazy fetch).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setActualSet(Set<?> set) {
|
||||
this.set = (Set<E>) set;
|
||||
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 Set<E> actualSet() {
|
||||
public LinkedHashSet<E> actualSet() {
|
||||
return set;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@@ -23,18 +25,36 @@ import static java.lang.System.Logger.Level.ERROR;
|
||||
|
||||
/**
|
||||
* Database sequence based IdGenerator.
|
||||
* <p>
|
||||
* Maintains a separate buffer of pre-fetched id values per tenant when the supplied
|
||||
* DataSource implements {@link TenantConnectionSource}. For the common single-tenant
|
||||
* case a single buffer is used (keyed by {@link #SINGLE}).
|
||||
*/
|
||||
public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
|
||||
protected static final System.Logger log = AppLog.getLogger("io.ebean.SEQ");
|
||||
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
/**
|
||||
* Buffer key used when there is no current tenant (single-tenant or no tenant in scope).
|
||||
*/
|
||||
private static final Object SINGLE = new Object();
|
||||
|
||||
protected final String seqName;
|
||||
protected final DataSource dataSource;
|
||||
protected final BackgroundExecutor backgroundExecutor;
|
||||
protected final NavigableSet<Long> idList = new TreeSet<>();
|
||||
protected final int allocationSize;
|
||||
protected AtomicBoolean currentlyBackgroundLoading = new AtomicBoolean(false);
|
||||
private final TenantConnectionSource tenantSource;
|
||||
private final TenantBuffer single = new TenantBuffer();
|
||||
private final ConcurrentMap<Object, TenantBuffer> buffers = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Per-tenant pre-fetched id buffer with its own lock and background-loading flag.
|
||||
*/
|
||||
private static final class TenantBuffer {
|
||||
final ReentrantLock lock = new ReentrantLock();
|
||||
final Deque<Long> idList = new ArrayDeque<>();
|
||||
final AtomicBoolean currentlyBackgroundLoading = new AtomicBoolean(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct given a dataSource and sql to return the next sequence value.
|
||||
@@ -44,6 +64,7 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
this.dataSource = ds;
|
||||
this.seqName = seqName;
|
||||
this.allocationSize = allocationSize;
|
||||
this.tenantSource = (ds instanceof TenantConnectionSource) ? (TenantConnectionSource) ds : null;
|
||||
}
|
||||
|
||||
public abstract String getSql(int batchSize);
|
||||
@@ -64,6 +85,24 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Object currentTenantKey() {
|
||||
if (tenantSource != null) {
|
||||
Object tenantId = tenantSource.currentTenantId();
|
||||
if (tenantId != null) {
|
||||
return tenantId;
|
||||
}
|
||||
}
|
||||
return SINGLE;
|
||||
}
|
||||
|
||||
private TenantBuffer buffer(Object tenantKey) {
|
||||
if (tenantKey == SINGLE) {
|
||||
// common single-tenant path - avoid the concurrent map lookup
|
||||
return single;
|
||||
}
|
||||
return buffers.computeIfAbsent(tenantKey, k -> new TenantBuffer());
|
||||
}
|
||||
|
||||
/**
|
||||
* If allocateSize is large load some sequences in a background thread.
|
||||
* <p>
|
||||
@@ -78,23 +117,22 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
|
||||
/**
|
||||
* Return the next Id.
|
||||
* <p>
|
||||
* If a Transaction has been passed in use the Connection from it.
|
||||
* </p>
|
||||
*/
|
||||
@Override
|
||||
public Object nextId(Transaction t) {
|
||||
lock.lock();
|
||||
Object tenantKey = currentTenantKey();
|
||||
TenantBuffer buffer = buffer(tenantKey);
|
||||
buffer.lock.lock();
|
||||
try {
|
||||
int size = idList.size();
|
||||
int size = buffer.idList.size();
|
||||
if (size > 0) {
|
||||
maybeLoadMoreInBackground(size);
|
||||
} else {
|
||||
loadMore(allocationSize);
|
||||
loadMore(tenantKey, buffer, allocationSize);
|
||||
}
|
||||
return idList.pollFirst();
|
||||
return buffer.idList.poll();
|
||||
} finally {
|
||||
lock.unlock();
|
||||
buffer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,29 +144,36 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadMore(int requestSize) {
|
||||
List<Long> newIds = getMoreIds(requestSize);
|
||||
lock.lock();
|
||||
private void loadMore(Object tenantKey, TenantBuffer buffer, int requestSize) {
|
||||
List<Long> newIds = getMoreIds(tenantKey, requestSize);
|
||||
buffer.lock.lock();
|
||||
try {
|
||||
idList.addAll(newIds);
|
||||
buffer.idList.addAll(newIds);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
buffer.lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load another batch of Id's using a background thread.
|
||||
* <p>
|
||||
* The tenant is captured here (submit time) as the current tenant is not in scope
|
||||
* on the background executor thread.
|
||||
*/
|
||||
protected void loadInBackground(final int requestSize) {
|
||||
if (currentlyBackgroundLoading.get()) {
|
||||
final Object tenantKey = currentTenantKey();
|
||||
final TenantBuffer buffer = buffer(tenantKey);
|
||||
if (!buffer.currentlyBackgroundLoading.compareAndSet(false, true)) {
|
||||
// skip as already background loading
|
||||
log.log(DEBUG, "... skip background sequence load (another load in progress)");
|
||||
return;
|
||||
}
|
||||
currentlyBackgroundLoading.set(true);
|
||||
backgroundExecutor.execute(() -> {
|
||||
loadMore(requestSize);
|
||||
currentlyBackgroundLoading.set(false);
|
||||
try {
|
||||
loadMore(tenantKey, buffer, requestSize);
|
||||
} finally {
|
||||
buffer.currentlyBackgroundLoading.set(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -140,7 +185,7 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
/**
|
||||
* Get more Id's by executing a query and reading the Id's returned.
|
||||
*/
|
||||
protected List<Long> getMoreIds(int requestSize) {
|
||||
protected List<Long> getMoreIds(Object tenantKey, int requestSize) {
|
||||
|
||||
String sql = getSql(requestSize);
|
||||
|
||||
@@ -148,7 +193,7 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
PreparedStatement statement = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
connection = dataSource.getConnection();
|
||||
connection = connectionFor(tenantKey);
|
||||
|
||||
statement = connection.prepareStatement(sql);
|
||||
resultSet = statement.executeQuery();
|
||||
@@ -174,6 +219,17 @@ public abstract class SequenceIdGenerator implements PlatformIdGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a connection for the given tenant. For multi-tenant this is routed to the
|
||||
* tenant database/schema/catalog; otherwise the plain DataSource connection is used.
|
||||
*/
|
||||
private Connection connectionFor(Object tenantKey) throws SQLException {
|
||||
if (tenantSource != null && tenantKey != SINGLE) {
|
||||
return tenantSource.connectionForTenant(tenantKey);
|
||||
}
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the JDBC resources.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package io.ebean.config.dbplatform;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Optionally implemented by the DataSource passed to a {@link SequenceIdGenerator}
|
||||
* to make sequence id allocation multi-tenant aware.
|
||||
* <p>
|
||||
* When the DataSource implements this interface the sequence generator maintains
|
||||
* a separate id buffer per tenant and obtains connections that are routed to the
|
||||
* correct tenant database (TenantMode.DB) or schema/catalog (TenantMode.SCHEMA / CATALOG).
|
||||
* <p>
|
||||
* The {@link #connectionForTenant(Object)} method takes an explicit tenantId so that
|
||||
* background pre-fetch (which runs on a separate thread without the current tenant
|
||||
* in scope) can fetch sequence values for the tenant captured at submit time.
|
||||
*/
|
||||
public interface TenantConnectionSource {
|
||||
|
||||
/**
|
||||
* Return the current tenant id, or null when there is no current tenant scope.
|
||||
*/
|
||||
Object currentTenantId();
|
||||
|
||||
/**
|
||||
* Return a connection routed to the given tenant (its database, schema or catalog).
|
||||
*/
|
||||
Connection connectionForTenant(Object tenantId) throws SQLException;
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import io.ebean.bean.*;
|
||||
|
||||
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 Map<K, E> freeze() {
|
||||
return map == null ? null : Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
@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() {
|
||||
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();
|
||||
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();
|
||||
return modifyListening ? new ModifyKeySet<>(this, map.keySet()) : map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E put(K key, E value) {
|
||||
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) {
|
||||
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 UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeBean(E bean) {
|
||||
throw new UnsupportedOperationException("Method not allowed on Map. Please use List instead.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public E remove(Object key) {
|
||||
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();
|
||||
return modifyListening ? new ModifyCollection<>(this, map.values()) : map.values();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------//
|
||||
// SequencedMap (Java 21+)
|
||||
// -----------------------------------------------------//
|
||||
|
||||
@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() {
|
||||
init();
|
||||
Entry<K, E> entry = map.pollFirstEntry();
|
||||
if (modifyListening && entry != null) {
|
||||
modifyRemoval(entry.getValue());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<K, E> pollLastEntry() {
|
||||
init();
|
||||
Entry<K, E> entry = map.pollLastEntry();
|
||||
if (modifyListening && entry != null) {
|
||||
modifyRemoval(entry.getValue());
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public E putFirst(K key, E value) {
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,419 @@
|
||||
package io.ebean.common;
|
||||
|
||||
import io.ebean.bean.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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 Set<E> freeze() {
|
||||
return set == null ? null : Collections.unmodifiableSet(set);
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
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() {
|
||||
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 (modifyListening) {
|
||||
return new ModifyIterator<>(this, set.iterator());
|
||||
}
|
||||
return set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object bean) {
|
||||
init();
|
||||
if (modifyListening) {
|
||||
if (set.remove(bean)) {
|
||||
modifyRemoval(bean);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return set.remove(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> beans) {
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------//
|
||||
// SequencedSet (Java 21+)
|
||||
// -----------------------------------------------------//
|
||||
|
||||
@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) {
|
||||
init();
|
||||
if (modifyListening) {
|
||||
modifyAddition(bean);
|
||||
}
|
||||
set.addFirst(bean);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLast(E bean) {
|
||||
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() {
|
||||
init();
|
||||
if (modifyListening) {
|
||||
E bean = set.removeFirst();
|
||||
modifyRemoval(bean);
|
||||
return bean;
|
||||
}
|
||||
return set.removeFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public E removeLast() {
|
||||
init();
|
||||
if (modifyListening) {
|
||||
E bean = set.removeLast();
|
||||
modifyRemoval(bean);
|
||||
return bean;
|
||||
}
|
||||
return set.removeLast();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -136,13 +136,13 @@ class ToStringBuilderTest {
|
||||
@Test
|
||||
void beanSet_null_empty() {
|
||||
assertThat(toStringFor(new BeanSet<String>(null))).isEqualTo("[]");
|
||||
assertThat(toStringFor(new BeanSet<String>(Collections.emptySet()))).isEqualTo("[]");
|
||||
assertThat(toStringFor(new BeanSet<String>(new LinkedHashSet<>()))).isEqualTo("[]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void beanMap_null_empty() {
|
||||
assertThat(toStringFor(new BeanMap<String, String>(null))).isEqualTo("{}");
|
||||
assertThat(toStringFor(new BeanMap<String, String>(Collections.emptyMap()))).isEqualTo("{}");
|
||||
assertThat(toStringFor(new BeanMap<String, String>(new LinkedHashMap<>()))).isEqualTo("{}");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -159,7 +159,7 @@ class ToStringBuilderTest {
|
||||
|
||||
@Test
|
||||
void beanMap_some() {
|
||||
Map<String, Recurse> under = new LinkedHashMap<>();
|
||||
var under = new LinkedHashMap<String, Recurse>();
|
||||
under.put("a", new Recurse(1, "a"));
|
||||
under.put("b", new Recurse(2, "b"));
|
||||
BeanMap<String, Recurse> list = new BeanMap<>(under);
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-bench</artifactId>
|
||||
|
||||
+28
-28
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -89,25 +89,25 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -125,13 +125,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -155,37 +155,37 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-spring-txn</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- platforms -->
|
||||
@@ -193,91 +193,91 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-clickhouse</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-db2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-h2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-hana</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mariadb</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mysql</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-nuodb</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-oracle</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlite</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlserver</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<name>ebean-core-json</name>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -157,21 +157,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>18.0.0</version>
|
||||
<version>18.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -104,6 +104,11 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
Boolean isUpdateAllLoadedProperties();
|
||||
|
||||
/**
|
||||
* Return true if generated properties ({@code @WhenCreated} etc.) are enabled for this transaction.
|
||||
*/
|
||||
boolean isGeneratedPropertiesEnabled();
|
||||
|
||||
/**
|
||||
* Return the batchSize specifically set for this transaction or 0.
|
||||
* <p>
|
||||
|
||||
@@ -244,6 +244,16 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
transaction.setUpdateAllLoadedProperties(updateAllLoaded);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGeneratedPropertiesEnabled(boolean enable) {
|
||||
transaction.setGeneratedPropertiesEnabled(enable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isGeneratedPropertiesEnabled() {
|
||||
return transaction.isGeneratedPropertiesEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean isUpdateAllLoadedProperties() {
|
||||
return transaction.isUpdateAllLoadedProperties();
|
||||
|
||||
@@ -150,4 +150,45 @@ public final class TransactionEvent implements Serializable {
|
||||
return changeSet;
|
||||
}
|
||||
|
||||
public void merge(TransactionEvent other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge table events
|
||||
if (other.eventTables != null) {
|
||||
if (this.eventTables == null) {
|
||||
this.eventTables = other.eventTables;
|
||||
} else {
|
||||
this.eventTables.add(other.eventTables);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge listeners
|
||||
if (other.listenerNotify != null) {
|
||||
if (this.listenerNotify == null) {
|
||||
this.listenerNotify = other.listenerNotify;
|
||||
} else {
|
||||
this.listenerNotify.addAll(other.listenerNotify);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge delete-by-id
|
||||
if (other.deleteByIdMap != null) {
|
||||
if (this.deleteByIdMap == null) {
|
||||
this.deleteByIdMap = other.deleteByIdMap;
|
||||
} else {
|
||||
this.deleteByIdMap.merge(other.deleteByIdMap);
|
||||
}
|
||||
}
|
||||
|
||||
// Merge cache changes
|
||||
if (other.changeSet != null) {
|
||||
if (this.changeSet == null) {
|
||||
this.changeSet = other.changeSet;
|
||||
} else {
|
||||
this.changeSet.merge(other.changeSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -42,4 +42,8 @@ final class CacheChangeBeanRemove implements CacheChange {
|
||||
public void addId(Object id) {
|
||||
ids.add(id);
|
||||
}
|
||||
|
||||
void merge(CacheChangeBeanRemove other) {
|
||||
this.ids.addAll(other.ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +184,32 @@ public final class CacheChangeSet {
|
||||
return manyChangeMap.computeIfAbsent(key, ManyChange::new);
|
||||
}
|
||||
|
||||
public void merge(CacheChangeSet other) {
|
||||
if (other == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.entries.addAll(other.entries);
|
||||
this.touchedTables.addAll(other.touchedTables);
|
||||
this.queryCaches.addAll(other.queryCaches);
|
||||
this.beanCaches.addAll(other.beanCaches);
|
||||
|
||||
other.beanRemoveMap.forEach((desc, remove) ->
|
||||
this.beanRemoveMap.merge(desc, remove, (a, b) -> {
|
||||
a.merge(b);
|
||||
return a;
|
||||
})
|
||||
);
|
||||
|
||||
other.manyChangeMap.forEach((key, change) ->
|
||||
this.manyChangeMap.merge(key, change, (a, b) -> {
|
||||
a.merge(b);
|
||||
return a;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Changes for a specific many property.
|
||||
*/
|
||||
@@ -215,6 +241,32 @@ public final class CacheChangeSet {
|
||||
}
|
||||
}
|
||||
|
||||
void merge(ManyChange other) {
|
||||
// clear dominates everything
|
||||
if (other.clear) {
|
||||
this.clear = true;
|
||||
this.removes.clear();
|
||||
this.puts.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.clear) {
|
||||
// already clearing, ignore finer changes
|
||||
return;
|
||||
}
|
||||
|
||||
// merge puts (put overrides remove)
|
||||
this.puts.putAll(other.puts);
|
||||
|
||||
// merge removes, but do not remove something we just put
|
||||
for (String key : other.removes) {
|
||||
if (!this.puts.containsKey(key)) {
|
||||
this.removes.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Put entry for the given parentId.
|
||||
*/
|
||||
|
||||
@@ -81,6 +81,7 @@ public final class InternalConfiguration {
|
||||
private final Binder binder;
|
||||
private final DeployCreateProperties deployCreateProperties;
|
||||
private final DeployUtil deployUtil;
|
||||
private final DataSourceSupplier dataSourceSupplier;
|
||||
private final BeanDescriptorManager beanDescriptorManager;
|
||||
private final CQueryEngine cQueryEngine;
|
||||
private final ClusterManager clusterManager;
|
||||
@@ -124,6 +125,7 @@ public final class InternalConfiguration {
|
||||
|
||||
final InternalConfigXmlMap xmlMap = initExternalMapping();
|
||||
this.dtoBeanManager = new DtoBeanManager(typeManager, xmlMap.readDtoMapping());
|
||||
this.dataSourceSupplier = createDataSourceSupplier();
|
||||
this.beanDescriptorManager = new BeanDescriptorManager(this);
|
||||
Map<String, String> asOfTableMapping = beanDescriptorManager.deploy(xmlMap.xmlDeployment());
|
||||
Map<String, String> draftTableMap = beanDescriptorManager.draftTableMap();
|
||||
@@ -391,7 +393,7 @@ public final class InternalConfiguration {
|
||||
|
||||
TransactionManagerOptions options =
|
||||
new TransactionManagerOptions(server, notifyL2CacheInForeground, config, scopeManager, clusterManager, backgroundExecutor,
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager,
|
||||
indexUpdateProcessor, beanDescriptorManager, dataSourceSupplier, profileHandler(), logManager,
|
||||
tableModState, cacheNotify);
|
||||
|
||||
if (config.isDocStoreOnly()) {
|
||||
@@ -409,9 +411,16 @@ public final class InternalConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DataSource supplier based on the tenancy mode.
|
||||
* Return the DataSource supplier (multi-tenant aware) based on the tenancy mode.
|
||||
*/
|
||||
private DataSourceSupplier dataSource() {
|
||||
public DataSourceSupplier getDataSourceSupplier() {
|
||||
return dataSourceSupplier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the DataSource supplier based on the tenancy mode.
|
||||
*/
|
||||
private DataSourceSupplier createDataSourceSupplier() {
|
||||
switch (config.getTenantMode()) {
|
||||
case DB:
|
||||
case DB_WITH_MASTER:
|
||||
|
||||
@@ -42,7 +42,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
private SpiQuerySecondary secondaryQueries;
|
||||
private List<T> cacheBeans;
|
||||
private boolean inlineCountDistinct;
|
||||
private Set<String> dependentTables;
|
||||
private SpiQueryManyJoin manyJoin;
|
||||
|
||||
public OrmQueryRequest(SpiEbeanServer server, OrmQueryEngine queryEngine, SpiQuery<T> query, SpiTransaction t) {
|
||||
@@ -667,7 +666,11 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
}
|
||||
|
||||
public void putToQueryCache(Object result) {
|
||||
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startTime()));
|
||||
CQueryPlan plan = queryPlan();
|
||||
if (plan != null) {
|
||||
// only cache when we have the plan's dependent tables
|
||||
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, plan.dependentTables(), transaction.startTime()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,15 +740,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
return inlineCountDistinct;
|
||||
}
|
||||
|
||||
public void addDependentTables(Set<String> tables) {
|
||||
if (tables != null && !tables.isEmpty()) {
|
||||
if (dependentTables == null) {
|
||||
dependentTables = new LinkedHashSet<>();
|
||||
}
|
||||
dependentTables.addAll(tables);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if no MaxRows or use LIMIT in SQL update.
|
||||
*/
|
||||
|
||||
@@ -262,13 +262,13 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
GeneratedProperty generatedProperty = prop.generatedProperty();
|
||||
if (prop.isVersion()) {
|
||||
if (isLoadedProperty(prop)) {
|
||||
// @Version property must be loaded to be involved
|
||||
// @Version property must be loaded to be involved — always auto-incremented
|
||||
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
|
||||
Object oldVal = prop.getValue(entityBean);
|
||||
setVersionValue(value);
|
||||
intercept.setOldValue(prop.propertyIndex(), oldVal);
|
||||
}
|
||||
} else {
|
||||
} else if (transaction == null || transaction.isGeneratedPropertiesEnabled()) {
|
||||
// @WhenModified set without invoking interception
|
||||
Object oldVal = prop.getValue(entityBean);
|
||||
Object value = generatedProperty.getUpdateValue(prop, entityBean, now());
|
||||
@@ -280,17 +280,22 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
|
||||
private void onFailedUpdateUndoGeneratedProperties() {
|
||||
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
|
||||
Object oldVal = intercept.origValue(prop.propertyIndex());
|
||||
if (oldVal != null) {
|
||||
prop.setValue(entityBean, oldVal);
|
||||
if (prop.isVersion() || transaction == null || transaction.isGeneratedPropertiesEnabled()) {
|
||||
// undo version always (it was always set); undo others only if they were set
|
||||
Object oldVal = intercept.origValue(prop.propertyIndex());
|
||||
if (oldVal != null) {
|
||||
prop.setValue(entityBean, oldVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onInsertGeneratedProperties() {
|
||||
for (BeanProperty prop : beanDescriptor.propertiesGenInsert()) {
|
||||
Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now());
|
||||
prop.setValueChanged(entityBean, value);
|
||||
if (prop.isVersion() || transaction == null || transaction.isGeneratedPropertiesEnabled() || prop.getValue(entityBean) == null) {
|
||||
Object value = prop.generatedProperty().getInsertValue(prop, entityBean, now());
|
||||
prop.setValueChanged(entityBean, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
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;
|
||||
|
||||
@@ -34,7 +31,7 @@ public interface BeanCollectionHelp<T> extends CQueryCollectionAdd<T> {
|
||||
* For Map's this needs to take the mapKey.
|
||||
* </p>
|
||||
*/
|
||||
BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey);
|
||||
BeanCollectionAdd collectionAdd(Object bc, String mapKey);
|
||||
|
||||
/**
|
||||
* Create an empty collection of the correct type without a parent bean.
|
||||
|
||||
@@ -556,6 +556,23 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse @Formula2 logical expressions into placeholder-form SQL after all
|
||||
* relationships have been wired up and elPropertyDeploy() is fully functional.
|
||||
* Called from BeanDescriptorManager in a dedicated pass after all descriptors
|
||||
* have been fully initialised, so cross-descriptor paths are safe to navigate.
|
||||
*/
|
||||
void initFormula2Properties() {
|
||||
for (BeanProperty prop : propertiesAll()) {
|
||||
String rawExpr = prop.formula2RawExpression();
|
||||
if (rawExpr != null) {
|
||||
DeployPropertyParser parser = parser().setCatchFirst(true);
|
||||
String parsed = parser.parse(rawExpr);
|
||||
prop.initFormula2(parsed, parser.includes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasCircularImportedId() {
|
||||
for (BeanPropertyAssocOne<?> assocOne : propertiesOneImportedSave) {
|
||||
if (assocOne.hasCircularImportedId(this)) {
|
||||
|
||||
+7
-4
@@ -633,14 +633,17 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
*/
|
||||
EntityBean loadBeanDirect(Object id, boolean unmodifiable, CachedBeanData data, PersistenceContext context) {
|
||||
id = desc.convertId(id);
|
||||
EntityBean bean = context == null ? null : (EntityBean) desc.contextGet(context, id);;
|
||||
EntityBean bean = context == null ? null : (EntityBean) desc.contextGet(context, id);
|
||||
if (bean == null) {
|
||||
bean = desc.createEntityBean2(unmodifiable);
|
||||
desc.setId(id, bean);
|
||||
if (context == null) {
|
||||
// a context is required to resolve @ManyToOne references when converting
|
||||
// the cached data to the bean - even for unmodifiable beans (which are
|
||||
// not themselves registered in the persistence context)
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
if (!unmodifiable) {
|
||||
if (context == null) {
|
||||
context = new DefaultPersistenceContext();
|
||||
}
|
||||
desc.contextPut(context, id, bean);
|
||||
EntityBeanIntercept ebi = bean._ebean_getIntercept();
|
||||
ebi.setPersistenceContext(context);
|
||||
|
||||
@@ -46,6 +46,8 @@ import io.ebeanservice.docstore.api.DocStoreFactory;
|
||||
import jakarta.persistence.MappedSuperclass;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import jakarta.persistence.Transient;
|
||||
import io.ebeaninternal.server.transaction.DataSourceSupplier;
|
||||
import io.ebeaninternal.server.transaction.SequenceDataSource;
|
||||
import javax.sql.DataSource;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
@@ -96,7 +98,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
private final Map<String, List<BeanDescriptor<?>>> tableToDescMap = new HashMap<>();
|
||||
private final Map<String, List<BeanDescriptor<?>>> tableToViewDescMap = new HashMap<>();
|
||||
private final DbIdentity dbIdentity;
|
||||
private final DataSource dataSource;
|
||||
private final DataSourceSupplier dataSourceSupplier;
|
||||
private final DatabasePlatform databasePlatform;
|
||||
private final SpiCacheManager cacheManager;
|
||||
private final BackgroundExecutor backgroundExecutor;
|
||||
@@ -132,7 +134,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
this.cacheManager = config.getCacheManager();
|
||||
this.docStoreFactory = config.getDocStoreFactory();
|
||||
this.backgroundExecutor = config.getBackgroundExecutor();
|
||||
this.dataSource = this.config.getDataSource();
|
||||
this.dataSourceSupplier = config.getDataSourceSupplier();
|
||||
this.encryptKeyManager = this.config.getEncryptKeyManager();
|
||||
this.databasePlatform = this.config.getDatabasePlatform();
|
||||
this.multiValueBind = config.getMultiValueBind();
|
||||
@@ -519,6 +521,14 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
d.initialiseDocMapping();
|
||||
}
|
||||
|
||||
// PASS 5:
|
||||
// parse @Formula2 expressions — runs after all descriptors are fully
|
||||
// initialised so cross-descriptor property paths (e.g. parent.parent.someBean.id)
|
||||
// can be resolved safely without hitting null targetDescriptors
|
||||
for (BeanDescriptor<?> d : descMap.values()) {
|
||||
d.initFormula2Properties();
|
||||
}
|
||||
|
||||
// create BeanManager for each non-embedded entity bean
|
||||
for (BeanDescriptor<?> d : descMap.values()) {
|
||||
d.initLast();
|
||||
@@ -788,7 +798,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
throw new RuntimeException(msg);
|
||||
}
|
||||
DeployTableJoin tableJoin = assocOne.getTableJoin();
|
||||
prop.setSecondaryTableJoin(tableJoin, assocOne.getName());
|
||||
prop.setSecondaryTableJoin(tableJoin, assocOne.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -846,8 +856,8 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
for (DeployBeanPropertyAssocOne<?> possibleMappedBy : ones) {
|
||||
Class<?> possibleMappedByType = possibleMappedBy.getTargetType();
|
||||
if (possibleMappedByType.equals(owningType)) {
|
||||
prop.setMappedBy(possibleMappedBy.getName());
|
||||
matchSet.add(possibleMappedBy.getName());
|
||||
prop.setMappedBy(possibleMappedBy.name());
|
||||
matchSet.add(possibleMappedBy.name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,7 +874,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
if (matchSet.size() == 2) {
|
||||
// try to find a match implicitly using a common naming convention
|
||||
// e.g. List<Bug> loggedBugs; ... search for "logged" in matchSet
|
||||
String name = prop.getName();
|
||||
String name = prop.name();
|
||||
|
||||
// get the target type short name
|
||||
String targetType = prop.getTargetType().getName();
|
||||
@@ -1264,7 +1274,10 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
}
|
||||
|
||||
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
|
||||
return databasePlatform.createSequenceIdGenerator(backgroundExecutor, dataSource, stepSize, seqName);
|
||||
DataSource ds = config.getTenantMode().isDynamicDataSource()
|
||||
? new SequenceDataSource(dataSourceSupplier)
|
||||
: dataSourceSupplier.dataSource();
|
||||
return databasePlatform.createSequenceIdGenerator(backgroundExecutor, ds, stepSize, seqName);
|
||||
}
|
||||
|
||||
private void setAccessors(DeployBeanDescriptor<?> deploy) {
|
||||
@@ -1306,7 +1319,7 @@ public final class BeanDescriptorManager implements BeanDescriptorMap, SpiBeanTy
|
||||
// abstract classes as well.
|
||||
BeanPropertiesReader reflectProps = new BeanPropertiesReader(desc.propertyNames());
|
||||
for (DeployBeanProperty prop : desc.propertiesAll()) {
|
||||
String propName = prop.getName();
|
||||
String propName = prop.name();
|
||||
Integer pos = reflectProps.propertyIndex(propName);
|
||||
if (pos == null) {
|
||||
if (isPersistentField(prop)) {
|
||||
|
||||
@@ -1,16 +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.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;
|
||||
|
||||
@@ -23,19 +19,11 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
|
||||
super(many);
|
||||
}
|
||||
|
||||
BeanListHelp() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
|
||||
if (bc instanceof BeanList<?>) {
|
||||
BeanList<?> bl = (BeanList<?>) bc;
|
||||
if (bl.actualList() == null) {
|
||||
bl.setActualList(new ArrayList<>());
|
||||
}
|
||||
return bl;
|
||||
|
||||
return bl.collectionAdd();
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
@@ -67,20 +55,20 @@ public class BeanListHelp<T> extends BaseCollectionHelp<T> {
|
||||
return beanList;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
|
||||
BeanList<?> newBeanList = (BeanList<?>) bc;
|
||||
BeanList<T> newBeanList = (BeanList<T>) 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<?> currentBeanList = (BeanList<?>) currentList;
|
||||
currentBeanList.setActualList(newBeanList.actualList());
|
||||
currentBeanList.setModifyListening(many.modifyListenMode());
|
||||
BeanList<T> currentBeanList = (BeanList<T>) currentList;
|
||||
currentBeanList.refresh(many.modifyListenMode(), newBeanList);
|
||||
|
||||
} else {
|
||||
// replace the entire list with the BeanList
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
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;
|
||||
|
||||
@@ -37,20 +33,14 @@ public class BeanMapHelp<T> extends BaseCollectionHelp<T> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
public final BeanCollectionAdd collectionAdd(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;
|
||||
Map<Object, Object> actualMap = bm.actualMap();
|
||||
if (actualMap == null) {
|
||||
actualMap = new LinkedHashMap<>();
|
||||
bm.setActualMap(actualMap);
|
||||
}
|
||||
return new Adder(beanProp, actualMap);
|
||||
|
||||
return new Adder(beanProp, bm.collectionAdd());
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
@@ -126,8 +116,7 @@ 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.setActualMap(newBeanMap.actualMap());
|
||||
currentBeanMap.setModifyListening(many.modifyListenMode());
|
||||
currentBeanMap.refresh(many.modifyListenMode(), newBeanMap);
|
||||
|
||||
} else {
|
||||
// replace the entire set
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.DataIntegrityException;
|
||||
import io.ebean.ModifyAwareType;
|
||||
import io.ebean.ValuePair;
|
||||
@@ -125,6 +124,9 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
final String elPlaceHolderEncrypted;
|
||||
private final String sqlFormulaSelect;
|
||||
final String sqlFormulaJoin;
|
||||
protected String formula2Select;
|
||||
private Set<String> formula2Includes;
|
||||
private final String formula2RawExpression;
|
||||
private final String aggregation;
|
||||
private final boolean formula;
|
||||
private final boolean dbEncrypted;
|
||||
@@ -189,7 +191,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
|
||||
public BeanProperty(BeanDescriptor<?> descriptor, DeployBeanProperty deploy) {
|
||||
this.descriptor = descriptor;
|
||||
this.name = InternString.intern(deploy.getName());
|
||||
this.name = InternString.intern(deploy.name());
|
||||
this.propertyIndex = deploy.getPropertyIndex();
|
||||
this.unidirectionalShadow = deploy.isUndirectionalShadow();
|
||||
this.importedPrimaryKey = deploy.isImportedPrimaryKey();
|
||||
@@ -242,10 +244,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
this.sqlFormulaJoin = InternString.intern(deploy.getSqlFormulaJoin());
|
||||
this.sqlFormulaSelect = InternString.intern(deploy.getSqlFormulaSelect());
|
||||
this.formula = sqlFormulaSelect != null;
|
||||
this.formula2RawExpression = deploy.getFormula2Expression();
|
||||
this.dbType = deploy.getDbType();
|
||||
this.scalarType = deploy.getScalarType();
|
||||
this.lob = isLobType(dbType);
|
||||
this.propertyType = deploy.getPropertyType();
|
||||
this.propertyType = deploy.propertyType();
|
||||
this.field = deploy.getField();
|
||||
this.docOptions = deploy.getDocPropertyOptions();
|
||||
this.elPlaceHolder = tableAliasIntern(descriptor, deploy.getElPlaceHolder(), false, null);
|
||||
@@ -298,6 +301,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
this.sqlFormulaJoin = null;
|
||||
this.sqlFormulaSelect = null;
|
||||
this.formula = false;
|
||||
this.formula2RawExpression = null;
|
||||
this.aggregation = null;
|
||||
this.excludedFromHistory = source.excludedFromHistory;
|
||||
this.tenantId = source.tenantId;
|
||||
@@ -401,7 +405,7 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
* Return true if this property should have a DB Column created in DDL.
|
||||
*/
|
||||
public boolean isDDLColumn() {
|
||||
return !formula && !secondaryTable && (aggregation == null);
|
||||
return !formula && formula2RawExpression == null && !secondaryTable && (aggregation == null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -487,6 +491,8 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
if (aggregation != null) {
|
||||
ctx.appendFormulaSelect(aggregation);
|
||||
} else if (formula2Select != null) {
|
||||
ctx.appendFormula2Select(formula2Select);
|
||||
} else if (formula) {
|
||||
ctx.appendFormulaSelect(sqlFormulaSelect);
|
||||
} else if (!isTransient && !ignoreDraftOnlyProperty(ctx.isDraftQuery())) {
|
||||
@@ -855,6 +861,30 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
return formula && sqlFormulaJoin != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise this property's formula2 parsed expression and required join paths.
|
||||
* Called by BeanDescriptor.initialise() after all relationships are wired.
|
||||
*/
|
||||
public void initFormula2(String parsedSelect, Set<String> includes) {
|
||||
this.formula2Select = parsedSelect;
|
||||
this.formula2Includes = includes.isEmpty() ? null : java.util.Collections.unmodifiableSet(includes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw @Formula2 expression (before parsing), or null.
|
||||
*/
|
||||
public String formula2RawExpression() {
|
||||
return formula2RawExpression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the join paths required by this @Formula2 property, or null if not a formula2.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> formula2Joins() {
|
||||
return formula2Includes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsManySince(String sinceProperty) {
|
||||
return containsMany();
|
||||
@@ -922,6 +952,10 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
|
||||
@Override
|
||||
public String elPlaceholder(boolean encrypted) {
|
||||
if (formula2Select != null) {
|
||||
// resolve to the parsed @Formula2 expression (with ${} / ${path} placeholders)
|
||||
return formula2Select;
|
||||
}
|
||||
return encrypted ? elPlaceHolderEncrypted : elPlaceHolder;
|
||||
}
|
||||
|
||||
|
||||
@@ -681,7 +681,7 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
}
|
||||
|
||||
private BeanCollectionAdd beanCollectionAdd(Object bc) {
|
||||
return help.getBeanCollectionAdd(bc, null);
|
||||
return help.collectionAdd(bc, null);
|
||||
}
|
||||
|
||||
public Object parentId(EntityBean parentBean) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.InterceptReadWrite;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.ScalarDataReader;
|
||||
@@ -439,27 +440,29 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
if (embedded) {
|
||||
setValue(bean, targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData, context));
|
||||
} else {
|
||||
// when the owning bean is unmodifiable the reference must also be unmodifiable
|
||||
final boolean unmodifiable = !(bean._ebean_getIntercept() instanceof InterceptReadWrite);
|
||||
if (cacheData instanceof CachedBeanId) {
|
||||
setValue(bean, refInheritBean((CachedBeanId) cacheData, context));
|
||||
setValue(bean, refInheritBean((CachedBeanId) cacheData, context, unmodifiable));
|
||||
} else {
|
||||
setValue(bean, refBean(targetDescriptor, cacheData, context));
|
||||
setValue(bean, refBean(targetDescriptor, cacheData, context, unmodifiable));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object refInheritBean(CachedBeanId cacheId, PersistenceContext context) {
|
||||
private Object refInheritBean(CachedBeanId cacheId, PersistenceContext context, boolean unmodifiable) {
|
||||
final InheritInfo rowInheritInfo = targetInheritInfo.readType(cacheId.getDiscValue());
|
||||
return refBean(rowInheritInfo.desc(), cacheId.getId(), context);
|
||||
return refBean(rowInheritInfo.desc(), cacheId.getId(), context, unmodifiable);
|
||||
}
|
||||
|
||||
private Object refBean(BeanDescriptor<?> desc, Object id, PersistenceContext context) {
|
||||
private Object refBean(BeanDescriptor<?> desc, Object id, PersistenceContext context, boolean unmodifiable) {
|
||||
if (id instanceof String) {
|
||||
id = desc.idProperty().scalarType.parse((String) id);
|
||||
}
|
||||
Object bean = desc.contextGet(context, id);
|
||||
Object bean = context == null ? null : desc.contextGet(context, id);
|
||||
if (bean == null) {
|
||||
bean = desc.createRef(id, context);
|
||||
bean = desc.createReference(unmodifiable, false, id, context);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
@@ -593,10 +596,18 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
return findMatch(embeddedProp, prop, prop.dbColumn(), tableJoin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFormula() {
|
||||
return super.isFormula() || formula2Select != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendSelect(DbSqlContext ctx, boolean subQuery) {
|
||||
if (!isTransient) {
|
||||
if (primaryKeyExport) {
|
||||
if (formula2Select != null) {
|
||||
// @Formula2 on @ManyToOne: use formula2 expression as FK selector
|
||||
ctx.appendFormula2Select(formula2Select);
|
||||
} else if (primaryKeyExport) {
|
||||
descriptor.idProperty().appendSelect(ctx, subQuery);
|
||||
} else {
|
||||
localHelp.appendSelect(ctx, subQuery);
|
||||
@@ -615,9 +626,32 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
return super.addJoin(joinType, a1, a2, ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add table join using a prefix to resolve table aliases.
|
||||
*/
|
||||
@Override
|
||||
public SqlJoinType addJoin(SqlJoinType joinType, String prefix, DbSqlContext ctx) {
|
||||
if (formula2Select != null) {
|
||||
// @Formula2 on @ManyToOne: join target table with formula2 FK expression as ON clause
|
||||
String parentPrefix = SplitName.split(prefix)[0]; // null for root-level
|
||||
String a2 = ctx.tableAlias(prefix);
|
||||
String resolvedFk = ctx.parseFormula2(formula2Select, parentPrefix);
|
||||
String joinLiteral = joinType.literal(SqlJoinType.OUTER);
|
||||
String foreignIdCol = tableJoin.columns()[0].getForeignDbColumn();
|
||||
ctx.addFormula2Join(joinLiteral, tableJoin.getTable(), a2, foreignIdCol, resolvedFk);
|
||||
return SqlJoinType.OUTER;
|
||||
}
|
||||
return super.addJoin(joinType, prefix, ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendFrom(DbSqlContext ctx, SqlJoinType joinType, String manyWhere) {
|
||||
if (!isTransient && !primaryKeyExport) {
|
||||
if (formula2Select != null) {
|
||||
// @Formula2 on @ManyToOne: auto-joins for the formula are handled via formula2JoinIncludes;
|
||||
// no additional join SQL is emitted here (the FK value comes from the formula2 SELECT expression)
|
||||
return;
|
||||
}
|
||||
localHelp.appendFrom(ctx, joinType);
|
||||
if (sqlFormulaJoin != null) {
|
||||
String alias = ctx.tableAliasManyWhere(manyWhere);
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
|
||||
public BeanPropertyJsonMapper(BeanDescriptor<?> desc, DeployBeanProperty deployProp) {
|
||||
super(desc, deployProp);
|
||||
this.sourceDetection = deployProp.getMutationDetection() == MutationDetection.SOURCE;
|
||||
this.sourceDetection = deployProp.mutationDetection() == MutationDetection.SOURCE;
|
||||
}
|
||||
|
||||
private BeanPropertyJsonMapper(BeanPropertyJsonMapper source, BeanPropertyOverride override) {
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -26,21 +22,11 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
|
||||
super(many);
|
||||
}
|
||||
|
||||
/**
|
||||
* For a query that returns a set.
|
||||
*/
|
||||
BeanSetHelp() {
|
||||
super();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final BeanCollectionAdd getBeanCollectionAdd(Object bc, String mapKey) {
|
||||
public final BeanCollectionAdd collectionAdd(Object bc, String mapKey) {
|
||||
if (bc instanceof BeanSet<?>) {
|
||||
BeanSet<?> beanSet = (BeanSet<?>) bc;
|
||||
if (beanSet.actualSet() == null) {
|
||||
beanSet.setActualSet(new LinkedHashSet<>());
|
||||
}
|
||||
return beanSet;
|
||||
return beanSet.collectionAdd();
|
||||
} else {
|
||||
throw new RuntimeException("Unhandled type " + bc);
|
||||
}
|
||||
@@ -72,9 +58,10 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
|
||||
return beanSet;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public final void refresh(BeanCollection<?> bc, EntityBean parentBean) {
|
||||
BeanSet<?> newBeanSet = (BeanSet<?>) bc;
|
||||
BeanSet<T> newBeanSet = (BeanSet<T>) bc;
|
||||
Set<?> current = (Set<?>) many.getValue(parentBean);
|
||||
newBeanSet.setModifyListening(many.modifyListenMode());
|
||||
if (current == null) {
|
||||
@@ -83,9 +70,8 @@ public class BeanSetHelp<T> extends BaseCollectionHelp<T> {
|
||||
|
||||
} else if (current instanceof BeanSet<?>) {
|
||||
// normally this case, replace just the underlying list
|
||||
BeanSet<?> currentBeanSet = (BeanSet<?>) current;
|
||||
currentBeanSet.setActualSet(newBeanSet.actualSet());
|
||||
currentBeanSet.setModifyListening(many.modifyListenMode());
|
||||
BeanSet<T> currentBeanSet = (BeanSet<T>) current;
|
||||
currentBeanSet.refresh(many.modifyListenMode(), newBeanSet);
|
||||
|
||||
} else {
|
||||
// replace the entire set
|
||||
|
||||
@@ -63,6 +63,12 @@ public interface DbSqlContext {
|
||||
*/
|
||||
void appendParseSelect(String parseSelect, String alias);
|
||||
|
||||
/**
|
||||
* Parse and add a @Formula2 path based formula resolving the path placeholders
|
||||
* (e.g. ${} or ${parent}) relative to the current node prefix.
|
||||
*/
|
||||
void appendFormula2Select(String parseSelect);
|
||||
|
||||
/**
|
||||
* Append a Sql Formula select. This converts the "${ta}" keyword to the
|
||||
* current table alias.
|
||||
@@ -75,6 +81,22 @@ public interface DbSqlContext {
|
||||
*/
|
||||
void appendFormulaJoin(String sqlFormulaJoin, SqlJoinType joinType, String tableAlias);
|
||||
|
||||
/**
|
||||
* Parse a @Formula2 expression, resolving path placeholders (e.g. ${} or ${parent})
|
||||
* relative to the given parent prefix.
|
||||
*/
|
||||
default String parseFormula2(String formula, String prefix) {
|
||||
return formula;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append a join where the ON clause FK side is a pre-resolved @Formula2 expression.
|
||||
* Used for @Formula2 on @ManyToOne properties where the FK value is a formula.
|
||||
*/
|
||||
default void addFormula2Join(String joinLiteral, String table, String a2, String foreignIdCol, String resolvedFkExpr) {
|
||||
// default no-op for non-default implementations
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current content length.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.util.SplitName;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -64,6 +65,7 @@ public final class DeployPropertyParser extends DeployParser {
|
||||
firstProp = elProp;
|
||||
}
|
||||
addIncludes(elProp.elPrefix());
|
||||
addFormula2Includes(elProp);
|
||||
return elProp.elPlaceholder(encrypted);
|
||||
}
|
||||
}
|
||||
@@ -79,4 +81,22 @@ public final class DeployPropertyParser extends DeployParser {
|
||||
includes.add(prefix);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the join paths required by a @Formula2 property so the joins it
|
||||
* references (e.g. parent, parent.parent) are included to support the
|
||||
* formula when used in where / order by / select clauses.
|
||||
*/
|
||||
private void addFormula2Includes(ElPropertyDeploy elProp) {
|
||||
BeanProperty beanProperty = elProp.beanProperty();
|
||||
if (beanProperty != null) {
|
||||
Set<String> formula2Joins = beanProperty.formula2Joins();
|
||||
if (formula2Joins != null) {
|
||||
String prefix = elProp.elPrefix();
|
||||
for (String join : formula2Joins) {
|
||||
includes.add(prefix == null ? join : SplitName.add(prefix, join));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.query.SqlBeanLoad;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Dynamic property based on aggregation (max, min, avg, count).
|
||||
*/
|
||||
@@ -15,13 +17,20 @@ class DynamicPropertyAggregationFormula extends DynamicPropertyBase {
|
||||
private final boolean aggregate;
|
||||
final BeanProperty asTarget;
|
||||
private final String alias;
|
||||
private final Set<String> formulaJoins;
|
||||
|
||||
DynamicPropertyAggregationFormula(String name, ScalarType<?> scalarType, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias) {
|
||||
DynamicPropertyAggregationFormula(String name, ScalarType<?> scalarType, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias, Set<String> formulaJoins) {
|
||||
super(name, name, null, scalarType);
|
||||
this.parsedFormula = parsedFormula;
|
||||
this.aggregate = aggregate;
|
||||
this.asTarget = asTarget;
|
||||
this.alias = alias;
|
||||
this.formulaJoins = formulaJoins.isEmpty() ? null : formulaJoins;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> formula2Joins() {
|
||||
return formulaJoins;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ public final class DynamicPropertyAggregationFormulaMTO extends DynamicPropertyA
|
||||
private final Set<String> includes;
|
||||
|
||||
DynamicPropertyAggregationFormulaMTO(BeanPropertyAssocOne prop, String name, String parsedFormula, boolean aggregate, BeanProperty asTarget, String alias, Set<String> includes) {
|
||||
super(name, prop.idScalarType(), parsedFormula, aggregate, asTarget, alias);
|
||||
super(name, prop.idScalarType(), parsedFormula, aggregate, asTarget, alias, Set.of());
|
||||
this.prop = prop;
|
||||
this.includes = includes;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.common.BeanMap;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
final class ElementHelpMap implements ElementHelp {
|
||||
|
||||
@@ -15,7 +14,7 @@ final class ElementHelpMap implements ElementHelp {
|
||||
|
||||
private static class Collector implements ElementCollector {
|
||||
|
||||
private final Map<Object, Object> map = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void addElement(Object element) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.common.BeanSet;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
final class ElementHelpSet implements ElementHelp {
|
||||
|
||||
@@ -15,7 +14,7 @@ final class ElementHelpSet implements ElementHelp {
|
||||
|
||||
private static class Collector implements ElementCollector {
|
||||
|
||||
private final Set<Object> set = new LinkedHashSet<>();
|
||||
private final LinkedHashSet<Object> set = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public void addElement(Object element) {
|
||||
|
||||
@@ -127,7 +127,7 @@ final class FormulaPropertyPath {
|
||||
|
||||
private DynamicPropertyAggregationFormula create(ScalarType<?> scalarType) {
|
||||
String logicalName = logicalName();
|
||||
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), target(logicalName), alias);
|
||||
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), target(logicalName), alias, includes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ final class CounterFactory {
|
||||
* Create the GeneratedProperty based on the property type.
|
||||
*/
|
||||
private GeneratedProperty createCounter(DeployBeanProperty property) {
|
||||
Class<?> propType = property.getPropertyType();
|
||||
Class<?> propType = property.propertyType();
|
||||
if (propType.equals(Integer.class) || propType.equals(int.class)) {
|
||||
return integerCounter;
|
||||
}
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ public final class GeneratedPropertyFactory {
|
||||
}
|
||||
|
||||
public void setVersion(DeployBeanProperty property) {
|
||||
if (isNumberType(property.getPropertyType().getName())) {
|
||||
if (isNumberType(property.propertyType().getName())) {
|
||||
setCounter(property);
|
||||
} else {
|
||||
setUpdateTimestamp(property);
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ final class InsertTimestampFactory {
|
||||
* Create the insert GeneratedProperty depending on the property type.
|
||||
*/
|
||||
GeneratedProperty createInsertTimestamp(DeployBeanProperty property) {
|
||||
Class<?> propType = property.getPropertyType();
|
||||
Class<?> propType = property.propertyType();
|
||||
GeneratedProperty generatedProperty = map.get(propType);
|
||||
if (generatedProperty != null) {
|
||||
return generatedProperty;
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ final class UpdateTimestampFactory {
|
||||
* Create the update GeneratedProperty depending on the property type.
|
||||
*/
|
||||
GeneratedProperty createUpdateTimestamp(DeployBeanProperty property) {
|
||||
Class<?> propType = property.getPropertyType();
|
||||
Class<?> propType = property.propertyType();
|
||||
GeneratedProperty generatedProperty = map.get(propType);
|
||||
if (generatedProperty != null) {
|
||||
return generatedProperty;
|
||||
|
||||
+2
-2
@@ -667,7 +667,7 @@ public class DeployBeanDescriptor<T> {
|
||||
* Add a bean property.
|
||||
*/
|
||||
public DeployBeanProperty addBeanProperty(DeployBeanProperty prop) {
|
||||
return propMap.put(prop.getName(), prop);
|
||||
return propMap.put(prop.name(), prop);
|
||||
}
|
||||
|
||||
public Collection<DeployBeanProperty> properties() {
|
||||
@@ -810,7 +810,7 @@ public class DeployBeanDescriptor<T> {
|
||||
for (DeployBeanProperty prop : propMap.values()) {
|
||||
if (!prop.isTransient() && !(prop instanceof DeployBeanPropertyAssocMany<?>)) {
|
||||
if (prop.isFetchEager()) {
|
||||
sb.append(prop.getName()).append(',');
|
||||
sb.append(prop.name()).append(',');
|
||||
} else {
|
||||
hasLazyFetch = true;
|
||||
}
|
||||
|
||||
+52
-18
@@ -38,7 +38,7 @@ import java.util.Set;
|
||||
* Description of a property of a bean. Includes its deployment information such
|
||||
* as database column mapping information.
|
||||
*/
|
||||
public class DeployBeanProperty {
|
||||
public class DeployBeanProperty implements DeployProperty {
|
||||
|
||||
private static final int ID_ORDER = 1000000;
|
||||
private static final int UNIDIRECTIONAL_ORDER = 100000;
|
||||
@@ -144,6 +144,7 @@ public class DeployBeanProperty {
|
||||
private String aggregationParsed;
|
||||
private String sqlFormulaSelect;
|
||||
private String sqlFormulaJoin;
|
||||
private String formula2Expression;
|
||||
/**
|
||||
* The jdbc data type this maps to.
|
||||
*/
|
||||
@@ -229,6 +230,11 @@ public class DeployBeanProperty {
|
||||
return desc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> ownerType() {
|
||||
return desc.getBeanType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DB column length for character columns.
|
||||
* <p>
|
||||
@@ -261,10 +267,12 @@ public class DeployBeanProperty {
|
||||
this.jsonDeserialize = jsonDeserialize;
|
||||
}
|
||||
|
||||
public MutationDetection getMutationDetection() {
|
||||
@Override
|
||||
public MutationDetection mutationDetection() {
|
||||
return mutationDetection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMutationDetection(MutationDetection dirtyDetection) {
|
||||
this.mutationDetection = dirtyDetection;
|
||||
}
|
||||
@@ -428,10 +436,8 @@ public class DeployBeanProperty {
|
||||
this.setter = setter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the name of the property.
|
||||
*/
|
||||
public String getName() {
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@@ -478,9 +484,7 @@ public class DeployBeanProperty {
|
||||
this.generatedProperty = generatedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this property is mandatory.
|
||||
*/
|
||||
@Override
|
||||
public boolean isNullable() {
|
||||
return nullable;
|
||||
}
|
||||
@@ -556,6 +560,23 @@ public class DeployBeanProperty {
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the raw logical expression set by {@code @Formula2}.
|
||||
*/
|
||||
public String getFormula2Expression() {
|
||||
return formula2Expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the raw logical expression for a {@code @Formula2} property.
|
||||
*/
|
||||
public void setFormula2Expression(String formula2Expression) {
|
||||
this.formula2Expression = formula2Expression;
|
||||
this.dbRead = true;
|
||||
this.dbInsertable = false;
|
||||
this.dbUpdateable = false;
|
||||
}
|
||||
|
||||
public void setImportedPrimaryKey() {
|
||||
this.importedPrimaryKey = true;
|
||||
}
|
||||
@@ -843,17 +864,13 @@ public class DeployBeanProperty {
|
||||
this.isTransient = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the property type.
|
||||
*/
|
||||
public Class<?> getPropertyType() {
|
||||
@Override
|
||||
public Class<?> propertyType() {
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the generic type for this property.
|
||||
*/
|
||||
public Type getGenericType() {
|
||||
@Override
|
||||
public Type genericType() {
|
||||
return genericType;
|
||||
}
|
||||
|
||||
@@ -1057,8 +1074,9 @@ public class DeployBeanProperty {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <A extends Annotation> List<A> getMetaAnnotations(Class<A> annotationType) {
|
||||
public <A extends Annotation> List<A> metaAnnotations(Class<A> annotationType) {
|
||||
List<A> result = new ArrayList<>();
|
||||
for (Annotation ann : metaAnnotations) {
|
||||
if (ann.annotationType() == annotationType) {
|
||||
@@ -1095,6 +1113,22 @@ public class DeployBeanProperty {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public Formula2 getMetaAnnotationFormula2(Platform platform) {
|
||||
Formula2 fallback = null;
|
||||
for (Annotation ann : metaAnnotations) {
|
||||
if (ann.annotationType() == Formula2.class) {
|
||||
Formula2 formula2 = (Formula2) ann;
|
||||
final Platform[] platforms = formula2.platforms();
|
||||
if (platforms.length == 0) {
|
||||
fallback = formula2;
|
||||
} else if (matchPlatform(platforms, platform)) {
|
||||
return formula2;
|
||||
}
|
||||
}
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public Where getMetaAnnotationWhere(Platform platform) {
|
||||
Where fallback = null;
|
||||
for (Annotation ann : metaAnnotations) {
|
||||
|
||||
+2
-2
@@ -136,7 +136,7 @@ public final class DeployBeanPropertyLists {
|
||||
|
||||
private DeployBeanProperty findImported(DeployBeanDescriptor<?> deploy, DeployBeanProperty embeddedScalar) {
|
||||
// the logical name and db column we are looking for a match on
|
||||
String name = embeddedScalar.getName();
|
||||
String name = embeddedScalar.name();
|
||||
String dbColumn = embeddedScalar.getDbColumn();
|
||||
|
||||
DeployBeanProperty match = deploy.getBeanProperty(name);
|
||||
@@ -146,7 +146,7 @@ public final class DeployBeanPropertyLists {
|
||||
// could look to match more by dbColumn
|
||||
|
||||
for (DeployBeanPropertyAssocOne<?> assocOne : deploy.propertiesAssocOne()) {
|
||||
if (name.equals(assocOne.getName()) || (dbColumn != null && dbColumn.equals(assocOne.getDbColumn()))) {
|
||||
if (name.equals(assocOne.name()) || (dbColumn != null && dbColumn.equals(assocOne.getDbColumn()))) {
|
||||
return assocOne;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package io.ebeaninternal.server.deploy.meta;
|
||||
|
||||
import io.ebean.annotation.MutationDetection;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Property, with basic type information (BeanProperty and DtoProperty).
|
||||
*/
|
||||
public interface DeployProperty {
|
||||
|
||||
/**
|
||||
* Return the name of the property.
|
||||
*/
|
||||
String name();
|
||||
|
||||
/**
|
||||
* Return the generic type for this property.
|
||||
*/
|
||||
Type genericType();
|
||||
|
||||
/**
|
||||
* Return the property type.
|
||||
*/
|
||||
Class<?> propertyType();
|
||||
|
||||
/**
|
||||
* Returns the owner class of this property.
|
||||
*/
|
||||
Class<?> ownerType();
|
||||
|
||||
/**
|
||||
* Returns the annotations on this property.
|
||||
*/
|
||||
<A extends Annotation> List<A> metaAnnotations(Class<A> annotationType);
|
||||
|
||||
/**
|
||||
* Returns the mutation detection setting of this property.
|
||||
*/
|
||||
MutationDetection mutationDetection();
|
||||
|
||||
/**
|
||||
* Sets the mutation detection setting of this property.
|
||||
*/
|
||||
void setMutationDetection(MutationDetection mutationDetection);
|
||||
|
||||
/**
|
||||
* Return true if this property is not mandatory.
|
||||
*/
|
||||
boolean isNullable();
|
||||
}
|
||||
+1
-1
@@ -187,7 +187,7 @@ final class AnnotationAssocManys extends AnnotationAssoc {
|
||||
CollectionTable collectionTable = get(prop, CollectionTable.class);
|
||||
String fullTableName = getFullTableName(collectionTable);
|
||||
if (fullTableName == null) {
|
||||
fullTableName = descriptor.getBaseTable()+"_"+ CamelCaseHelper.toUnderscoreFromCamel(prop.getName());
|
||||
fullTableName = descriptor.getBaseTable()+"_"+ CamelCaseHelper.toUnderscoreFromCamel(prop.name());
|
||||
}
|
||||
|
||||
BeanTable localTable = factory.beanTable(descriptor.getBeanType());
|
||||
|
||||
+9
-2
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import io.ebean.annotation.DbForeignKey;
|
||||
import io.ebean.annotation.FetchPreference;
|
||||
import io.ebean.annotation.Formula2;
|
||||
import io.ebean.annotation.TenantId;
|
||||
import io.ebean.annotation.Where;
|
||||
import io.ebean.config.NamingConvention;
|
||||
@@ -88,6 +89,11 @@ final class AnnotationAssocOnes extends AnnotationAssoc {
|
||||
prop.setExtraWhere(processFormula(where.clause()));
|
||||
}
|
||||
|
||||
Formula2 formula2 = prop.getMetaAnnotationFormula2(platform);
|
||||
if (formula2 != null) {
|
||||
prop.setFormula2Expression(formula2.value());
|
||||
}
|
||||
|
||||
PrimaryKeyJoinColumn primaryKeyJoin = get(prop, PrimaryKeyJoinColumn.class);
|
||||
if (primaryKeyJoin != null) {
|
||||
readPrimaryKeyJoin(primaryKeyJoin, prop);
|
||||
@@ -138,10 +144,11 @@ final class AnnotationAssocOnes extends AnnotationAssoc {
|
||||
|
||||
String fkeyPrefix = null;
|
||||
if (nc.isUseForeignKeyPrefix()) {
|
||||
fkeyPrefix = nc.getColumnFromProperty(beanType, prop.getName());
|
||||
fkeyPrefix = nc.getColumnFromProperty(beanType, prop.name());
|
||||
}
|
||||
|
||||
beanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), true, prop.getSqlFormulaSelect());
|
||||
String formulaSelect = prop.getSqlFormulaSelect() != null ? prop.getSqlFormulaSelect() : prop.getFormula2Expression();
|
||||
beanTable.createJoinColumn(fkeyPrefix, prop.getTableJoin(), true, formulaSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ final class AnnotationFields extends AnnotationParser {
|
||||
|
||||
private void readField(DeployBeanProperty prop) {
|
||||
// all Enums will have a ScalarType assigned...
|
||||
boolean isEnum = prop.getPropertyType().isEnum();
|
||||
boolean isEnum = prop.propertyType().isEnum();
|
||||
Enumerated enumerated = get(prop, Enumerated.class);
|
||||
if (isEnum || enumerated != null) {
|
||||
util.setEnumScalarType(enumerated, prop);
|
||||
@@ -135,7 +135,7 @@ final class AnnotationFields extends AnnotationParser {
|
||||
readJsonAnnotations(prop);
|
||||
if (prop.getDbColumn() == null) {
|
||||
// No @Column or @Column.name() so use NamingConvention
|
||||
prop.setDbColumn(namingConvention.getColumnFromProperty(beanType, prop.getName()));
|
||||
prop.setDbColumn(namingConvention.getColumnFromProperty(beanType, prop.name()));
|
||||
}
|
||||
initIdentity(prop);
|
||||
initTenantId(prop);
|
||||
@@ -270,9 +270,13 @@ final class AnnotationFields extends AnnotationParser {
|
||||
if (formula != null) {
|
||||
prop.setSqlFormula(processFormula(formula.select()), processFormula(formula.join()));
|
||||
}
|
||||
Formula2 formula2 = prop.getMetaAnnotationFormula2(platform);
|
||||
if (formula2 != null) {
|
||||
prop.setFormula2Expression(formula2.value());
|
||||
}
|
||||
final Aggregation aggregation = prop.getMetaAnnotation(Aggregation.class);
|
||||
if (aggregation != null) {
|
||||
prop.setAggregation(aggregation.value().replace("$1", prop.getName()));
|
||||
prop.setAggregation(aggregation.value().replace("$1", prop.name()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,7 +446,7 @@ final class AnnotationFields extends AnnotationParser {
|
||||
|
||||
private void readGenValue(GeneratedValue gen, Id id, DeployBeanProperty prop) {
|
||||
if (id == null) {
|
||||
if (UUID.class.equals(prop.getPropertyType())) {
|
||||
if (UUID.class.equals(prop.propertyType())) {
|
||||
generatedPropFactory.setUuid(prop);
|
||||
return;
|
||||
}
|
||||
@@ -472,7 +476,7 @@ final class AnnotationFields extends AnnotationParser {
|
||||
if (idGenerator != null) {
|
||||
descriptor.setCustomIdGenerator(idGenerator);
|
||||
}
|
||||
} else if (prop.getPropertyType().equals(UUID.class)) {
|
||||
} else if (prop.propertyType().equals(UUID.class)) {
|
||||
descriptor.setUuidGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public abstract class AnnotationParser extends AnnotationBase {
|
||||
prop.setImportedPrimaryKey();
|
||||
} else {
|
||||
prop.setId();
|
||||
if (prop.getPropertyType().equals(UUID.class) && readConfig.isIdGeneratorAutomatic()) {
|
||||
if (prop.propertyType().equals(UUID.class) && readConfig.isIdGeneratorAutomatic()) {
|
||||
descriptor.setUuidGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
+39
-76
@@ -8,10 +8,12 @@ import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.server.deploy.ManyType;
|
||||
import io.ebeaninternal.server.deploy.meta.*;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
import io.ebeaninternal.server.type.TypeReflectHelper;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import jakarta.persistence.Transient;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.System.Logger.Level.*;
|
||||
@@ -37,7 +39,11 @@ public final class DeployCreateProperties {
|
||||
* Create the appropriate properties for a bean.
|
||||
*/
|
||||
public void createProperties(DeployBeanDescriptor<?> desc) {
|
||||
createProperties(desc, desc.getBeanType(), 0, new HashMap<>());
|
||||
// Build the full type-variable map once for the entire hierarchy. TypeReflectHelper
|
||||
// (via TypeResolver) walks superclasses and interfaces, composing mappings so that
|
||||
// multi-level generic hierarchies (A extends B<T>, B<T> extends C<T>) resolve correctly.
|
||||
Map<TypeVariable<?>, Type> typeMap = TypeReflectHelper.typeVariableMap(desc.getBeanType());
|
||||
createProperties(desc, desc.getBeanType(), 0, typeMap);
|
||||
desc.sortProperties();
|
||||
}
|
||||
|
||||
@@ -63,10 +69,9 @@ public final class DeployCreateProperties {
|
||||
}
|
||||
|
||||
/**
|
||||
* properties the bean properties from Class. Some of these properties may not map to database
|
||||
* columns.
|
||||
* Create the bean properties from Class. Some of these properties may not map to database columns.
|
||||
*/
|
||||
private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, int level, Map<TypeVariable<?>, Class<?>> genericTypeMap) {
|
||||
private void createProperties(DeployBeanDescriptor<?> desc, Class<?> beanType, int level, Map<TypeVariable<?>, Type> typeMap) {
|
||||
if (beanType.equals(Model.class)) {
|
||||
// ignore all fields on model (_$dbName)
|
||||
return;
|
||||
@@ -76,7 +81,7 @@ public final class DeployCreateProperties {
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
Field field = fields[i];
|
||||
if (!ignoreField(field)) {
|
||||
DeployBeanProperty prop = createProp(desc, field, beanType, genericTypeMap);
|
||||
DeployBeanProperty prop = createProp(desc, field, beanType, typeMap);
|
||||
if (prop != null) {
|
||||
// set a order that gives priority to inherited properties
|
||||
// push Id/EmbeddedId up and CreatedTimestamp/UpdatedTimestamp down
|
||||
@@ -96,9 +101,10 @@ public final class DeployCreateProperties {
|
||||
Class<?> superClass = beanType.getSuperclass();
|
||||
if (!superClass.equals(Object.class)) {
|
||||
// recursively add any properties in the inheritance hierarchy
|
||||
// up to the Object.class level...
|
||||
createProperties(desc, superClass, level + 1, mapGenerics(beanType));
|
||||
// up to the Object.class level - the same typeMap covers the full hierarchy
|
||||
createProperties(desc, superClass, level + 1, typeMap);
|
||||
}
|
||||
|
||||
} catch (PersistenceException ex) {
|
||||
throw ex;
|
||||
} catch (Exception ex) {
|
||||
@@ -118,18 +124,24 @@ public final class DeployCreateProperties {
|
||||
return new DeployBeanPropertyAssocMany<>(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field, Map<TypeVariable<?>, Class<?>> genericTypeMap) {
|
||||
Class<?> propertyType = field.getGenericType() instanceof TypeVariable<?>
|
||||
? genericTypeMap.get(field.getGenericType())
|
||||
: field.getType();
|
||||
if (isSpecialScalarType(field)) {
|
||||
return new DeployBeanProperty(desc, propertyType, field.getGenericType());
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field, Map<TypeVariable<?>, Type> typeMap) {
|
||||
// Resolve the field's generic type through the accumulated type-variable map.
|
||||
// This handles TypeVariables from generic superclasses at any depth in the hierarchy.
|
||||
Type resolvedGenericType = TypeReflectHelper.resolveType(field.getGenericType(), typeMap);
|
||||
Class<?> propertyType = TypeReflectHelper.resolveToClass(resolvedGenericType);
|
||||
if (propertyType == null) {
|
||||
propertyType = field.getType();
|
||||
}
|
||||
|
||||
if (isSpecialScalarType(field)) {
|
||||
return new DeployBeanProperty(desc, propertyType, resolvedGenericType);
|
||||
}
|
||||
|
||||
// check for Collection type (list, set or map)
|
||||
ManyType manyType = determineManyType.getManyType(propertyType);
|
||||
ManyType manyType = determineManyType.manyType(propertyType);
|
||||
if (manyType != null) {
|
||||
// List, Set or Map based object
|
||||
Class<?> targetType = determineTargetType(field);
|
||||
Class<?> targetType = determineTargetType(resolvedGenericType);
|
||||
if (targetType == null) {
|
||||
if (AnnotationUtil.has(field, Transient.class)) {
|
||||
// not supporting this field (generic type used)
|
||||
@@ -139,20 +151,25 @@ public final class DeployCreateProperties {
|
||||
}
|
||||
return createManyType(desc, targetType, manyType);
|
||||
}
|
||||
|
||||
if (propertyType.isEnum() || propertyType.isPrimitive()) {
|
||||
return new DeployBeanProperty(desc, propertyType, null, null);
|
||||
}
|
||||
|
||||
ScalarType<?> scalarType = typeManager.type(propertyType);
|
||||
if (scalarType != null) {
|
||||
return new DeployBeanProperty(desc, propertyType, scalarType, null);
|
||||
}
|
||||
|
||||
if (isTransientField(field)) {
|
||||
// return with no ScalarType (still support JSON features)
|
||||
return new DeployBeanProperty(desc, propertyType, null, null);
|
||||
}
|
||||
|
||||
if (AnnotationUtil.has(field, Convert.class)) {
|
||||
throw new IllegalStateException("No AttributeConverter registered for type " + propertyType + " at " + desc.getFullName() + "." + field.getName());
|
||||
}
|
||||
|
||||
try {
|
||||
return new DeployBeanPropertyAssocOne<>(desc, propertyType);
|
||||
} catch (Exception e) {
|
||||
@@ -176,8 +193,8 @@ public final class DeployCreateProperties {
|
||||
return AnnotationUtil.has(field, Transient.class);
|
||||
}
|
||||
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field, Class<?> beanType, Map<TypeVariable<?>, Class<?>> genericTypeMap) {
|
||||
DeployBeanProperty prop = createProp(desc, field, genericTypeMap);
|
||||
private DeployBeanProperty createProp(DeployBeanDescriptor<?> desc, Field field, Class<?> beanType, Map<TypeVariable<?>, Type> typeMap) {
|
||||
DeployBeanProperty prop = createProp(desc, field, typeMap);
|
||||
if (prop == null) {
|
||||
// transient annotation on unsupported type
|
||||
return null;
|
||||
@@ -193,75 +210,21 @@ public final class DeployCreateProperties {
|
||||
* Determine the type of the List,Set or Map. Not been set explicitly so determine this from
|
||||
* ParameterizedType.
|
||||
*/
|
||||
private Class<?> determineTargetType(Field field) {
|
||||
Type genType = field.getGenericType();
|
||||
private Class<?> determineTargetType(Type genType) {
|
||||
if (genType instanceof ParameterizedType) {
|
||||
ParameterizedType ptype = (ParameterizedType) genType;
|
||||
Type[] typeArgs = ptype.getActualTypeArguments();
|
||||
if (typeArgs.length == 1) {
|
||||
// expecting set or list
|
||||
if (typeArgs[0] instanceof Class<?>) {
|
||||
return (Class<?>) typeArgs[0];
|
||||
}
|
||||
if (typeArgs[0] instanceof WildcardType) {
|
||||
final Type[] upperBounds = ((WildcardType) typeArgs[0]).getUpperBounds();
|
||||
if (upperBounds.length == 1 && upperBounds[0] instanceof Class<?>) {
|
||||
// kotlin generated wildcard type
|
||||
return (Class<?>) upperBounds[0];
|
||||
}
|
||||
}
|
||||
// throw new RuntimeException("Unexpected Parameterised Type? "+typeArgs[0]);
|
||||
return null;
|
||||
return TypeReflectHelper.resolveCollectionTarget(typeArgs[0]);
|
||||
}
|
||||
if (typeArgs.length == 2) {
|
||||
// this is probably a Map
|
||||
if (typeArgs[1] instanceof ParameterizedType) {
|
||||
// not supporting ParameterizedType on Map.
|
||||
return null;
|
||||
}
|
||||
if (typeArgs[1] instanceof WildcardType) {
|
||||
return Object.class;
|
||||
}
|
||||
return (Class<?>) typeArgs[1];
|
||||
return TypeReflectHelper.resolveCollectionTarget(typeArgs[1]);
|
||||
}
|
||||
}
|
||||
// if targetType is null, then must be set in annotations
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<TypeVariable<?>, Class<?>> mapGenerics(Class<?> clazz) {
|
||||
Type genericSuperclass = clazz.getGenericSuperclass();
|
||||
if (!(genericSuperclass instanceof ParameterizedType)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
ParameterizedType parameterized = (ParameterizedType) genericSuperclass;
|
||||
TypeVariable<?>[] typeVars = ((Class<?>) parameterized.getRawType()).getTypeParameters();
|
||||
Type[] actualTypes = parameterized.getActualTypeArguments();
|
||||
|
||||
Map<TypeVariable<?>, Class<?>> typeMap = new HashMap<>();
|
||||
for (int i = 0; i < typeVars.length; i++) {
|
||||
Type actual = actualTypes[i];
|
||||
Class<?> resolvedClass = resolveToClass(actual);
|
||||
if (resolvedClass != null) {
|
||||
typeMap.put(typeVars[i], resolvedClass);
|
||||
} else {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return typeMap;
|
||||
}
|
||||
|
||||
private static Class<?> resolveToClass(Type type) {
|
||||
if (type instanceof Class<?>) {
|
||||
return (Class<?>) type;
|
||||
} else if (type instanceof ParameterizedType) {
|
||||
ParameterizedType pType = (ParameterizedType) type;
|
||||
Type raw = pType.getRawType();
|
||||
if (raw instanceof Class<?>) {
|
||||
return (Class<?>) raw;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ public final class DeployUtil {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void setEnumScalarType(Enumerated enumerated, DeployBeanProperty prop) {
|
||||
Class<?> enumType = prop.getPropertyType();
|
||||
Class<?> enumType = prop.propertyType();
|
||||
if (!enumType.isEnum()) {
|
||||
throw new IllegalArgumentException("Class [" + enumType + "] is Not a Enum?");
|
||||
}
|
||||
@@ -130,7 +130,7 @@ public final class DeployUtil {
|
||||
private ScalarType<?> scalarType(DeployBeanProperty property) {
|
||||
// Note that Temporal types already have dbType
|
||||
// set via annotations
|
||||
Class<?> propType = property.getPropertyType();
|
||||
Class<?> propType = property.propertyType();
|
||||
try {
|
||||
ScalarType<?> scalarType = typeManager.type(propType, property.getDbType());
|
||||
if (scalarType != null || property.isTransient()) {
|
||||
@@ -171,8 +171,8 @@ public final class DeployUtil {
|
||||
// set nullable(false) before the ScalarTypeArray is determined and assigned
|
||||
prop.setNullable(false);
|
||||
}
|
||||
Class<?> type = prop.getPropertyType();
|
||||
ScalarType<?> scalarType = typeManager.dbArrayType(type, prop.getGenericType(), prop.isNullable());
|
||||
Class<?> type = prop.propertyType();
|
||||
ScalarType<?> scalarType = typeManager.dbArrayType(type, prop.genericType(), prop.isNullable());
|
||||
if (scalarType == null) {
|
||||
throw new RuntimeException("No ScalarType for @DbArray type for " + prop);
|
||||
}
|
||||
@@ -219,7 +219,7 @@ public final class DeployUtil {
|
||||
/**
|
||||
* Return the JDBC type for the JSON storage type.
|
||||
*/
|
||||
private int dbJsonStorage(DbJsonType dbJsonType) {
|
||||
public static int dbJsonStorage(DbJsonType dbJsonType) {
|
||||
switch (dbJsonType) {
|
||||
case JSONB:
|
||||
return DbPlatformType.JSONB;
|
||||
@@ -244,7 +244,7 @@ public final class DeployUtil {
|
||||
prop.setDbType(lobType);
|
||||
} else {
|
||||
// is String or byte[] ? used to determine if its a CLOB or BLOB
|
||||
Class<?> type = prop.getPropertyType();
|
||||
Class<?> type = prop.propertyType();
|
||||
// this also sets the lob flag on DeployBeanProperty
|
||||
int lobType = isClobType(type) ? dbCLOBType : dbBLOBType;
|
||||
|
||||
|
||||
+3
-3
@@ -11,14 +11,14 @@ import java.util.Set;
|
||||
*/
|
||||
final class DetermineManyType {
|
||||
|
||||
ManyType getManyType(Class<?> type) {
|
||||
ManyType manyType(Class<?> type) {
|
||||
if (type.equals(List.class)) {
|
||||
return ManyType.LIST;
|
||||
}
|
||||
if (type.equals(Set.class)) {
|
||||
if (type.equals(Set.class) || type.getCanonicalName().equals("java.util.SequencedSet")) {
|
||||
return ManyType.SET;
|
||||
}
|
||||
if (type.equals(Map.class)) {
|
||||
if (type.equals(Map.class) || type.getCanonicalName().equals("java.util.SequencedMap")) {
|
||||
return ManyType.MAP;
|
||||
}
|
||||
return null;
|
||||
|
||||
+2
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.parse;
|
||||
|
||||
import io.ebean.annotation.Aggregation;
|
||||
import io.ebean.annotation.Formula;
|
||||
import io.ebean.annotation.Formula2;
|
||||
import io.ebean.annotation.Where;
|
||||
import io.ebean.config.ClassLoadConfig;
|
||||
import io.ebean.DatabaseBuilder;
|
||||
@@ -46,6 +47,7 @@ final class ReadAnnotationConfig {
|
||||
this.metaAnnotations.add(Column.class);
|
||||
this.metaAnnotations.add(Formula.class);
|
||||
this.metaAnnotations.add(Formula.List.class);
|
||||
this.metaAnnotations.add(Formula2.class);
|
||||
this.metaAnnotations.add(Where.class);
|
||||
this.metaAnnotations.add(Where.List.class);
|
||||
this.metaAnnotations.add(Aggregation.class);
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ final class ReadValidationAnnotationsJakarta implements ReadValidationAnnotation
|
||||
}
|
||||
|
||||
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
|
||||
final List<Size> size = prop.getMetaAnnotations(Size.class);
|
||||
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
|
||||
final List<Size> size = prop.metaAnnotations(Size.class);
|
||||
final List<Size.List> lists = prop.metaAnnotations(Size.List.class);
|
||||
for (Size.List list : lists) {
|
||||
Collections.addAll(size, list.value());
|
||||
}
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ final class ReadValidationAnnotationsJavax implements ReadValidationAnnotations
|
||||
}
|
||||
|
||||
private List<Size> getMetaAnnotationJavaxSize(DeployBeanProperty prop) {
|
||||
final List<Size> size = prop.getMetaAnnotations(Size.class);
|
||||
final List<Size.List> lists = prop.getMetaAnnotations(Size.List.class);
|
||||
final List<Size> size = prop.metaAnnotations(Size.class);
|
||||
final List<Size.List> lists = prop.metaAnnotations(Size.List.class);
|
||||
for (Size.List list : lists) {
|
||||
Collections.addAll(size, list.value());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.annotation.DbJson;
|
||||
import io.ebean.annotation.DbJsonB;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
|
||||
@@ -21,10 +23,16 @@ final class DtoMetaBuilder {
|
||||
private final Class<?> dtoType;
|
||||
private final List<DtoMetaProperty> properties = new ArrayList<>();
|
||||
private final Map<Integer, DtoMetaConstructor> constructorMap = new HashMap<>();
|
||||
private final Set<Class<?>> annotationFilter = new HashSet<>();
|
||||
|
||||
DtoMetaBuilder(Class<?> dtoType, TypeManager typeManager) {
|
||||
this.dtoType = dtoType;
|
||||
this.typeManager = typeManager;
|
||||
annotationFilter.add(DbJson.class);
|
||||
annotationFilter.add(DbJsonB.class);
|
||||
if (typeManager.jsonMarkerAnnotation() != null) {
|
||||
annotationFilter.add(typeManager.jsonMarkerAnnotation());
|
||||
}
|
||||
}
|
||||
|
||||
DtoMeta build() {
|
||||
@@ -38,7 +46,7 @@ final class DtoMetaBuilder {
|
||||
if (includeMethod(method)) {
|
||||
try {
|
||||
final String name = propertyName(method.getName());
|
||||
properties.add(new DtoMetaProperty(typeManager, dtoType, method, name));
|
||||
properties.add(new DtoMetaProperty(typeManager, dtoType, method, name, annotationFilter));
|
||||
} catch (Exception e) {
|
||||
CoreLog.log.log(DEBUG, "exclude on " + dtoType + " method " + method, e);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.annotation.MutationDetection;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployProperty;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* DeployProperty for Dto-Properties.
|
||||
*
|
||||
* @author Roland Praml, FOCONIS AG
|
||||
*/
|
||||
final class DtoMetaDeployProperty implements DeployProperty {
|
||||
|
||||
private final String name;
|
||||
private final Class<?> ownerType;
|
||||
private final Type genericType;
|
||||
private final Class<?> propertyType;
|
||||
private final Set<Annotation> metaAnnotations;
|
||||
private final boolean nullable;
|
||||
private MutationDetection mutationDetection = MutationDetection.DEFAULT;
|
||||
|
||||
DtoMetaDeployProperty(String name, Class<?> ownerType, Type genericType, Class<?> propertyType, Set<Annotation> metaAnnotations) {
|
||||
this.name = name;
|
||||
this.ownerType = ownerType;
|
||||
this.genericType = genericType;
|
||||
this.nullable = !propertyType.isPrimitive();
|
||||
this.propertyType = propertyType;
|
||||
this.metaAnnotations = metaAnnotations;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type genericType() {
|
||||
return genericType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> propertyType() {
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> ownerType() {
|
||||
return ownerType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> List<A> metaAnnotations(Class<A> annotationType) {
|
||||
List<A> result = new ArrayList<>();
|
||||
for (Annotation ann : metaAnnotations) {
|
||||
if (ann.annotationType() == annotationType) {
|
||||
result.add((A) ann);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutationDetection mutationDetection() {
|
||||
return mutationDetection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMutationDetection(MutationDetection mutationDetection) {
|
||||
this.mutationDetection = mutationDetection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullable() {
|
||||
return nullable;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,26 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import io.ebean.annotation.DbJson;
|
||||
import io.ebean.annotation.DbJsonB;
|
||||
import io.ebean.config.dbplatform.DbPlatformType;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.plugin.Lookups;
|
||||
import io.ebean.util.AnnotationUtil;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployProperty;
|
||||
import io.ebeaninternal.server.deploy.parse.DeployUtil;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
final class DtoMetaProperty implements DtoReadSet {
|
||||
|
||||
private final Class<?> dtoType;
|
||||
@@ -18,18 +28,68 @@ final class DtoMetaProperty implements DtoReadSet {
|
||||
private final MethodHandle setter;
|
||||
private final ScalarType<?> scalarType;
|
||||
|
||||
DtoMetaProperty(TypeManager typeManager, Class<?> dtoType, Method writeMethod, String name) throws IllegalAccessException, NoSuchMethodException {
|
||||
DtoMetaProperty(TypeManager typeManager, Class<?> dtoType, Method writeMethod, String name, Set<Class<?>> annotationFilter)
|
||||
throws IllegalAccessException, NoSuchMethodException {
|
||||
this.dtoType = dtoType;
|
||||
this.name = name;
|
||||
if (writeMethod != null) {
|
||||
this.setter = lookupMethodHandle(dtoType, writeMethod);
|
||||
this.scalarType = typeManager.type(propertyType(writeMethod), propertyClass(writeMethod));
|
||||
var deployProp = new DtoMetaDeployProperty(name,
|
||||
dtoType,
|
||||
propertyType(writeMethod),
|
||||
propertyClass(writeMethod),
|
||||
findMetaAnnotations(dtoType, writeMethod, name, annotationFilter));
|
||||
scalarType = scalarType(typeManager, deployProp);
|
||||
} else {
|
||||
this.scalarType = null;
|
||||
this.setter = null;
|
||||
}
|
||||
}
|
||||
|
||||
private ScalarType<?> scalarType(TypeManager typeManager, DeployProperty deployProp) {
|
||||
List<DbJson> json = deployProp.metaAnnotations(DbJson.class);
|
||||
if (!json.isEmpty()) {
|
||||
return typeManager.dbJsonType(deployProp, DeployUtil.dbJsonStorage(json.get(0).storage()), json.get(0).length());
|
||||
}
|
||||
List<DbJsonB> jsonB = deployProp.metaAnnotations(DbJsonB.class);
|
||||
if (!jsonB.isEmpty()) {
|
||||
return typeManager.dbJsonType(deployProp, DbPlatformType.JSONB, jsonB.get(0).length());
|
||||
}
|
||||
if (typeManager.jsonMarkerAnnotation() != null
|
||||
&& !deployProp.metaAnnotations(typeManager.jsonMarkerAnnotation()).isEmpty()) {
|
||||
return typeManager.dbJsonType(deployProp, DbPlatformType.JSON, 0);
|
||||
}
|
||||
return typeManager.type(deployProp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all annotations on fields and methods.
|
||||
*/
|
||||
private Set<Annotation> findMetaAnnotations(Class<?> dtoType, Method writeMethod, String name, Set<Class<?>> annotationFilter) {
|
||||
Field field = findField(dtoType, name);
|
||||
if (field != null) {
|
||||
Set<Annotation> metaAnnotations = AnnotationUtil.metaFindAllFor(field, annotationFilter);
|
||||
metaAnnotations.addAll(AnnotationUtil.metaFindAllFor(writeMethod, annotationFilter));
|
||||
return metaAnnotations;
|
||||
} else {
|
||||
return AnnotationUtil.metaFindAllFor(writeMethod, annotationFilter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find field in class with same name
|
||||
*/
|
||||
private Field findField(Class<?> type, String name) {
|
||||
while (type != Object.class && type != null) {
|
||||
try {
|
||||
return type.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static MethodHandle lookupMethodHandle(Class<?> dtoType, Method method) throws NoSuchMethodException, IllegalAccessException {
|
||||
return Lookups.getLookup(dtoType).findVirtual(dtoType, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import io.ebeaninternal.api.SpiExpressionRequest;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,9 @@ import java.util.Arrays;
|
||||
*/
|
||||
public final class ElPropertyChain implements ElPropertyValue {
|
||||
|
||||
/** Matches placeholders of the form ${} or ${path} (e.g. ${parent}) used by @Formula2. */
|
||||
private static final Pattern PLACEHOLDER = Pattern.compile("\\$\\{([^}]*)}");
|
||||
|
||||
private final String prefix;
|
||||
private final String placeHolder;
|
||||
private final String placeHolderEncrypted;
|
||||
@@ -94,9 +99,18 @@ public final class ElPropertyChain implements ElPropertyValue {
|
||||
if (!el.contains("${}")) {
|
||||
// typically a secondary table property
|
||||
return el.replace("${", "${" + prefix + ".");
|
||||
} else {
|
||||
return el.replace(ROOT_ELPREFIX, "${" + prefix + "}");
|
||||
}
|
||||
// prefix the root placeholder ${} as well as any path placeholders ${path}
|
||||
// (e.g. ${parent} used by a @Formula2 property referenced via a path)
|
||||
Matcher matcher = PLACEHOLDER.matcher(el);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (matcher.find()) {
|
||||
String path = matcher.group(1);
|
||||
String replacement = path.isEmpty() ? "${" + prefix + "}" : "${" + prefix + "." + path + "}";
|
||||
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -345,6 +345,10 @@ final class SaveManyBeans extends SaveManyBase {
|
||||
if (insertedParent) {
|
||||
// after insert set the modify listening mode for private owned etc
|
||||
c.setModifyListening(many.modifyListenMode());
|
||||
} else {
|
||||
// a lazily initialized collection (e.g. first save had null value) has no
|
||||
// listen mode yet - set it now so that subsequent modifications are tracked
|
||||
setListenMode(c, many);
|
||||
}
|
||||
// We must not reset when we still have to update other entities in the collection and set their new orderColumn value
|
||||
if (!hasOrderColumn) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user