mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-20 11:17:36 +00:00
Compare commits
188
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ebb0305cf3 | ||
|
|
d03d36bba4 | ||
|
|
88408edd24 | ||
|
|
0aad35b840 | ||
|
|
e7979b285d | ||
|
|
91920b00dc | ||
|
|
bdfe016d2d | ||
|
|
b2670f9cc0 | ||
|
|
ebc90e0e82 | ||
|
|
c4230c780f | ||
|
|
794f933310 | ||
|
|
c8a7a263a9 | ||
|
|
aeef6d0ea2 | ||
|
|
a99aef8b00 | ||
|
|
6f17cc6327 | ||
|
|
903947b3cb | ||
|
|
797f75f7b7 | ||
|
|
af443ef2f2 | ||
|
|
ad4f027835 | ||
|
|
367eba8685 | ||
|
|
be542635a5 | ||
|
|
6e9d0a91e0 | ||
|
|
a99ef3ebf2 | ||
|
|
19afa845d1 | ||
|
|
239900cd3b | ||
|
|
73ac39971c | ||
|
|
3a876252ec | ||
|
|
231b52ba88 | ||
|
|
808019cf3d | ||
|
|
364520455f | ||
|
|
e92621a489 | ||
|
|
ccd1b7b8ec | ||
|
|
d144273307 | ||
|
|
41c5ebdcd7 | ||
|
|
9e711efecb | ||
|
|
ef7fd76f14 | ||
|
|
b7e3ddbedd | ||
|
|
69c932816a | ||
|
|
a1facf527b | ||
|
|
ec42ebf66d | ||
|
|
5dede70801 | ||
|
|
b2f02ecc1f | ||
|
|
15fa7cd1c2 | ||
|
|
8dced38cd5 | ||
|
|
7addba7c93 | ||
|
|
00b45c1642 | ||
|
|
a3d9711fe6 | ||
|
|
1239c50723 | ||
|
|
d85d8449e2 | ||
|
|
baf71cbab3 | ||
|
|
ff0ca35e54 | ||
|
|
0fc9a1ac2f | ||
|
|
f7b4edb193 | ||
|
|
284c8d4d21 | ||
|
|
59a5c5f9bd | ||
|
|
5b04d6eca3 | ||
|
|
d5547808a2 | ||
|
|
5606c95de5 | ||
|
|
4c2bd408b0 | ||
|
|
db76d5c27c | ||
|
|
f92c2f8c14 | ||
|
|
51c3065419 | ||
|
|
e46dd66292 | ||
|
|
3e1451bee7 | ||
|
|
4ac3c4ddb1 | ||
|
|
ee8e48902b | ||
|
|
c37e752ace | ||
|
|
c9a6f822ac | ||
|
|
2231ef9fd1 | ||
|
|
3ff1905318 | ||
|
|
9b4fecff96 | ||
|
|
6f356fd55e | ||
|
|
591f260f5b | ||
|
|
b361b10571 | ||
|
|
c0e4e548ee | ||
|
|
92f3245919 | ||
|
|
59c2290916 | ||
|
|
1135eb3f99 | ||
|
|
1dccdff7bf | ||
|
|
5f74a53e3e | ||
|
|
1f9e652ea9 | ||
|
|
4e592c97ff | ||
|
|
760d19a10d | ||
|
|
d2693c3ff2 | ||
|
|
3324b2bda4 | ||
|
|
1b5e066562 | ||
|
|
104e4ec756 | ||
|
|
74a2967a6e | ||
|
|
e693b12a97 | ||
|
|
48fb9b34bb | ||
|
|
f64400400a | ||
|
|
d7cc081942 | ||
|
|
3351548b60 | ||
|
|
8d9858f57a | ||
|
|
9b520bc176 | ||
|
|
44fd8e3a56 | ||
|
|
6910016c4d | ||
|
|
5f8a3c0591 | ||
|
|
0ae6956c1c | ||
|
|
2e9c5d8548 | ||
|
|
93edc840b3 | ||
|
|
34311785fa | ||
|
|
63929f8ff3 | ||
|
|
d918c5117f | ||
|
|
dd845a7b0c | ||
|
|
df0e4d57f0 | ||
|
|
9c7fa25d49 | ||
|
|
e82c1a4140 | ||
|
|
389bf48e3e | ||
|
|
3b3b44543e | ||
|
|
297f334da5 | ||
|
|
78c7439445 | ||
|
|
2043ba6411 | ||
|
|
437c58001b | ||
|
|
0e812efa7e | ||
|
|
80d4d21986 | ||
|
|
fe66b8c242 | ||
|
|
206b2b2d3b | ||
|
|
c0bc3faea0 | ||
|
|
3d07e7d804 | ||
|
|
fc124e8318 | ||
|
|
f4fd4f4b09 | ||
|
|
16f5311885 | ||
|
|
75a0675977 | ||
|
|
80ebf4a1ee | ||
|
|
4185beb5aa | ||
|
|
79908c0164 | ||
|
|
b5a16f42ca | ||
|
|
a320a132b3 | ||
|
|
9e84e93511 | ||
|
|
6fb40c71ef | ||
|
|
202b9ae94a | ||
|
|
d79b452378 | ||
|
|
78a408dd10 | ||
|
|
6af974e432 | ||
|
|
04a8475291 | ||
|
|
c56345f92e | ||
|
|
ed0a46955e | ||
|
|
3c4b04a893 | ||
|
|
ee403bdb83 | ||
|
|
b18abe2221 | ||
|
|
d68f372d99 | ||
|
|
c8f769aa2a | ||
|
|
bde626fd45 | ||
|
|
b828b73b2c | ||
|
|
2795a8578f | ||
|
|
bd0a3c2303 | ||
|
|
21e50e9d00 | ||
|
|
88a47bf0a9 | ||
|
|
d9a45245f4 | ||
|
|
7be72df788 | ||
|
|
42685335e3 | ||
|
|
478a2be405 | ||
|
|
ebcac9a503 | ||
|
|
996239c29f | ||
|
|
caf2772fb3 | ||
|
|
1482c4c84f | ||
|
|
792d52b794 | ||
|
|
aa4ea4904f | ||
|
|
bab839fb38 | ||
|
|
dde2567ccd | ||
|
|
7e4d070b75 | ||
|
|
3151e3f82b | ||
|
|
c630aaff0e | ||
|
|
67ec848f15 | ||
|
|
57f0734381 | ||
|
|
cbd5bf23c9 | ||
|
|
5dc664a63f | ||
|
|
d9b53677f2 | ||
|
|
f55c2cf3b6 | ||
|
|
c98def7ab7 | ||
|
|
705e158a70 | ||
|
|
569e782e99 | ||
|
|
265b92fb12 | ||
|
|
74311fad0a | ||
|
|
1eded5682a | ||
|
|
ff21bfe1de | ||
|
|
86db7fadfd | ||
|
|
e48b327967 | ||
|
|
964b3ccf8e | ||
|
|
eb6906ad2b | ||
|
|
86e5ea3627 | ||
|
|
3a5e8290e0 | ||
|
|
929604e33d | ||
|
|
2eed68ad61 | ||
|
|
f705560b8b | ||
|
|
a35db6bdee | ||
|
|
3ab3f81fa3 |
@@ -40,5 +40,5 @@ 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 8 clean test
|
||||
run: mvn -T 8 clean test -Pdefault
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java_version: [GA,EA,23]
|
||||
java_version: [GA,EA]
|
||||
os: [ubuntu-latest]
|
||||
|
||||
steps:
|
||||
@@ -37,5 +37,5 @@ jobs:
|
||||
- name: Maven version
|
||||
run: mvn --version
|
||||
- name: Build with Maven
|
||||
run: mvn -T 8 test
|
||||
run: mvn test -Pea
|
||||
|
||||
|
||||
@@ -34,5 +34,5 @@ jobs:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: mariadb 10.6
|
||||
- name: mariadb 10.11
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-mariadb.properties
|
||||
|
||||
@@ -34,5 +34,5 @@ jobs:
|
||||
path:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: sqlserver 2017
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-sqlserver17.properties
|
||||
- name: sqlserver 2022
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-sqlserver.properties
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
[](https://maven-badges.herokuapp.com/maven-central/io.ebean/ebean)
|
||||
[](https://github.com/ebean-orm/ebean/blob/master/LICENSE)
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/multi-jdk-build.yml)
|
||||
[](https://www.graalvm.org/)
|
||||
|
||||
##### Build with database platforms
|
||||
[](https://github.com/ebean-orm/ebean/actions/workflows/h2database.yml)
|
||||
@@ -80,6 +81,16 @@ or [github discussions](https://github.com/ebean-orm/ebean/discussions)
|
||||
## Documentation
|
||||
Goto [https://ebean.io/docs/](https://ebean.io/docs/)
|
||||
|
||||
## Guides
|
||||
Step-by-step guides for common tasks: [docs/guides/](docs/guides/README.md)
|
||||
|
||||
Available guides:
|
||||
- [Maven POM setup](docs/guides/add-ebean-postgres-maven-pom.md)
|
||||
- [Database configuration](docs/guides/add-ebean-postgres-database-config.md)
|
||||
- [Test container setup](docs/guides/add-ebean-postgres-test-container.md)
|
||||
- [DB migration generation](docs/guides/add-ebean-db-migration-generation.md)
|
||||
- [Lombok with Ebean entity beans](docs/guides/lombok-with-ebean-entity-beans.md)
|
||||
|
||||
## Maven central
|
||||
[Maven central - g:io.ebean](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.ebean%22%20)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-clickhouse</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-db2</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-hana</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mariadb</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mysql</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-net-postgis-types</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-nuodb</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-oracle</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
<name>ebean-pgvector</name>
|
||||
<description>ebean-pgvector composite</description>
|
||||
<artifactId>ebean-pgvector</artifactId>
|
||||
|
||||
<properties>
|
||||
<pgvector.version>0.1.6</pgvector.version>
|
||||
<postgres.jdbc.version>42.7.2</postgres.jdbc.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-datasource</artifactId>
|
||||
<version>${ebean-datasource.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>${ebean-migration.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Technically optional but most expected to use query beans -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgres.jdbc.version}</version>
|
||||
<exclusions>
|
||||
<!-- exclude unnecessary checker framework -->
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.pgvector</groupId>
|
||||
<artifactId>pgvector</artifactId>
|
||||
<version>${pgvector.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,7 @@
|
||||
package io.ebean.pgvector.assembly;
|
||||
|
||||
/**
|
||||
* Nothing interesting here - required placeholder for javadoc.
|
||||
*/
|
||||
public class Assembly {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module io.ebean.pgvector {
|
||||
|
||||
requires transitive io.ebean.api;
|
||||
requires transitive io.ebean.core;
|
||||
requires transitive io.ebean.datasource;
|
||||
requires transitive io.ebean.querybean;
|
||||
requires transitive io.ebean.platform.postgres;
|
||||
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlite</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -60,13 +60,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-all</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>composites</artifactId>
|
||||
@@ -25,6 +25,7 @@
|
||||
<module>ebean-postgres</module>
|
||||
<module>ebean-postgis</module>
|
||||
<module>ebean-net-postgis</module>
|
||||
<module>ebean-pgvector</module>
|
||||
<!-- <module>sqlanywhere</module>-->
|
||||
<module>ebean-sqlite</module>
|
||||
<module>ebean-sqlserver</module>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Guides
|
||||
|
||||
Step-by-step guides written as instructions for AI agents and developers.
|
||||
|
||||
## Adding Ebean ORM with PostgreSQL to an existing Maven project
|
||||
|
||||
A three-part guide covering everything needed to wire Ebean + PostgreSQL into an
|
||||
existing Maven project. Complete the steps in order.
|
||||
|
||||
| Step | Guide | Description |
|
||||
|------|-------|-------------|
|
||||
| 1 | [Maven POM setup](add-ebean-postgres-maven-pom.md) | Add Ebean dependencies, the enhancement plugin, and the querybean-generator annotation processor to `pom.xml` |
|
||||
| 2 | [Database configuration](add-ebean-postgres-database-config.md) | Configure the Ebean `Database` bean using `DataSourceBuilder` and `DatabaseBuilder` with Avaje Inject |
|
||||
| 3 | [Test container setup](add-ebean-postgres-test-container.md) | Start a PostgreSQL (or PostGIS) Docker container for tests using `@TestScope @Factory` with Avaje Inject; covers image mirror, read-only datasource, and PostGIS variant |
|
||||
|
||||
## Entity beans
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [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)` |
|
||||
|
||||
## Database migrations
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [DB migration generation](add-ebean-db-migration-generation.md) | Add `GenerateDbMigration.java` to generate schema diff migrations offline; configure the migration runner; understand `.sql` and `.model.xml` output files; workflow for pending drops |
|
||||
|
||||
## Connection Pooling & DataSource Configuration
|
||||
|
||||
The [ebean-datasource](https://github.com/ebean-orm/ebean-datasource) project provides
|
||||
comprehensive guides on connection pool configuration and best practices. These are particularly
|
||||
useful for production deployments, especially in Kubernetes or AWS environments:
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Creating DataSource Pools](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/create-datasource-pool.md) | Step-by-step guide for basic, read-only, Kubernetes, and AWS Lambda datasource configurations |
|
||||
| [AWS Aurora Read-Write Split](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/aws-aurora-read-write-split.md) | Setting up dual DataSources with Aurora read and write endpoints for Ebean integration |
|
||||
| [Connection Validation Best Practices](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/connection-validation-best-practices.md) | Understanding connection heartbeat strategies and why `Connection.isValid()` is the recommended approach |
|
||||
|
||||
---
|
||||
|
||||
## Helping AI agents find these guides
|
||||
|
||||
AI coding agents can only follow these guides if they know they exist. Below are
|
||||
copy-paste snippets for the most common AI tooling configurations. Add whichever
|
||||
applies to your project.
|
||||
|
||||
|
||||
### Project `README.md` (universal — works with all AI tools)
|
||||
|
||||
Add a section near the top of your project README:
|
||||
|
||||
```markdown
|
||||
## AI Agent Instructions
|
||||
|
||||
This project uses [Ebean ORM](https://ebean.io). Step-by-step guides for common
|
||||
Ebean tasks (Maven setup, database configuration, test containers, DB migrations,
|
||||
Lombok) are at:
|
||||
|
||||
**https://github.com/ebean-orm/ebean/tree/main/docs/guides/**
|
||||
|
||||
AI agents should fetch and follow the relevant guide before performing Ebean-related
|
||||
tasks.
|
||||
```
|
||||
|
||||
### Why README is important
|
||||
|
||||
Many AI agents (Copilot, Claude, Cursor, Aider) read the project `README.md`
|
||||
automatically as part of understanding the codebase. A visible link in README is the
|
||||
most universally effective way to ensure any agent working on your project will
|
||||
discover the guides without requiring tool-specific configuration files.
|
||||
|
||||
------
|
||||
|
||||
### GitHub Copilot — `.github/copilot-instructions.md`
|
||||
|
||||
```markdown
|
||||
## Ebean ORM
|
||||
|
||||
This project uses [Ebean ORM](https://ebean.io). Step-by-step guides for common
|
||||
tasks are at: https://github.com/ebean-orm/ebean/tree/main/docs/guides/
|
||||
|
||||
Key guides (fetch and follow these when performing the relevant task):
|
||||
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/main/docs/guides/add-ebean-postgres-maven-pom.md
|
||||
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/main/docs/guides/add-ebean-postgres-database-config.md
|
||||
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/main/docs/guides/add-ebean-postgres-test-container.md
|
||||
- DB migration generation: https://raw.githubusercontent.com/ebean-orm/ebean/main/docs/guides/add-ebean-db-migration-generation.md
|
||||
- Lombok with entity beans: https://raw.githubusercontent.com/ebean-orm/ebean/main/docs/guides/lombok-with-ebean-entity-beans.md
|
||||
```
|
||||
|
||||
### Claude Code — `CLAUDE.md`
|
||||
|
||||
Same content as above — Claude Code reads `CLAUDE.md` at the project root.
|
||||
|
||||
### Cursor — `.cursor/rules/ebean.mdc`
|
||||
|
||||
```markdown
|
||||
---
|
||||
description: Ebean ORM task guidance
|
||||
globs: ["**/*.java", "**/pom.xml"]
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
## Ebean ORM
|
||||
|
||||
This project uses Ebean ORM. Before performing any Ebean-related task, fetch and
|
||||
follow the relevant step-by-step guide from:
|
||||
https://github.com/ebean-orm/ebean/tree/main/docs/guides/
|
||||
```
|
||||
@@ -0,0 +1,366 @@
|
||||
# Guide: Add Ebean Database Migration Generation to an Existing Maven Project
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide provides step-by-step instructions for adding Ebean DB migration generation
|
||||
to an existing Maven project that already uses Ebean ORM. Ebean generates migrations by
|
||||
performing a diff of the current entity model against the previously recorded model state,
|
||||
producing platform-specific DDL SQL scripts.
|
||||
|
||||
These instructions are designed for AI agents and developers to follow precisely.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An existing Maven project with Ebean ORM configured (entity beans present)
|
||||
- `ebean-test` is already a test-scoped dependency (from POM setup guide)
|
||||
- The project targets PostgreSQL (adjust `Platform.POSTGRES` for other databases)
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Verify migration dependencies
|
||||
|
||||
### Generation tooling (`ebean-ddl-generator`)
|
||||
|
||||
`ebean-test` (already present as a test dependency) transitively includes
|
||||
`ebean-ddl-generator`, which provides the `DbMigration` class. No additional dependency
|
||||
is required for generation.
|
||||
|
||||
### Runtime migration runner (`ebean-migration`)
|
||||
|
||||
`ebean-migration` is the library that runs migrations on application startup.
|
||||
It is typically included **transitively** via `io.ebean:ebean-postgres` (or the
|
||||
equivalent platform dependency). Verify it is on the classpath by running:
|
||||
|
||||
```bash
|
||||
mvn dependency:tree | grep ebean-migration
|
||||
```
|
||||
|
||||
If it is **not** present transitively, add it explicitly as a compile-scope dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-migration</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create `GenerateDbMigration.java`
|
||||
|
||||
Create the following class in `src/test/java/main/`. This `main` method is run manually
|
||||
by a developer (or AI agent) whenever entity beans change and a new migration is needed.
|
||||
|
||||
```java
|
||||
package main;
|
||||
|
||||
import io.ebean.annotation.Platform;
|
||||
import io.ebean.dbmigration.DbMigration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Generate the next database migration based on a diff of the entity model.
|
||||
* Run this main method after making entity bean changes to produce the migration SQL.
|
||||
*/
|
||||
public class GenerateDbMigration {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
|
||||
DbMigration migration = DbMigration.create();
|
||||
migration.setPlatform(Platform.POSTGRES);
|
||||
|
||||
migration.setVersion("1.1"); // set to the next migration version
|
||||
migration.setName("add-customer"); // short description of the change
|
||||
|
||||
migration.generateMigration();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Version naming convention
|
||||
|
||||
Ebean supports two common version formats — choose one and apply it consistently:
|
||||
|
||||
| Format | Example | Notes |
|
||||
|--------|---------|-------|
|
||||
| **Date-based** | `20240820` | `YYYYMMDD`; used when changes are tied to dates; easily sortable |
|
||||
| **Semantic** | `1.1`, `1.2`, `2.0` | Traditional versioning; useful for release-based workflows |
|
||||
|
||||
The version controls execution order — Ebean runs migrations in ascending version order.
|
||||
|
||||
### Name convention
|
||||
|
||||
The `name` should be a short, lowercase, hyphenated description of the change:
|
||||
- `add-customer-email`
|
||||
- `rename-machine-type`
|
||||
- `drop-unused-columns`
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Configure the output path (if needed)
|
||||
|
||||
By default, migration files are written to `src/main/resources/dbmigration/` relative
|
||||
to the **current working directory** when `generateMigration()` is called. This is
|
||||
usually the module root, which is correct for single-module projects.
|
||||
|
||||
For **multi-module projects** where `GenerateDbMigration` is in a submodule but the
|
||||
resources directory is at a different relative path, specify it explicitly:
|
||||
|
||||
```java
|
||||
// Relative path from the working directory (project root) to the module's resources
|
||||
migration.setPathToResources("my-module/src/main/resources");
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Run `GenerateDbMigration` to produce the first migration
|
||||
|
||||
Run the `main` method via the IDE or Maven:
|
||||
|
||||
```bash
|
||||
# Run via Maven exec plugin (or use IDE run configuration)
|
||||
mvn test-compile exec:java \
|
||||
-Dexec.mainClass="main.GenerateDbMigration" \
|
||||
-Dexec.classpathScope="test" \
|
||||
-pl <your-module>
|
||||
```
|
||||
|
||||
Ebean migration generation runs in **offline mode** — no database connection is required.
|
||||
|
||||
### Expected output files
|
||||
|
||||
After running, two files are created per migration in `src/main/resources/dbmigration/`:
|
||||
|
||||
```
|
||||
src/main/resources/dbmigration/
|
||||
1.1__add-customer.sql ← DDL SQL to apply (commit this)
|
||||
model/
|
||||
1.1__add-customer.model.xml ← logical model diff XML (commit this)
|
||||
```
|
||||
|
||||
Both files must be committed to source control. The `.model.xml` file records the
|
||||
logical state of the diff and is used by subsequent migration generations to determine
|
||||
what has changed.
|
||||
|
||||
If **no entity beans have changed** since the last migration, the command outputs:
|
||||
```
|
||||
DbMigration - no changes detected - no migration written
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Enable the migration runner
|
||||
|
||||
Configure Ebean to run pending migrations automatically on application startup.
|
||||
|
||||
### Preferred approach — programmatic via `DatabaseBuilder`
|
||||
|
||||
Set `runMigration(true)` directly on the `DatabaseBuilder` when constructing
|
||||
the `Database` bean. This is the preferred approach as it is explicit, co-located with
|
||||
the database configuration, and does not rely on external property files.
|
||||
|
||||
In the `@Factory` class that builds the `Database` bean (see the database configuration
|
||||
guide), add `.runMigration(true)` to the builder chain:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
Database database(ConfigWrapper config) {
|
||||
var dataSource = DataSourceBuilder.create()
|
||||
.url(config.getDatabaseUrl())
|
||||
.username(config.getDatabaseUser())
|
||||
.password(config.getDatabasePassword())
|
||||
// ... other datasource settings ...
|
||||
;
|
||||
|
||||
return Database.builder()
|
||||
.name("db")
|
||||
.dataSourceBuilder(dataSource)
|
||||
.runMigration(true) // run pending migrations on startup
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
If migrations should only run in certain environments (e.g., not in production, or
|
||||
only when a config flag is set), make it conditional:
|
||||
|
||||
```java
|
||||
.runMigration(config.isRunMigrations()) // driven by config value
|
||||
```
|
||||
|
||||
### Alternative — via application properties
|
||||
|
||||
If programmatic configuration is not available or not preferred, set the property
|
||||
in `src/main/resources/application.properties`:
|
||||
|
||||
```properties
|
||||
ebean.migration.run=true
|
||||
```
|
||||
|
||||
Or in `src/main/resources/application.yaml`:
|
||||
```yaml
|
||||
ebean:
|
||||
migration:
|
||||
run: true
|
||||
```
|
||||
|
||||
For a **named database** (i.e., `Database.builder().name("mydb")`), use the database
|
||||
name in the property key:
|
||||
|
||||
```properties
|
||||
ebean.mydb.migration.run=true
|
||||
```
|
||||
|
||||
### What the runner does at startup
|
||||
|
||||
When migration running is enabled, Ebean will on each application start:
|
||||
1. Look at the migrations in `src/main/resources/dbmigration/`
|
||||
2. Compare against the `db_migration` table (created automatically on first run)
|
||||
3. Apply any migrations that have not yet been executed, in version order
|
||||
4. Record each successfully applied migration in `db_migration`
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Commit the migration files
|
||||
|
||||
Add both generated files to source control:
|
||||
|
||||
```bash
|
||||
git add src/main/resources/dbmigration/1.1__add-customer.sql
|
||||
git add src/main/resources/dbmigration/model/1.1__add-customer.model.xml
|
||||
git commit -m "Add db migration 1.1: add-customer"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ongoing workflow — generating subsequent migrations
|
||||
|
||||
For each future set of entity bean changes:
|
||||
|
||||
1. Make changes to the entity bean classes
|
||||
2. Update `GenerateDbMigration.java` with the **new version** and **new name**:
|
||||
```java
|
||||
migration.setVersion("1.2");
|
||||
migration.setName("add-address-table");
|
||||
```
|
||||
3. Run the `main` method — a new `.sql` and `.model.xml` pair is written
|
||||
4. Review the generated `.sql` to confirm it reflects the intended changes
|
||||
5. Commit both files
|
||||
|
||||
---
|
||||
|
||||
## Understanding the output files
|
||||
|
||||
### Apply SQL (`.sql`)
|
||||
|
||||
The apply SQL file contains the DDL that will be executed against the database:
|
||||
|
||||
```sql
|
||||
-- apply changes
|
||||
alter table customer add column email varchar(255);
|
||||
```
|
||||
|
||||
### Model XML (`.model.xml`)
|
||||
|
||||
The model XML records the logical diff in a database-agnostic format. Ebean uses
|
||||
this file on the next generation run to determine what has already been captured.
|
||||
It is not executed against the database.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<migration xmlns="http://ebean-orm.github.io/xml/ns/dbmigration">
|
||||
<changeSet type="apply">
|
||||
<addColumn tableName="customer">
|
||||
<column name="email" type="varchar(255)"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</migration>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional configurations
|
||||
|
||||
### Multiple database platforms
|
||||
|
||||
To generate migrations for multiple platforms simultaneously, use `addPlatform()`
|
||||
instead of `setPlatform()`:
|
||||
|
||||
```java
|
||||
migration.addPlatform(Platform.POSTGRES);
|
||||
migration.addPlatform(Platform.SQLSERVER17);
|
||||
migration.addPlatform(Platform.MYSQL);
|
||||
```
|
||||
|
||||
Each platform gets its own subdirectory under `dbmigration/`.
|
||||
|
||||
### Include index
|
||||
|
||||
When enabled the migration generation also generates a file that contains
|
||||
all the migrations and their associated hashes. This is a performance
|
||||
optimisation (that will become the default) and means that the migration
|
||||
runner just needs to read the one resource and has the pre-computed hash
|
||||
values (so does not need to read each migration resource and compute the
|
||||
hash for each of those at runtime).
|
||||
|
||||
```java
|
||||
migration.setIncludeIndex(true);
|
||||
```
|
||||
|
||||
### Strict mode
|
||||
|
||||
Strict mode (on by default) errors if there are any pending drops not yet applied.
|
||||
Set to `false` to allow generation to proceed regardless:
|
||||
|
||||
```java
|
||||
migration.setStrictMode(false);
|
||||
```
|
||||
|
||||
### Applying pending drops
|
||||
|
||||
Destructive changes (drop column, drop table) are **not** included in the apply
|
||||
SQL by default — they are recorded as `pendingDrops` in the model XML. This allows
|
||||
the application to be deployed without immediately dropping columns (important for
|
||||
rolling deployments).
|
||||
|
||||
The migration runner logs a message when pending drops exist:
|
||||
```
|
||||
INFO DbMigration - Pending un-applied drops in versions [1.1]
|
||||
```
|
||||
|
||||
When ready to apply the drops, set `setGeneratePendingDrop` to the version that
|
||||
contains the pending drops:
|
||||
|
||||
```java
|
||||
migration.setVersion("1.3");
|
||||
migration.setName("drop-pending-from-1.1");
|
||||
migration.setGeneratePendingDrop("1.1"); // apply drops recorded in version 1.1
|
||||
migration.generateMigration();
|
||||
```
|
||||
|
||||
### Custom dbSchema
|
||||
|
||||
If the project uses a named Postgres schema (set via `ebean.dbSchema` in
|
||||
`application.properties`), no additional configuration is needed in
|
||||
`GenerateDbMigration` — Ebean picks up the schema from the application config
|
||||
automatically when running in offline mode.
|
||||
|
||||
```properties
|
||||
# application.properties
|
||||
ebean.dbSchema=myschema
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| `no changes detected - no migration written` | Entity beans unchanged since last migration | Make entity bean changes first, then re-run |
|
||||
| `DbMigration - Pending un-applied drops` | A previous migration has drops not yet applied | Either suppress with `setStrictMode(false)` or apply drops with `setGeneratePendingDrop(...)` |
|
||||
| Generated SQL is empty or wrong | Wrong working directory path | Set `setPathToResources(...)` to the correct module-relative path |
|
||||
| `ClassNotFoundException` for entity classes | Test classpath not including main classes | Ensure `exec.classpathScope=test` or run via IDE with test classpath |
|
||||
| Migrations not running on startup | Property key wrong or `ebean-migration` missing | Verify `ebean[.name].migration.run=true` and that `ebean-migration` is on the classpath |
|
||||
@@ -0,0 +1,282 @@
|
||||
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 2: Database Configuration
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide provides step-by-step instructions for configuring an Ebean `Database` bean
|
||||
using **Avaje Inject** (`@Factory` / `@Bean`), backed by a PostgreSQL datasource built
|
||||
with Ebean's `DataSourceBuilder`. Follow every step in order. This is Step 2 of 2.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Step 1 complete**: `pom.xml` already includes `ebean-postgres`, `ebean-maven-plugin`,
|
||||
and `querybean-generator` (see `add-ebean-postgres-maven-pom.md`)
|
||||
- **Avaje Inject** is on the classpath (e.g. `io.avaje:avaje-inject`)
|
||||
- A configuration source is available at runtime (e.g. `avaje-config` reading
|
||||
`application.yml` or environment variables)
|
||||
- The following configuration keys are resolvable at runtime (adapt names to your project):
|
||||
| Key | Description |
|
||||
|-----|-------------|
|
||||
| `db_url` | JDBC URL for the master/write connection |
|
||||
| `db_user` | Database username |
|
||||
| `db_pass` | Database password |
|
||||
| `db_master_min_connections` | Minimum pool size (default: 1) |
|
||||
| `db_master_initial_connections` | Initial pool size at startup — set high to pre-warm on pod start (see K8s note below) |
|
||||
| `db_master_max_connections` | Maximum pool size (default: 200) |
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Locate or create the `@Factory` class
|
||||
|
||||
Look for an existing Avaje Inject `@Factory`-annotated class in the project
|
||||
(often named `AppConfig`, `DatabaseConfig`, or similar). If one exists, add the new
|
||||
`@Bean` method to it. If none exists, create one:
|
||||
|
||||
```java
|
||||
package com.example.configuration;
|
||||
|
||||
import io.avaje.inject.Bean;
|
||||
import io.avaje.inject.Factory;
|
||||
|
||||
@Factory
|
||||
class DatabaseConfig {
|
||||
// beans will be added in the steps below
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Add the `Database` bean method (minimal — master datasource only)
|
||||
|
||||
Add the following `@Bean` method to the `@Factory` class. This creates an Ebean
|
||||
`Database` backed by a single master (read-write) PostgreSQL datasource.
|
||||
|
||||
```java
|
||||
import io.ebean.Database;
|
||||
import io.ebean.datasource.DataSourceBuilder;
|
||||
|
||||
@Bean
|
||||
Database database() {
|
||||
var dataSource = DataSourceBuilder.create()
|
||||
.url(/* resolve from config, e.g.: */ Config.get("db_url"))
|
||||
.username(Config.get("db_user"))
|
||||
.password(Config.get("db_pass"))
|
||||
.driver("org.postgresql.Driver")
|
||||
.schema("myschema") // set to your target schema
|
||||
.applicationName("my-app") // visible in pg_stat_activity
|
||||
.minConnections(Config.getInt("db_master_min_connections", 1))
|
||||
.initialConnections(Config.getInt("db_master_initial_connections", 10))
|
||||
.maxConnections(Config.getInt("db_master_max_connections", 200));
|
||||
|
||||
return Database.builder()
|
||||
.name("db") // logical name for this Database instance
|
||||
.dataSourceBuilder(dataSource)
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Field guidance
|
||||
|
||||
| Field | Notes |
|
||||
|-------|-------|
|
||||
| `url` | Full JDBC URL, e.g. `jdbc:postgresql://host:5432/dbname` |
|
||||
| `schema` | The Postgres schema Ebean should use (omit if using `public`) |
|
||||
| `applicationName` | Shown in `pg_stat_activity.application_name`; helps with DB-side diagnostics |
|
||||
| `name("db")` | Logical Ebean database name; relevant if multiple Database instances exist |
|
||||
| `minConnections` | Connections kept open at all times; pool will not shrink below this |
|
||||
| `initialConnections` | Connections opened at startup; see K8s warm-up note below |
|
||||
| `maxConnections` | Hard upper limit on concurrent connections |
|
||||
|
||||
### Connection pool sizing for Kubernetes (and similar orchestrated environments)
|
||||
|
||||
When a pod starts in Kubernetes it will receive live traffic as soon as it passes
|
||||
readiness checks — often before the connection pool has had a chance to grow to handle
|
||||
the load. This can cause latency spikes on the first wave of requests while the pool
|
||||
expands one connection at a time.
|
||||
|
||||
Use `initialConnections` to **pre-warm the pool at startup** so it is already sized
|
||||
for peak load when the pod goes live:
|
||||
|
||||
```
|
||||
minConnections: 2 ← floor; pool will shrink back here when idle
|
||||
initialConnections: 20 ← opened at pod start, before first request arrives
|
||||
maxConnections: 50 ← hard ceiling
|
||||
```
|
||||
|
||||
The lifecycle is:
|
||||
1. **Pod starts** — pool opens `initialConnections` connections immediately.
|
||||
2. **Pod receives traffic** — pool is already at capacity; no growth latency.
|
||||
3. **Traffic drops** — idle connections are closed; pool trims back toward `minConnections`.
|
||||
4. **Next traffic spike** — pool grows again up to `maxConnections` on demand.
|
||||
|
||||
Set `initialConnections` to a value high enough that the pool does not need to grow
|
||||
during the first minute of live traffic. A common starting point is 50–75% of
|
||||
`maxConnections`.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Inject configuration via a constructor or config helper (recommended)
|
||||
|
||||
Rather than calling `Config.get(...)` inline, inject a typed config helper or the
|
||||
Avaje `Configuration` bean if one is available. This makes the factory testable and
|
||||
keeps the wiring explicit. For example:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
Database database(Configuration config) {
|
||||
String url = config.get("db_url");
|
||||
String user = config.get("db_user");
|
||||
String pass = config.get("db_pass");
|
||||
int min = config.getInt("db_master_min_connections", 1);
|
||||
int init = config.getInt("db_master_initial_connections", 10);
|
||||
int max = config.getInt("db_master_max_connections", 200);
|
||||
|
||||
var dataSource = DataSourceBuilder.create()
|
||||
.url(url)
|
||||
.username(user)
|
||||
.password(pass)
|
||||
.driver("org.postgresql.Driver")
|
||||
.schema("myschema")
|
||||
.applicationName("my-app")
|
||||
.minConnections(min)
|
||||
.initialConnections(init)
|
||||
.maxConnections(max);
|
||||
|
||||
return Database.builder()
|
||||
.name("db")
|
||||
.dataSourceBuilder(dataSource)
|
||||
.skipDataSourceCheck(true)
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
If the project has a dedicated config-wrapper class (a `@Component` that reads config
|
||||
keys), accept it as a parameter instead of `Configuration`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 (Optional) — Add a read-only datasource
|
||||
|
||||
For production services that have a separate read-replica, add a second
|
||||
`DataSourceBuilder` for read-only queries and wire it via
|
||||
`readOnlyDataSourceBuilder(...)`. The read-only datasource:
|
||||
|
||||
- Uses `readOnly(true)` and `autoCommit(true)` (Ebean routes read queries there automatically)
|
||||
- Typically has a higher max connection count than the master
|
||||
- Benefits from a prepared-statement cache (`pstmtCacheSize`)
|
||||
|
||||
```java
|
||||
@Bean
|
||||
Database database(Configuration config) {
|
||||
String masterUrl = config.get("db_url");
|
||||
String readOnlyUrl = config.get("db_url_readonly");
|
||||
String user = config.get("db_user");
|
||||
String pass = config.get("db_pass");
|
||||
|
||||
var masterDataSource = buildDataSource(user, pass)
|
||||
.url(masterUrl)
|
||||
.minConnections(config.getInt("db_master_min_connections", 1))
|
||||
.initialConnections(config.getInt("db_master_initial_connections", 10))
|
||||
.maxConnections(config.getInt("db_master_max_connections", 50));
|
||||
|
||||
var readOnlyDataSource = buildDataSource(user, pass)
|
||||
.url(readOnlyUrl)
|
||||
.readOnly(true)
|
||||
.autoCommit(true)
|
||||
.pstmtCacheSize(250) // cache up to 250 prepared statements per connection
|
||||
.maxInactiveTimeSecs(600) // close idle connections after 10 minutes
|
||||
.minConnections(config.getInt("db_readonly_min_connections", 2))
|
||||
.initialConnections(config.getInt("db_readonly_initial_connections", 10))
|
||||
.maxConnections(config.getInt("db_readonly_max_connections", 200));
|
||||
|
||||
return Database.builder()
|
||||
.name("db")
|
||||
.dataSourceBuilder(masterDataSource)
|
||||
.readOnlyDataSourceBuilder(readOnlyDataSource)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static DataSourceBuilder buildDataSource(String user, String pass) {
|
||||
return DataSourceBuilder.create()
|
||||
.username(user)
|
||||
.password(pass)
|
||||
.driver("org.postgresql.Driver")
|
||||
.schema("myschema")
|
||||
.applicationName("my-app")
|
||||
.addProperty("prepareThreshold", "2"); // PostgreSQL: server-side prepared statements
|
||||
}
|
||||
```
|
||||
|
||||
### Additional configuration keys for the read-only datasource
|
||||
|
||||
| Key | Description | Default |
|
||||
|-----|-------------|---------|
|
||||
| `db_url_readonly` | JDBC URL for the read replica | — |
|
||||
| `db_master_initial_connections` | Initial master pool size at startup | 10 |
|
||||
| `db_readonly_min_connections` | Minimum pool size | 2 |
|
||||
| `db_readonly_initial_connections` | Initial pool size at startup | same as min |
|
||||
| `db_readonly_max_connections` | Maximum pool size | 20 |
|
||||
|
||||
---
|
||||
|
||||
## Step 5 (Optional) — Enable the migration runner
|
||||
|
||||
If the project uses Ebean's built-in DB migration runner to apply SQL migrations on
|
||||
startup, enable it on the `DatabaseBuilder`:
|
||||
|
||||
```java
|
||||
return Database.builder()
|
||||
.name("db")
|
||||
.dataSourceBuilder(dataSource)
|
||||
.runMigration(true) // run pending migrations on startup
|
||||
.build();
|
||||
```
|
||||
|
||||
This is equivalent to setting `ebean.migration.run=true` in `application.properties`
|
||||
but is preferred because it keeps all database configuration in one place. To make it
|
||||
conditional (e.g. only in non-production environments):
|
||||
|
||||
```java
|
||||
.runMigration(config.getBoolean("db.runMigrations", false))
|
||||
```
|
||||
|
||||
See the DB migration generation guide (`add-ebean-db-migration-generation.md`) for
|
||||
full details on generating and managing migration files.
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
For advanced connection pool configuration, production deployment patterns, and connection
|
||||
validation best practices, see the [ebean-datasource guides](https://github.com/ebean-orm/ebean-datasource/tree/master/docs/guides/):
|
||||
|
||||
- **[Creating DataSource Pools](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/create-datasource-pool.md)** — Covers read-only pools (`readOnly(true)` + `autoCommit(true)`), Kubernetes deployment strategies using `initialConnections`, and AWS Lambda optimization
|
||||
- **[AWS Aurora Read-Write Split](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/aws-aurora-read-write-split.md)** — Setting up dual DataSources with Aurora reader and writer endpoints, including Ebean secondary datasource routing
|
||||
- **[Connection Validation Best Practices](https://github.com/ebean-orm/ebean-datasource/blob/master/docs/guides/connection-validation-best-practices.md)** — Why `Connection.isValid()` is the recommended default and when (rarely) explicit `heartbeatSql` is needed
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
1. Start the application (or run `mvn test -pl <your-module>`).
|
||||
2. Look for log output similar to:
|
||||
|
||||
```
|
||||
INFO o.a.datasource.pool.ConnectionPool - DataSourcePool [db] autoCommit[false] min[1] max[5]
|
||||
INFO io.ebean.internal.DefaultContainer - DatabasePlatform name:db platform:postgres
|
||||
```
|
||||
|
||||
3. If you see `DataSourcePool` and `DatabasePlatform` log lines, Ebean is connected and
|
||||
the database bean is wired correctly.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely cause | Fix |
|
||||
|---------|-------------|-----|
|
||||
| `ClassNotFoundException: org.postgresql.Driver` | PostgreSQL JDBC driver missing | Add `org.postgresql:postgresql` dependency (see Step 1 guide) |
|
||||
| `Cannot connect to database` at startup | DB unreachable but `skipDataSourceCheck` is `false` | Set `.skipDataSourceCheck(true)` |
|
||||
| Ebean enhancement warnings in logs | `ebean-maven-plugin` not configured | Complete Step 1 guide |
|
||||
| `NullPointerException` reading config key | Config key not defined | Add the key to `application.yml` or environment |
|
||||
@@ -0,0 +1,175 @@
|
||||
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 1: POM Setup
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide provides step-by-step instructions for modifying an existing Maven `pom.xml`
|
||||
to add Ebean ORM with PostgreSQL support. Follow every step in order. This is Step 1 of 2.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- An existing Maven project (`pom.xml` already exists)
|
||||
- Java 11 or higher
|
||||
- The project does **not** yet include any Ebean dependencies
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Define the Ebean version property
|
||||
|
||||
Open the module's `pom.xml` (the one that will use Ebean directly, i.e. the module
|
||||
containing the database configuration and entity classes).
|
||||
|
||||
Inside the `<properties>` block, add the `ebean.version` property if it does not
|
||||
already exist:
|
||||
|
||||
```xml
|
||||
<properties>
|
||||
<!-- add this line; use latest stable from https://github.com/ebean-orm/ebean/releases -->
|
||||
<ebean.version>17.2.0</ebean.version>
|
||||
</properties>
|
||||
```
|
||||
|
||||
> If the project has a parent POM that already defines `ebean.version`, skip this step.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Add the PostgreSQL JDBC driver dependency
|
||||
|
||||
Inside the `<dependencies>` block, add the PostgreSQL JDBC driver:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.8</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
> Check [Maven Central](https://central.sonatype.com/artifact/org.postgresql/postgresql)
|
||||
> for the latest version. If the parent POM manages the PostgreSQL version, omit the
|
||||
> `<version>` tag.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Add the Ebean PostgreSQL platform dependency
|
||||
|
||||
Inside the `<dependencies>` block, add the Ebean Postgres platform dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgres</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
This single artifact pulls in the Ebean core, the datasource connection pool
|
||||
(`ebean-datasource`), and all Postgres-specific support.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Add the ebean-test dependency (test scope)
|
||||
|
||||
`ebean-test` configures Ebean for tests and enables automatic Docker container management
|
||||
for Postgres test instances:
|
||||
|
||||
```xml
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — Add the ebean-maven-plugin (bytecode enhancement)
|
||||
|
||||
Ebean requires bytecode enhancement to provide dirty-checking and lazy-loading.
|
||||
The `ebean-maven-plugin` performs this enhancement at build time.
|
||||
|
||||
Inside the `<build><plugins>` block, add:
|
||||
|
||||
```xml
|
||||
<plugin> <!-- perform ebean enhancement -->
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
<extensions>true</extensions>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — Add the querybean-generator annotation processor
|
||||
|
||||
The `querybean-generator` annotation processor generates type-safe query bean classes
|
||||
at compile time. It must be registered as an `annotationProcessorPath` inside
|
||||
`maven-compiler-plugin`.
|
||||
|
||||
### Case A — No existing `maven-compiler-plugin` configuration
|
||||
|
||||
Add the full plugin entry to `<build><plugins>`:
|
||||
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.15.0</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path> <!-- generate ebean query beans -->
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
### Case B — `maven-compiler-plugin` already exists with `<annotationProcessorPaths>`
|
||||
|
||||
Locate the existing `<annotationProcessorPaths>` block inside the existing
|
||||
`maven-compiler-plugin` entry and add the new `<path>` inside it. Do **not** add a
|
||||
second `<configuration>` block or a second `<annotationProcessorPaths>` block.
|
||||
|
||||
Example — if the existing block already has a path for, say, `avaje-nima-generator`:
|
||||
|
||||
```xml
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-nima-generator</artifactId>
|
||||
<version>${avaje-nima.version}</version>
|
||||
</path>
|
||||
<!-- ADD the new path here, inside the existing block -->
|
||||
<path>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Run the following to confirm the POM is valid and the project compiles:
|
||||
|
||||
```bash
|
||||
mvn compile -pl <your-module-name>
|
||||
```
|
||||
|
||||
Expected result: `BUILD SUCCESS` with no errors from Ebean or the annotation processor.
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
|
||||
Proceed to **Step 2: Configure the Datasource and Ebean Database bean**
|
||||
(`add-ebean-postgres-database-config.md`).
|
||||
@@ -0,0 +1,293 @@
|
||||
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 3: Test Container Setup
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide provides step-by-step instructions for setting up a PostgreSQL Docker
|
||||
container for tests using `ebean-test-containers`, exposing an `io.ebean.Database`
|
||||
bean via an Avaje Inject `@TestScope @Factory` class. This is Step 3 of 3.
|
||||
|
||||
Two variants are covered:
|
||||
- **Variant A** — plain PostgreSQL
|
||||
- **Variant B** — PostgreSQL with PostGIS extension
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Step 1 complete**: `pom.xml` includes `ebean-postgres`, `ebean-maven-plugin`,
|
||||
`querybean-generator`, and **`ebean-test`** as a test-scoped dependency
|
||||
(see `add-ebean-postgres-maven-pom.md`)
|
||||
- **Step 2 complete**: A production `Database` bean exists (see `add-ebean-postgres-database-config.md`)
|
||||
- **Avaje Inject** is on the classpath with test support (`io.avaje:avaje-inject-test`)
|
||||
- **Docker** is installed and running on the developer machine
|
||||
|
||||
---
|
||||
|
||||
## Overview: Declarative vs Programmatic approach
|
||||
|
||||
`ebean-test` supports two ways to configure the test database:
|
||||
|
||||
| Approach | How | Best for |
|
||||
|----------|-----|---------|
|
||||
| **Declarative** | `src/test/resources/application-test.yaml` | Simple projects with no DI, no image mirrors |
|
||||
| **Programmatic** | `@TestScope @Factory` class | Avaje Inject tests, private image mirrors (ECR), more control |
|
||||
|
||||
This guide uses the **programmatic approach** because it integrates naturally with
|
||||
Avaje Inject, allows a private mirror to be specified (useful in CI with ECR or similar),
|
||||
and makes the `Database` injectable into tests.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — Verify ebean-test is a test dependency
|
||||
|
||||
Confirm the following is present in `pom.xml` (added in Step 1):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
`ebean-test` transitively brings in `ebean-test-containers` which provides
|
||||
`PostgresContainer` and `PostgisContainer`.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — Create a `@TestScope @Factory` class
|
||||
|
||||
Create a new class in the test source tree (e.g., `src/test/java/.../testconfig/TestConfiguration.java`).
|
||||
Annotate it with `@TestScope` and `@Factory` so Avaje Inject uses it only in tests.
|
||||
|
||||
```java
|
||||
package com.example.testconfig;
|
||||
|
||||
import io.avaje.inject.Bean;
|
||||
import io.avaje.inject.Factory;
|
||||
import io.avaje.inject.test.TestScope;
|
||||
import io.ebean.Database;
|
||||
|
||||
@TestScope
|
||||
@Factory
|
||||
class TestConfiguration {
|
||||
// bean methods added in the steps below
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — Add a container bean and a Database bean
|
||||
|
||||
### Variant A — Plain PostgreSQL
|
||||
|
||||
```java
|
||||
import io.ebean.test.containers.PostgresContainer;
|
||||
|
||||
@TestScope
|
||||
@Factory
|
||||
class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
PostgresContainer postgres() {
|
||||
return PostgresContainer.builder("17") // Postgres image version
|
||||
.dbName("my_app") // database to create inside the container
|
||||
.build()
|
||||
.start();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Database database(PostgresContainer container) {
|
||||
return container.ebean()
|
||||
.builder()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Variant B — PostGIS (PostgreSQL + PostGIS extension)
|
||||
|
||||
Use `PostgisContainer` instead of `PostgresContainer`. The default image is
|
||||
`ghcr.io/baosystems/postgis:{version}` and the extensions `hstore`, `pgcrypto`,
|
||||
and `postgis` are installed automatically.
|
||||
|
||||
```java
|
||||
import io.ebean.test.containers.PostgisContainer;
|
||||
|
||||
@TestScope
|
||||
@Factory
|
||||
class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
PostgisContainer postgres() {
|
||||
return PostgisContainer.builder("17") // PostGIS image version (Postgres 17)
|
||||
.dbName("my_app")
|
||||
.build()
|
||||
.start();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Database database(PostgisContainer container) {
|
||||
return container.ebean()
|
||||
.builder()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key differences from Variant A
|
||||
|
||||
| | PostgresContainer | PostgisContainer |
|
||||
|---|---|---|
|
||||
| Docker image | `postgres:{version}` | `ghcr.io/baosystems/postgis:{version}` |
|
||||
| Default extensions | `hstore, pgcrypto` | `hstore, pgcrypto, postgis` |
|
||||
| Default port | 6432 | 6432 |
|
||||
| Optional LW mode | — | `.useLW(true)` (see Optional section) |
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — Write a test
|
||||
|
||||
Annotate the test class with `@InjectTest` and inject `Database` with `@Inject`:
|
||||
|
||||
```java
|
||||
package com.example.testconfig;
|
||||
|
||||
import io.avaje.inject.test.InjectTest;
|
||||
import io.ebean.Database;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@InjectTest
|
||||
class DatabaseTest {
|
||||
|
||||
@Inject
|
||||
Database database;
|
||||
|
||||
@Test
|
||||
void database_isAvailable() {
|
||||
assertThat(database).isNotNull();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification
|
||||
|
||||
Run the tests:
|
||||
|
||||
```bash
|
||||
mvn test -pl <your-module>
|
||||
```
|
||||
|
||||
Expected log output confirming the container started and Ebean connected:
|
||||
|
||||
```
|
||||
INFO Container ut_postgres running with port:6432 ...
|
||||
INFO connectivity confirmed for ut_postgres
|
||||
INFO DataSourcePool [my_app] autoCommit[false] ...
|
||||
INFO DatabasePlatform name:my_app platform:postgres
|
||||
INFO Executing db-create-all.sql - ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Optional configurations
|
||||
|
||||
### Image mirror (for CI / private registry)
|
||||
|
||||
If CI builds pull images from a private registry (e.g., AWS ECR) instead of Docker Hub
|
||||
or GitHub Container Registry, specify a mirror. The mirror is **only used in CI** —
|
||||
it is ignored on local developer machines (where Docker Hub / GHCR is used directly).
|
||||
|
||||
```java
|
||||
@Bean
|
||||
PostgresContainer postgres() {
|
||||
return PostgresContainer.builder("16")
|
||||
.dbName("my_app")
|
||||
.mirror("123456789.dkr.ecr.ap-southeast-2.amazonaws.com/mirrored")
|
||||
.build()
|
||||
.start();
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, set the mirror globally via a system property or
|
||||
`ebean.test.containers.mirror` in a properties file, avoiding code changes per project.
|
||||
|
||||
### Read-only datasource (for tests using read-replica simulation)
|
||||
|
||||
Call `.autoReadOnlyDataSource(true)` on the `DatabaseBuilder` to automatically
|
||||
create a second read-only datasource pointing at the same container:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
Database database(PostgresContainer container) {
|
||||
return container.ebean()
|
||||
.builder()
|
||||
.autoReadOnlyDataSource(true) // test read-only queries against same container
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### Dump metrics on shutdown
|
||||
|
||||
Useful for performance analysis during test runs:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
Database database(PostgresContainer container) {
|
||||
return container.ebean()
|
||||
.builder()
|
||||
.dumpMetricsOnShutdown(true)
|
||||
.dumpMetricsOptions("loc,sql,hash")
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
### PostGIS: LW mode (HexWKB)
|
||||
|
||||
For PostGIS with DriverWrapperLW (HexWKB binary geometry encoding), set `.useLW(true)`.
|
||||
This switches the JDBC URL prefix to `jdbc:postgresql_lwgis://` and requires the
|
||||
`net.postgis:postgis-jdbc` dependency on the test classpath:
|
||||
|
||||
```xml
|
||||
<!-- add to pom.xml test dependencies when using useLW(true) -->
|
||||
<dependency>
|
||||
<groupId>net.postgis</groupId>
|
||||
<artifactId>postgis-jdbc</artifactId>
|
||||
<version>2024.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
```java
|
||||
@Bean
|
||||
PostgisContainer postgres() {
|
||||
return PostgisContainer.builder("16")
|
||||
.dbName("my_app")
|
||||
.useLW(true) // use HexWKB + DriverWrapperLW
|
||||
.build()
|
||||
.start();
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**: LW mode is not required for most PostGIS use cases. Only enable it if
|
||||
> your entities use binary geometry types (e.g., `net.postgis.jdbc.geometry.Geometry`)
|
||||
> that require the `DriverWrapperLW` driver.
|
||||
|
||||
---
|
||||
|
||||
## Keeping the container running (local development)
|
||||
|
||||
By default, `ebean-test` stops the Docker container when tests finish. To keep it
|
||||
running between test runs (much faster for local development), create a marker file:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.ebean && touch ~/.ebean/ignore-docker-shutdown
|
||||
```
|
||||
|
||||
On CI servers, omit this file so containers are cleaned up after each build.
|
||||
@@ -0,0 +1,206 @@
|
||||
# Guide: Using Lombok with Ebean Entity Beans
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide explains which Lombok annotations are safe and recommended for Ebean
|
||||
entity beans, which ones to avoid, and why. It is written as prescriptive instructions
|
||||
for AI agents and developers.
|
||||
|
||||
---
|
||||
|
||||
## The Core Rule
|
||||
|
||||
> **Do NOT use `@Data` on Ebean entity beans.**
|
||||
|
||||
Use `@Getter` + `@Setter` instead, with the optional `@Accessors(chain = true)` for
|
||||
a fluent setter style.
|
||||
|
||||
---
|
||||
|
||||
## Why `@Data` is Incompatible with Ebean
|
||||
|
||||
`@Data` is a convenience annotation that is equivalent to applying `@Getter`,
|
||||
`@Setter`, `@RequiredArgsConstructor`, `@ToString`, and `@EqualsAndHashCode` together.
|
||||
Three of those are problematic for Ebean entity beans:
|
||||
|
||||
### 1. `@EqualsAndHashCode` (included in `@Data`) — breaks entity identity
|
||||
|
||||
`@Data` generates `hashCode()` and `equals()` based on all non-static, non-transient
|
||||
fields. Ebean entity beans have identity semantics — two references to the same database
|
||||
row should be considered equal based on their `@Id` value, not field-by-field comparison.
|
||||
|
||||
Problems caused:
|
||||
- Inconsistent `hashCode` before and after persist (the `@Id` field is `0` on a new
|
||||
entity, then changes after insert — violating the `hashCode` contract for collections)
|
||||
- Entities placed in a `Set` or `HashMap` before saving will be unfindable after saving
|
||||
- Ebean's internal identity map and dirty checking can be confused
|
||||
|
||||
### 2. `@ToString` (included in `@Data`) — triggers unexpected lazy loading
|
||||
|
||||
`@Data` generates a `toString()` that accesses **all** fields, including
|
||||
`@OneToMany` and `@ManyToOne` associations. Accessing an unloaded lazy association
|
||||
outside of a transaction triggers a `LazyInitializationException` or fires an unexpected
|
||||
SQL query, which can:
|
||||
- Cause subtle bugs in logging statements
|
||||
- Trigger N+1 queries in test output or debug logging
|
||||
- Fail with an exception if no active transaction exists
|
||||
|
||||
### 3. `@RequiredArgsConstructor` (included in `@Data`) — unnecessary for Ebean
|
||||
|
||||
Ebean does not require a default constructor — it can construct entity instances without
|
||||
one. `@RequiredArgsConstructor` therefore adds nothing useful to entity beans.
|
||||
|
||||
---
|
||||
|
||||
## Recommended Annotation Set
|
||||
|
||||
Use exactly these three Lombok annotations on every Ebean entity bean:
|
||||
|
||||
```java
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@Table(name = "my_table")
|
||||
public class MyEntity {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
| Annotation | Purpose |
|
||||
|---|---|
|
||||
| `@Getter` | Generates `getFoo()` / `isFoo()` accessor methods |
|
||||
| `@Setter` | Generates `setFoo(value)` mutator methods; Ebean enhancement intercepts these for dirty tracking |
|
||||
| `@Accessors(chain = true)` | Makes setters return `this`, enabling fluent/builder-style property setting |
|
||||
|
||||
---
|
||||
|
||||
## `@Accessors(chain = true)` — Fluent Setter Style
|
||||
|
||||
With `chain = true`, setters return `this` instead of `void`, allowing method chaining:
|
||||
|
||||
```java
|
||||
// without chain = true (void setters)
|
||||
CMachine machine = new CMachine();
|
||||
machine.setMake("Toyota");
|
||||
machine.setModel("Hilux");
|
||||
machine.setStatus("active");
|
||||
|
||||
// with @Accessors(chain = true)
|
||||
CMachine machine = new CMachine()
|
||||
.setMake("Toyota")
|
||||
.setModel("Hilux")
|
||||
.setStatus("active");
|
||||
```
|
||||
|
||||
This is particularly useful when building test data:
|
||||
|
||||
```java
|
||||
CMachine machine = new CMachine()
|
||||
.setGid(UUID.randomUUID())
|
||||
.setMachineType("HV")
|
||||
.setStatus("active")
|
||||
.setMake("Komatsu")
|
||||
.setModel("PC200");
|
||||
|
||||
database.save(machine);
|
||||
```
|
||||
|
||||
Ebean's bytecode enhancement is fully compatible with chained setters — the
|
||||
enhancement intercepts each `setFoo()` call to record which fields have been modified
|
||||
(dirty checking), regardless of whether the setter returns `void` or `this`.
|
||||
|
||||
---
|
||||
|
||||
## `@Accessors(fluent = true)` — also compatible
|
||||
|
||||
`@Accessors(fluent = true)` removes the `get`/`set`/`is` prefix, generating `name()`
|
||||
(getter) and `name(value)` (setter) instead of `getName()` and `setName(value)`.
|
||||
|
||||
Ebean does **not** require JavaBeans naming conventions — it can work with any accessor
|
||||
method style, including fluent accessors with no prefix. `@Accessors(fluent = true)` is
|
||||
therefore compatible with Ebean.
|
||||
|
||||
`@Accessors(chain = true)` is the more common choice in practice (it keeps the familiar
|
||||
`get`/`set` prefix while adding method chaining), but `fluent = true` is a valid
|
||||
alternative if that style is preferred consistently across the codebase.
|
||||
|
||||
---
|
||||
|
||||
## Full Entity Bean Example
|
||||
|
||||
```java
|
||||
package com.example.repository.data;
|
||||
|
||||
import io.ebean.annotation.WhenCreated;
|
||||
import io.ebean.annotation.WhenModified;
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@Setter
|
||||
@Accessors(chain = true)
|
||||
@Table(name = "machine")
|
||||
public class CMachine {
|
||||
|
||||
@Id
|
||||
private long id;
|
||||
|
||||
@Version
|
||||
private int version;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private UUID gid;
|
||||
|
||||
@Column(nullable = false, length = 10)
|
||||
private String machineType;
|
||||
|
||||
@Column(length = 200)
|
||||
private String make;
|
||||
|
||||
@Column(length = 200)
|
||||
private String model;
|
||||
|
||||
@WhenCreated
|
||||
private Instant created;
|
||||
|
||||
@WhenModified
|
||||
private Instant lastModified;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary: Lombok Annotations and Ebean Compatibility
|
||||
|
||||
| Lombok Annotation | Compatible? | Notes |
|
||||
|---|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `@Getter` | ✅ Safe | Use on every entity bean |
|
||||
| `@Setter` | ✅ Safe | Use on every entity bean; enhancement intercepts these |
|
||||
| `@Accessors(chain = true)` | ✅ Safe | Recommended for fluent construction style |
|
||||
| `@ToString` | ❌ Avoid | Ebean does a better job and handles recursion |
|
||||
| `@EqualsAndHashCode` | ❌ Avoid | Breaks entity identity and `@Id`-based equality |
|
||||
| `@Data` | ❌ Avoid | Includes `@EqualsAndHashCode` and `@ToString` — both problematic |
|
||||
| `@Value` | ❌ Avoid | Makes fields final — incompatible with Ebean's field-level bytecode enhancement |
|
||||
| `@Accessors(fluent = true)` | ✅ Safe | Removes `get`/`set` prefix — Ebean does not require JavaBeans naming conventions and works with any accessor style |
|
||||
| `@Builder` | ⚠️ Careful | Usable on non-entity helper/factory classes; on entity beans it requires a no-arg constructor alongside it and offers no advantage over `@Accessors(chain = true)` |
|
||||
|
||||
---
|
||||
|
||||
## Relationship with Ebean Bytecode Enhancement
|
||||
|
||||
Ebean's bytecode enhancement (applied by `ebean-maven-plugin` at build time) modifies
|
||||
the `setXxx()` methods of entity beans to:
|
||||
1. Mark the field as dirty (changed) so only modified fields are included in UPDATE statements
|
||||
2. Support lazy loading of associations when a getter is called on an unloaded field
|
||||
|
||||
For this to work correctly, Ebean needs:
|
||||
- Accessor methods for each persistent field (any naming style is fine — `getFoo()`, `foo()`, or no accessors at all; Ebean can also access fields directly)
|
||||
- No override of `hashCode()` / `equals()` that would interfere with the identity map — which means **no `@Data` or `@EqualsAndHashCode`**
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -35,7 +35,7 @@
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-config</artifactId>
|
||||
<version>3.12</version>
|
||||
<version>4.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -137,6 +137,7 @@ public interface Database {
|
||||
/**
|
||||
* Return the associated read only DataSource for this Database instance (can be null).
|
||||
*/
|
||||
@Nullable
|
||||
DataSource readOnlyDataSource();
|
||||
|
||||
/**
|
||||
|
||||
@@ -989,6 +989,14 @@ public interface DatabaseBuilder {
|
||||
@Deprecated
|
||||
DatabaseBuilder setNamingConvention(NamingConvention namingConvention);
|
||||
|
||||
/**
|
||||
* Set the AggregateFormulaContext which is used to determine if a database function
|
||||
* is an aggregate function (like sum, min, max, avg etc).
|
||||
* <p>
|
||||
* Use this to override the default known aggregation functions.
|
||||
*/
|
||||
DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext);
|
||||
|
||||
/**
|
||||
* Set to true if all DB column and table names should use quoted identifiers.
|
||||
* <p>
|
||||
@@ -1062,6 +1070,11 @@ public interface DatabaseBuilder {
|
||||
*/
|
||||
DatabaseBuilder readOnlyDatabase(boolean readOnlyDatabase);
|
||||
|
||||
/**
|
||||
* Set to false such that the instance does not register a JVM shutdown hook.
|
||||
*/
|
||||
DatabaseBuilder shutdownHook(boolean shutdownHook);
|
||||
|
||||
/**
|
||||
* Set a DataSource.
|
||||
*/
|
||||
@@ -2019,6 +2032,11 @@ public interface DatabaseBuilder {
|
||||
@Deprecated
|
||||
DatabaseBuilder setQueryPlanTTLSeconds(int queryPlanTTLSeconds);
|
||||
|
||||
/**
|
||||
* Set the EXPLAIN (with options) to use for query plan capture.
|
||||
*/
|
||||
DatabaseBuilder queryPlanExplain(String queryPlanExplain);
|
||||
|
||||
/**
|
||||
* Create a new PlatformConfig based of the one held but with overridden properties by reading
|
||||
* properties with the given path and prefix.
|
||||
@@ -2600,6 +2618,11 @@ public interface DatabaseBuilder {
|
||||
*/
|
||||
NamingConvention getNamingConvention();
|
||||
|
||||
/**
|
||||
* Return the AggregateFormulaContext.
|
||||
*/
|
||||
AggregateFormulaContext aggregateFormulaContext();
|
||||
|
||||
/**
|
||||
* Return true if all DB column and table names should use quoted identifiers.
|
||||
*/
|
||||
@@ -2638,6 +2661,11 @@ public interface DatabaseBuilder {
|
||||
*/
|
||||
boolean readOnlyDatabase();
|
||||
|
||||
/**
|
||||
* Return if a JVM shutdown hook should be registered.
|
||||
*/
|
||||
boolean shutdownHook();
|
||||
|
||||
/**
|
||||
* Return the DataSource.
|
||||
*/
|
||||
@@ -3044,6 +3072,11 @@ public interface DatabaseBuilder {
|
||||
*/
|
||||
int getQueryPlanTTLSeconds();
|
||||
|
||||
/**
|
||||
* Return the EXPLAIN (with options) to use for capturing query plans.
|
||||
*/
|
||||
String getQueryPlanExplain();
|
||||
|
||||
/**
|
||||
* Return mapping locations to search for xml mapping via class path search.
|
||||
*/
|
||||
|
||||
@@ -234,5 +234,16 @@ public interface DtoQuery<T> extends CancelableQuery {
|
||||
* source. We use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
DtoQuery<T> usingMaster();
|
||||
default DtoQuery<T> usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
DtoQuery<T> usingMaster(boolean useMaster);
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.ebean;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* FutureMap represents the result of a background query execution that will
|
||||
* return a map of entities.
|
||||
* <p>
|
||||
* It extends the java.util.concurrent.Future with the ability to cancel the
|
||||
* query, check if it is finished and get the resulting list waiting for the
|
||||
* query to finish (ie. the standard features of java.util.concurrent.Future).
|
||||
* </p>
|
||||
* <p>
|
||||
* A simple example:
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // create a query to find all orders
|
||||
* Query<Long,Order> query = DB.find(Order.class)
|
||||
* .setMapKey("id");
|
||||
*
|
||||
* // execute the query in a background thread
|
||||
* // immediately returning the futureMap
|
||||
* FutureMap<Long,Order> futureMap = query.findFutureMap();
|
||||
*
|
||||
* // do something else ...
|
||||
*
|
||||
* if (!futureMap.isDone()){
|
||||
* // we can cancel the query execution. This will cancel
|
||||
* // the underlying query if that is supported by the JDBC
|
||||
* // driver and database
|
||||
* futureMap.cancel(true);
|
||||
* }
|
||||
*
|
||||
* if (!futureMap.isCancelled()){
|
||||
* // wait for the query to finish and return the map
|
||||
* Map<Long,Order> map = futureMap.get();
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
public interface FutureMap<K, T> extends Future<Map<K, T>> {
|
||||
|
||||
/**
|
||||
* Return the query that is being executed by a background thread.
|
||||
*/
|
||||
Query<T> getQuery();
|
||||
|
||||
/**
|
||||
* Same as {@link #get()} but wraps InterruptedException and ExecutionException in the
|
||||
* unchecked PersistenceException.
|
||||
*
|
||||
* @return The query list result
|
||||
* @throws PersistenceException when a InterruptedException or ExecutionException occurs.
|
||||
*/
|
||||
Map<K, T> getUnchecked();
|
||||
|
||||
/**
|
||||
* Same as {@link #get(long, TimeUnit)} but wraps InterruptedException
|
||||
* and ExecutionException in the unchecked PersistenceException.
|
||||
*
|
||||
* @return The query list result
|
||||
* @throws TimeoutException if the wait timed out
|
||||
* @throws PersistenceException if a InterruptedException or ExecutionException occurs.
|
||||
*/
|
||||
Map<K, T> getUnchecked(long timeout, TimeUnit unit) throws TimeoutException;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Object relational query for finding a List, Set, Map or single entity bean.
|
||||
@@ -310,6 +311,7 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
|
||||
/**
|
||||
* Return the Id value.
|
||||
*/
|
||||
@Nullable
|
||||
Object getId();
|
||||
|
||||
/**
|
||||
@@ -446,11 +448,13 @@ public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
|
||||
/**
|
||||
* Return the "for update" wait mode to use.
|
||||
*/
|
||||
@Nullable
|
||||
LockWait getForUpdateLockWait();
|
||||
|
||||
/**
|
||||
* Return the lock type (strength) to use with "for update".
|
||||
*/
|
||||
@Nullable
|
||||
LockType getForUpdateLockType();
|
||||
|
||||
/**
|
||||
|
||||
@@ -135,7 +135,17 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
SELF usingMaster();
|
||||
default SELF usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
SELF usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Set the base table to use for this query.
|
||||
@@ -393,6 +403,12 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
* This means that the returning graph can't be mutated via setters, all the collections
|
||||
* are unmodifiable collections, lazy loading is disabled and that the query uses
|
||||
* {@link PersistenceContextScope#QUERY}.
|
||||
* <p>
|
||||
* Attempting to mutate an unmodifiable bean will throw a <code>UnmodifiableEntityException</code>.
|
||||
* Attempting to load an unloaded property will throw a <code>LazyInitialisationException</code>
|
||||
*
|
||||
* @see LazyInitialisationException
|
||||
* @see UnmodifiableEntityException
|
||||
*/
|
||||
SELF setUnmodifiable(boolean unmodifiable);
|
||||
|
||||
@@ -441,6 +457,16 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
|
||||
/**
|
||||
* Set the {@link CacheMode} to use the query for executing this query.
|
||||
* <p>
|
||||
* Since version 16.x using the query bean will set the query to use unmodifiable (see
|
||||
* {@link #setUnmodifiable(boolean)}) so the returned object graph is unmodifiable
|
||||
* and safe to cache by the application.
|
||||
* <p>
|
||||
* Attempting to mutate an unmodifiable bean will throw a <code>UnmodifiableEntityException</code>.
|
||||
* Attempting to load an unloaded property will throw a <code>LazyInitialisationException</code>
|
||||
*
|
||||
* @see LazyInitialisationException
|
||||
* @see UnmodifiableEntityException
|
||||
*/
|
||||
SELF setUseQueryCache(CacheMode cacheMode);
|
||||
|
||||
@@ -956,6 +982,16 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
*/
|
||||
FutureList<T> findFutureList();
|
||||
|
||||
/**
|
||||
* Execute find map query in a background thread.
|
||||
* <p>
|
||||
* This query will execute in it's own PersistenceContext and using its own transaction.
|
||||
* What that means is that it will not share any bean instances with other queries.
|
||||
*
|
||||
* @return a Future object for the map result of the query
|
||||
*/
|
||||
<K> FutureMap<K,T> findFutureMap();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
|
||||
@@ -63,7 +63,17 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
* source. We use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
SqlQuery usingMaster();
|
||||
default SqlQuery usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
SqlQuery usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
@@ -357,6 +367,14 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
*/
|
||||
interface TypeQuery<T> {
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see SqlQuery#usingMaster(boolean)
|
||||
*/
|
||||
TypeQuery<T> usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Execute the query using the given transaction.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebean.cache;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -10,11 +11,11 @@ import java.util.Set;
|
||||
* the query cache entry is treated as invalid.
|
||||
* </p>
|
||||
*/
|
||||
public class QueryCacheEntry {
|
||||
public final class QueryCacheEntry {
|
||||
|
||||
private final Object value;
|
||||
private final Set<String> dependentTables;
|
||||
private final long timestamp;
|
||||
private final Instant timestamp;
|
||||
|
||||
/**
|
||||
* Create with dependent tables and timestamp.
|
||||
@@ -23,7 +24,7 @@ public class QueryCacheEntry {
|
||||
* @param dependentTables The extra tables the query is dependent on (joins to)
|
||||
* @param timestamp The timestamp that the query uses to check for modifications
|
||||
*/
|
||||
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
|
||||
public QueryCacheEntry(Object value, Set<String> dependentTables, Instant timestamp) {
|
||||
this.value = value;
|
||||
this.dependentTables = dependentTables;
|
||||
this.timestamp = timestamp;
|
||||
@@ -32,21 +33,21 @@ public class QueryCacheEntry {
|
||||
/**
|
||||
* Return the actual query result.
|
||||
*/
|
||||
public Object getValue() {
|
||||
public Object value() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tables the query result is dependent on.
|
||||
*/
|
||||
public Set<String> getDependentTables() {
|
||||
public Set<String> dependentTables() {
|
||||
return dependentTables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the timestamp used to check for modifications on the dependent tables.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
public Instant timestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Used when parsing formulas to determine if they are aggregation formulas like
|
||||
* sum, min, max, avg, count etc.
|
||||
* <p>
|
||||
* Ebean needs to determine if they are aggregation formulas to determine which
|
||||
* properties should be included in a GROUP BY clause etc.
|
||||
*/
|
||||
public interface AggregateFormulaContext {
|
||||
|
||||
/**
|
||||
* Return true if the outer function is an aggregate function (like sum, count, min, max, avg etc).
|
||||
*/
|
||||
boolean isAggregate(String outerFunction);
|
||||
|
||||
/**
|
||||
* Return true if the aggregate function returns a BIGINT type.
|
||||
* This is true for functions like count that return a numeric value regardless of the
|
||||
* type of the property or expression inside the outer function.
|
||||
*/
|
||||
boolean isCount(String outerFunction);
|
||||
|
||||
/**
|
||||
* Return true if the aggregate function returns a VARCHAR type.
|
||||
* This is true for functions that return a string concatenation like group_concat etc
|
||||
* regardless of the type of the property used inside the outer function.
|
||||
*/
|
||||
boolean isConcat(String outerFunction);
|
||||
|
||||
/**
|
||||
* Return a builder for the AggregateFormulaContext.
|
||||
*/
|
||||
static Builder builder() {
|
||||
return new AggregateFormulaContextBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for the AggregateFormulaContext.
|
||||
*/
|
||||
interface Builder {
|
||||
|
||||
/**
|
||||
* Override the default set of aggregation functions.
|
||||
*/
|
||||
Builder aggregateFunctions(Set<String> count);
|
||||
|
||||
/**
|
||||
* Override the default set of concat functions.
|
||||
*/
|
||||
Builder concatFunctions(Set<String> concat);
|
||||
|
||||
/**
|
||||
* Override the default set of count functions.
|
||||
*/
|
||||
Builder countFunctions(Set<String> count);
|
||||
|
||||
/**
|
||||
* Build the AggregateFormulaContext.
|
||||
*/
|
||||
AggregateFormulaContext build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
final class AggregateFormulaContextBuilder implements AggregateFormulaContext.Builder {
|
||||
|
||||
private Set<String> aggFunctions = Set.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg");
|
||||
private Set<String> concat = Set.of("concat", "group_concat", "string_agg", "listagg");
|
||||
private Set<String> count = Set.of("count");
|
||||
|
||||
@Override
|
||||
public AggregateFormulaContext.Builder aggregateFunctions(Set<String> agg) {
|
||||
this.aggFunctions = agg;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregateFormulaContext.Builder concatFunctions(Set<String> concat) {
|
||||
this.concat = concat;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregateFormulaContext.Builder countFunctions(Set<String> count) {
|
||||
this.count = count;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregateFormulaContext build() {
|
||||
return new FormulaContext(aggFunctions, concat, count);
|
||||
}
|
||||
|
||||
private static final class FormulaContext implements AggregateFormulaContext {
|
||||
|
||||
private final Set<String> aggFunctions;
|
||||
private final Set<String> concat;
|
||||
private final Set<String> count;
|
||||
|
||||
private FormulaContext(Set<String> aggFunctions, Set<String> concat, Set<String> count) {
|
||||
this.aggFunctions = aggFunctions;
|
||||
this.concat = concat;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregate(String outerFunction) {
|
||||
return aggFunctions.contains(outerFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCount(String outerFunction) {
|
||||
return count.contains(outerFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConcat(String outerFunction) {
|
||||
return concat.contains(outerFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,7 +251,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
/**
|
||||
* The default batch size for lazy loading
|
||||
*/
|
||||
private int lazyLoadBatchSize = 10;
|
||||
private int lazyLoadBatchSize = 100;
|
||||
|
||||
/**
|
||||
* The default batch size for 'query joins'.
|
||||
@@ -311,6 +311,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
private boolean skipDataSourceCheck;
|
||||
|
||||
private boolean readOnlyDatabase;
|
||||
private boolean shutdownHook = true;
|
||||
|
||||
/**
|
||||
* The data source (if programmatically provided).
|
||||
@@ -355,6 +356,8 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
*/
|
||||
private NamingConvention namingConvention = new UnderscoreNamingConvention();
|
||||
|
||||
private AggregateFormulaContext aggregateFormulaContext = AggregateFormulaContext.builder().build();
|
||||
|
||||
/**
|
||||
* Behaviour of updates in JDBC batch to by default include all properties.
|
||||
*/
|
||||
@@ -481,6 +484,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
* Time to live for query plans - defaults to 5 minutes.
|
||||
*/
|
||||
private int queryPlanTTLSeconds = 60 * 5;
|
||||
private String queryPlanExplain;
|
||||
|
||||
/**
|
||||
* Set to true to globally disable L2 caching (typically for performance testing).
|
||||
@@ -1277,6 +1281,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregateFormulaContext aggregateFormulaContext() {
|
||||
return aggregateFormulaContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext) {
|
||||
this.aggregateFormulaContext = aggregateFormulaContext;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAllQuotedIdentifiers() {
|
||||
return platformConfig.isAllQuotedIdentifiers();
|
||||
@@ -1364,6 +1379,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
return readOnlyDatabase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseBuilder shutdownHook(boolean shutdownHook) {
|
||||
this.shutdownHook = shutdownHook;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shutdownHook() {
|
||||
return shutdownHook;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return dataSource;
|
||||
@@ -2136,6 +2162,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
loadDocStoreSettings(p);
|
||||
|
||||
defaultServer = p.getBoolean("defaultServer", defaultServer);
|
||||
shutdownHook = p.getBoolean("shutdownHook", shutdownHook);
|
||||
readOnlyDatabase = p.getBoolean("readOnlyDatabase", readOnlyDatabase);
|
||||
autoPersistUpdates = p.getBoolean("autoPersistUpdates", autoPersistUpdates);
|
||||
loadModuleInfo = p.getBoolean("loadModuleInfo", loadModuleInfo);
|
||||
@@ -2151,6 +2178,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
queryPlanCapturePeriodSecs = p.getLong("queryPlan.capturePeriodSecs", queryPlanCapturePeriodSecs);
|
||||
queryPlanCaptureMaxTimeMillis = p.getLong("queryPlan.captureMaxTimeMillis", queryPlanCaptureMaxTimeMillis);
|
||||
queryPlanCaptureMaxCount = p.getInt("queryPlan.captureMaxCount", queryPlanCaptureMaxCount);
|
||||
queryPlanExplain = p.get("queryPlan.explain", queryPlanExplain);
|
||||
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
|
||||
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
|
||||
localOnlyL2Cache = p.getBoolean("localOnlyL2Cache", localOnlyL2Cache);
|
||||
@@ -2402,6 +2430,17 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
return queryPlanTTLSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryPlanExplain() {
|
||||
return queryPlanExplain;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseBuilder queryPlanExplain(String queryPlanExplain) {
|
||||
this.queryPlanExplain = queryPlanExplain;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConfig setQueryPlanTTLSeconds(int queryPlanTTLSeconds) {
|
||||
this.queryPlanTTLSeconds = queryPlanTTLSeconds;
|
||||
|
||||
@@ -167,6 +167,7 @@ public class DatabasePlatform {
|
||||
* findIterate() and findVisit().
|
||||
*/
|
||||
protected boolean forwardOnlyHintOnFindIterate;
|
||||
protected boolean autoCommitFalseOnFindIterate;
|
||||
|
||||
/**
|
||||
* If set then use the CONCUR_UPDATABLE hint when creating ResultSets.
|
||||
@@ -528,6 +529,10 @@ public class DatabasePlatform {
|
||||
this.forwardOnlyHintOnFindIterate = forwardOnlyHintOnFindIterate;
|
||||
}
|
||||
|
||||
public boolean autoCommitFalseOnFindIterate() {
|
||||
return autoCommitFalseOnFindIterate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the ResultSet CONCUR_UPDATABLE Hint should be used on
|
||||
* createNativeSqlTree() PreparedStatements.
|
||||
@@ -780,4 +785,5 @@ public class DatabasePlatform {
|
||||
public String inlineSqlHint(String hint) {
|
||||
return "/*+ " + hint + " */ ";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,6 +46,18 @@ public class DbPlatformTypeMapping {
|
||||
private static final DbPlatformType MULTILINESTRING = new DbPlatformType("multilinestring");
|
||||
private static final DbPlatformType MULTIPOLYGON = new DbPlatformType("multipolygon");
|
||||
|
||||
private static final DbPlatformType VECTOR = new DbPlatformType("vector", 2000, null);
|
||||
private static final DbPlatformType VECTOR_HALF = new DbPlatformType("halfvec", 4000, null);
|
||||
private static final DbPlatformType VECTOR_BIT = new DbPlatformType("bit", 64000, null);
|
||||
private static final DbPlatformType VECTOR_SPARSE = new DbPlatformType("sparsevec", 1000, null);
|
||||
|
||||
/**
|
||||
* Timestamp with max precision of 15, and fallback to plain timestamp without precision defined.
|
||||
*/
|
||||
private static final DbPlatformType TIMESTAMP =
|
||||
new DbPlatformType("timestamp", 0, 15,
|
||||
new DbPlatformType("timestamp", false));
|
||||
|
||||
private final Map<DbType, DbPlatformType> typeMap = new EnumMap<>(DbType.class);
|
||||
|
||||
/**
|
||||
@@ -82,7 +94,8 @@ public class DbPlatformTypeMapping {
|
||||
put(DbType.ARRAY);
|
||||
put(DbType.DATE);
|
||||
put(DbType.TIME);
|
||||
put(DbType.TIMESTAMP);
|
||||
put(DbType.TIMESTAMP, TIMESTAMP);
|
||||
|
||||
put(DbType.LONGVARBINARY);
|
||||
put(DbType.LONGVARCHAR);
|
||||
// most commonly real maps to db float
|
||||
@@ -93,6 +106,10 @@ public class DbPlatformTypeMapping {
|
||||
put(DbType.MULTIPOINT, MULTIPOINT);
|
||||
put(DbType.MULTILINESTRING, MULTILINESTRING);
|
||||
put(DbType.MULTIPOLYGON, MULTIPOLYGON);
|
||||
put(DbType.VECTOR, VECTOR);
|
||||
put(DbType.VECTOR_HALF, VECTOR_HALF);
|
||||
put(DbType.VECTOR_BIT, VECTOR_BIT);
|
||||
put(DbType.VECTOR_SPARSE, VECTOR_SPARSE);
|
||||
|
||||
if (logicalTypes) {
|
||||
// keep it logical for 2 layer DDL generation
|
||||
|
||||
@@ -51,7 +51,12 @@ public enum DbType {
|
||||
JSONB(ExtraDbTypes.JSONB),
|
||||
JSONCLOB(ExtraDbTypes.JSONClob),
|
||||
JSONBLOB(ExtraDbTypes.JSONBlob),
|
||||
JSONVARCHAR(ExtraDbTypes.JSONVarchar);
|
||||
JSONVARCHAR(ExtraDbTypes.JSONVarchar),
|
||||
|
||||
VECTOR(ExtraDbTypes.VECTOR),
|
||||
VECTOR_HALF(ExtraDbTypes.VECTOR_HALF),
|
||||
VECTOR_BIT(ExtraDbTypes.VECTOR_BIT),
|
||||
VECTOR_SPARSE(ExtraDbTypes.VECTOR_SPARSE);
|
||||
|
||||
private final int id;
|
||||
|
||||
|
||||
@@ -74,4 +74,24 @@ public interface ExtraDbTypes {
|
||||
*/
|
||||
int MULTILINESTRING = 6007;
|
||||
|
||||
/**
|
||||
* PGVector base type
|
||||
*/
|
||||
int VECTOR = 7000;
|
||||
|
||||
/**
|
||||
* PGVector half precision float type
|
||||
*/
|
||||
int VECTOR_HALF = 7001;
|
||||
|
||||
/**
|
||||
* PGVector binary type (bit)
|
||||
*/
|
||||
int VECTOR_BIT = 7002;
|
||||
|
||||
/**
|
||||
* PGVector sparse type
|
||||
*/
|
||||
int VECTOR_SPARSE = 7003;
|
||||
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ public final class ShutdownManager {
|
||||
private static void registerShutdownHook() {
|
||||
lock.lock();
|
||||
try {
|
||||
String value = System.getProperty("ebean.registerShutdownHook");
|
||||
if (value == null || !value.trim().equalsIgnoreCase("false")) {
|
||||
if ("true".equalsIgnoreCase(System.getProperty("ebean.registerShutdownHook", "true"))) {
|
||||
Runtime.getRuntime().addShutdownHook(shutdownHook);
|
||||
}
|
||||
} catch (IllegalStateException ex) {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Initiate query plan collection for plans by their hash or all query plans.
|
||||
*/
|
||||
public class QueryPlanInit {
|
||||
|
||||
private final Map<String,Long> hashes = new HashMap<>();
|
||||
|
||||
private boolean all;
|
||||
|
||||
private Set<String> hashes = new HashSet<>();
|
||||
|
||||
private long thresholdMicros;
|
||||
private long defaultThresholdMicros;
|
||||
|
||||
/**
|
||||
* Return true if this initiates bind collection on all query plans.
|
||||
@@ -29,39 +30,74 @@ public class QueryPlanInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the query execution time threshold which must be exceeded to initiate
|
||||
* Return the default query execution time threshold which must be exceeded to initiate
|
||||
* query plan collection.
|
||||
*/
|
||||
public long thresholdMicros() {
|
||||
return thresholdMicros;
|
||||
return defaultThresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the query execution time threshold which must be exceeded to initiate
|
||||
* Set the default query execution time threshold which must be exceeded to initiate
|
||||
* query plan collection.
|
||||
*/
|
||||
public void thresholdMicros(long thresholdMicros) {
|
||||
this.thresholdMicros = thresholdMicros;
|
||||
this.defaultThresholdMicros = thresholdMicros;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the query plan should be initiated based on it's hash.
|
||||
*/
|
||||
public boolean includeHash(String hash) {
|
||||
return all || hashes.contains(hash);
|
||||
return all || hashes.containsKey(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the specific hashes that we want to collect query plans on.
|
||||
*
|
||||
* @param hash The hash of the query plan.
|
||||
* @param thresholdMicros The threshold in micros to use.
|
||||
*/
|
||||
public Set<String> hashes() {
|
||||
return hashes;
|
||||
public void add(String hash, long thresholdMicros) {
|
||||
requireNonNull(hash);
|
||||
if (!"all".equals(hash)) {
|
||||
hashes.put(hash, thresholdMicros);
|
||||
} else {
|
||||
all = true;
|
||||
if (thresholdMicros > 0) {
|
||||
defaultThresholdMicros = thresholdMicros;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the specific hashes that we want to collect query plans on.
|
||||
* Remove a hash from this request.
|
||||
*/
|
||||
public void hashes(Set<String> hashes) {
|
||||
this.hashes = hashes;
|
||||
public void remove(String hash) {
|
||||
hashes.remove(hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the threshold in micros to use for the given hash.
|
||||
*/
|
||||
public long thresholdMicros(String hash) {
|
||||
Long threshold = hashes.get(hash);
|
||||
return threshold == null || threshold < 1 ? defaultThresholdMicros : threshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if there are no registered hashes and not collect <em>All</em> plans.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return !all && hashes.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QueryPlanInit{" +
|
||||
"all=" + all +
|
||||
", hashes=" + hashes +
|
||||
", thresholdMicros=" + defaultThresholdMicros +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class AggregateFormulaContextTest {
|
||||
|
||||
@Test
|
||||
void defaultContext() {
|
||||
var defaultContext = AggregateFormulaContext.builder().build();
|
||||
for (String aggFunction : List.of("count", "max", "min", "avg", "sum", "group_concat", "string_agg", "listagg")) {
|
||||
assertThat(defaultContext.isAggregate(aggFunction)).isTrue();
|
||||
}
|
||||
for (String c : List.of("concat", "group_concat", "string_agg", "listagg")) {
|
||||
assertThat(defaultContext.isConcat(c)).isTrue();
|
||||
}
|
||||
for (String c : List.of("count")) {
|
||||
assertThat(defaultContext.isCount(c)).isTrue();
|
||||
}
|
||||
|
||||
assertThat(defaultContext.isConcat("junk")).isFalse();
|
||||
assertThat(defaultContext.isCount("junk")).isFalse();
|
||||
assertThat(defaultContext.isAggregate("junk")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideAggregateFunctions() {
|
||||
AggregateFormulaContext mySum = AggregateFormulaContext.builder()
|
||||
.aggregateFunctions(Set.of("my_sum"))
|
||||
.build();
|
||||
|
||||
assertThat(mySum.isAggregate("my_sum")).isTrue();
|
||||
assertThat(mySum.isAggregate("avg")).isFalse();
|
||||
assertThat(mySum.isCount("count")).isTrue();
|
||||
assertThat(mySum.isConcat("group_concat")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideConcatFunctions() {
|
||||
AggregateFormulaContext myConcat = AggregateFormulaContext.builder()
|
||||
.concatFunctions(Set.of("my_concat"))
|
||||
.build();
|
||||
|
||||
assertThat(myConcat.isAggregate("avg")).isTrue();
|
||||
assertThat(myConcat.isCount("count")).isTrue();
|
||||
assertThat(myConcat.isConcat("group_concat")).isFalse();
|
||||
assertThat(myConcat.isConcat("my_concat")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void overrideCountFunctions() {
|
||||
AggregateFormulaContext myCount = AggregateFormulaContext.builder()
|
||||
.countFunctions(Set.of("my_count"))
|
||||
.build();
|
||||
|
||||
assertThat(myCount.isAggregate("avg")).isTrue();
|
||||
assertThat(myCount.isCount("count")).isFalse();
|
||||
assertThat(myCount.isCount("my_count")).isTrue();
|
||||
assertThat(myCount.isConcat("group_concat")).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -77,6 +77,9 @@ class DatabaseConfigTest {
|
||||
props.setProperty("readOnlyDatabase", "true");
|
||||
props.setProperty("lengthCheck", "ON");
|
||||
props.setProperty("includeLabelInSql", "false");
|
||||
props.setProperty("lazyLoadBatchSize", "50");
|
||||
props.setProperty("queryBatchSize", "60");
|
||||
props.setProperty("shutdownHook", "false");
|
||||
|
||||
props.setProperty("queryPlan.enable", "true");
|
||||
props.setProperty("queryPlan.thresholdMicros", "10000");
|
||||
@@ -84,6 +87,7 @@ class DatabaseConfigTest {
|
||||
props.setProperty("queryPlan.capturePeriodSecs", "42");
|
||||
props.setProperty("queryPlan.captureMaxTimeMillis", "560");
|
||||
props.setProperty("queryPlan.captureMaxCount", "7");
|
||||
props.setProperty("queryPlan.explain", "explain (verbose)");
|
||||
|
||||
config.loadFromProperties(props);
|
||||
|
||||
@@ -97,8 +101,11 @@ class DatabaseConfigTest {
|
||||
assertTrue(settings.isLoadModuleInfo());
|
||||
assertTrue(settings.skipDataSourceCheck());
|
||||
assertTrue(settings.readOnlyDatabase());
|
||||
assertFalse(settings.shutdownHook());
|
||||
assertFalse(settings.isIncludeLabelInSql());
|
||||
assertThat(settings.getLengthCheck()).isEqualTo(LengthCheck.ON);
|
||||
assertThat(settings.getLazyLoadBatchSize()).isEqualTo(50);
|
||||
assertThat(settings.getQueryBatchSize()).isEqualTo(60);
|
||||
|
||||
assertTrue(settings.isIdGeneratorAutomatic());
|
||||
assertFalse(settings.getPlatformConfig().isCaseSensitiveCollation());
|
||||
@@ -129,15 +136,18 @@ class DatabaseConfigTest {
|
||||
assertEquals(42, settings.getQueryPlanCapturePeriodSecs());
|
||||
assertEquals(560, settings.getQueryPlanCaptureMaxTimeMillis());
|
||||
assertEquals(7, settings.getQueryPlanCaptureMaxCount());
|
||||
assertEquals("explain (verbose)", settings.getQueryPlanExplain());
|
||||
|
||||
assertThat(settings.getMappingLocations()).containsExactly("classpath:/foo","bar");
|
||||
|
||||
config.persistBatch(PersistBatch.NONE)
|
||||
.persistBatchOnCascade(PersistBatch.NONE)
|
||||
.lengthCheck(LengthCheck.ON)
|
||||
.lengthCheck(LengthCheck.UTF8);
|
||||
.lengthCheck(LengthCheck.UTF8)
|
||||
.queryPlanExplain("explain (buffers)");
|
||||
|
||||
|
||||
assertThat(config.settings().getQueryPlanExplain()).isEqualTo("explain (buffers)");
|
||||
Properties props1 = new Properties();
|
||||
props1.setProperty("ebean.persistBatch", "ALL");
|
||||
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
|
||||
@@ -164,6 +174,7 @@ class DatabaseConfigTest {
|
||||
DatabaseBuilder.Settings config = new DatabaseConfig().settings();
|
||||
assertTrue(config.isIdGeneratorAutomatic());
|
||||
assertTrue(config.isDefaultServer());
|
||||
assertTrue(config.shutdownHook());
|
||||
assertFalse(config.isAutoPersistUpdates());
|
||||
assertFalse(config.skipDataSourceCheck());
|
||||
|
||||
@@ -175,6 +186,8 @@ class DatabaseConfigTest {
|
||||
assertTrue(config.getPlatformConfig().isCaseSensitiveCollation());
|
||||
assertTrue(config.isAutoLoadModuleInfo());
|
||||
assertTrue(config.isLoadModuleInfo());
|
||||
assertThat(config.getLazyLoadBatchSize()).isEqualTo(100);
|
||||
assertThat(config.getQueryBatchSize()).isEqualTo(100);
|
||||
|
||||
assertFalse(config.isQueryPlanEnable());
|
||||
assertEquals(Long.MAX_VALUE, config.getQueryPlanThresholdMicros());
|
||||
@@ -182,9 +195,12 @@ class DatabaseConfigTest {
|
||||
assertEquals(600, config.getQueryPlanCapturePeriodSecs());
|
||||
assertEquals(10000L, config.getQueryPlanCaptureMaxTimeMillis());
|
||||
assertEquals(10, config.getQueryPlanCaptureMaxCount());
|
||||
assertThat(config.getQueryPlanExplain()).isNull();
|
||||
assertThat(config.getLengthCheck()).isEqualTo(LengthCheck.OFF);
|
||||
assertTrue(config.isIncludeLabelInSql());
|
||||
|
||||
config.shutdownHook(false);
|
||||
assertFalse(config.shutdownHook());
|
||||
config.setLoadModuleInfo(false);
|
||||
assertFalse(config.isAutoLoadModuleInfo());
|
||||
assertFalse(config.isLoadModuleInfo());
|
||||
@@ -192,6 +208,10 @@ class DatabaseConfigTest {
|
||||
assertTrue(config.isAutoPersistUpdates());
|
||||
config.setSkipDataSourceCheck(true);
|
||||
assertTrue(config.skipDataSourceCheck());
|
||||
config.lazyLoadBatchSize(20);
|
||||
assertThat(config.getLazyLoadBatchSize()).isEqualTo(20);
|
||||
config.queryBatchSize(30);
|
||||
assertThat(config.getQueryBatchSize()).isEqualTo(30);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class QueryPlanInitTest {
|
||||
|
||||
@Test
|
||||
void initialQueryPlanInit() {
|
||||
var init = new QueryPlanInit();
|
||||
assertThat(init.isEmpty()).isTrue();
|
||||
assertThat(init.isAll()).isFalse();
|
||||
assertThat(init.thresholdMicros()).isEqualTo(0L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void add_all() {
|
||||
var init = new QueryPlanInit();
|
||||
init.add("all", 57L);
|
||||
|
||||
assertThat(init.isEmpty()).isFalse();
|
||||
assertThat(init.isAll()).isTrue();
|
||||
assertThat(init.thresholdMicros()).isEqualTo(57L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addWithThresholds() {
|
||||
var init = new QueryPlanInit();
|
||||
init.thresholdMicros(1000);
|
||||
init.add("xOne", 0);
|
||||
init.add("xTwo", 2000L);
|
||||
|
||||
assertThat(init.isEmpty()).isFalse();
|
||||
assertThat(init.isAll()).isFalse();
|
||||
assertThat(init.includeHash("xJunk")).isFalse();
|
||||
assertThat(init.includeHash("xOne")).isTrue();
|
||||
assertThat(init.includeHash("xTwo")).isTrue();
|
||||
assertThat(init.thresholdMicros("xJunk")).isEqualTo(1000L);
|
||||
assertThat(init.thresholdMicros("xOne")).isEqualTo(1000L);
|
||||
assertThat(init.thresholdMicros("xTwo")).isEqualTo(2000L);
|
||||
}
|
||||
}
|
||||
+38
-26
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -89,25 +89,25 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -125,13 +125,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -155,37 +155,37 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-spring-txn</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- platforms -->
|
||||
@@ -193,79 +193,91 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-clickhouse</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-db2</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-h2</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-hana</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mariadb</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mysql</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-nuodb</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-oracle</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlite</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlserver</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<name>ebean-core-json</name>
|
||||
<scm>
|
||||
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
|
||||
<tag>HEAD</tag>
|
||||
</scm>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Jackson core used internally by Ebean -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
+5
-10
@@ -8,11 +8,7 @@ import io.ebean.service.SpiJsonService;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Utility that converts between JSON content and simple java Maps/Lists.
|
||||
@@ -52,8 +48,8 @@ public final class DJsonService implements SpiJsonService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map additionally specifying if the returned map should
|
||||
* be modify aware meaning that it can detect when it has been modified.
|
||||
* Parse the json and return as a Map additionally specifying if the returned map should be modify
|
||||
* aware meaning that it can detect when it has been modified.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
@@ -94,9 +90,8 @@ public final class DJsonService implements SpiJsonService {
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser and a starting token.
|
||||
* <p>
|
||||
* Used when the first token is checked to see if the value is null prior to calling this.
|
||||
* </p>
|
||||
*
|
||||
* <p>Used when the first token is checked to see if the value is null prior to calling this.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
+20
-32
@@ -8,15 +8,23 @@ import io.ebean.ModifyAwareType;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.*;
|
||||
|
||||
final class EJsonReader {
|
||||
|
||||
static final JsonFactory json = new JsonFactory();
|
||||
private final JsonParser parser;
|
||||
private final boolean modifyAware;
|
||||
private final ModifyAwareFlag modifyAwareOwner;
|
||||
private int depth;
|
||||
private Stack stack;
|
||||
private Context currentContext;
|
||||
|
||||
EJsonReader(JsonParser parser, boolean modifyAware) {
|
||||
this.parser = parser;
|
||||
this.modifyAware = modifyAware;
|
||||
this.modifyAwareOwner = modifyAware ? new ModifyAwareFlag() : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
@@ -102,24 +110,6 @@ final class EJsonReader {
|
||||
return new EJsonReader(parser, modifyAware).parseJson(token);
|
||||
}
|
||||
|
||||
private final JsonParser parser;
|
||||
|
||||
private final boolean modifyAware;
|
||||
|
||||
private final ModifyAwareFlag modifyAwareOwner;
|
||||
|
||||
private int depth;
|
||||
|
||||
private Stack stack;
|
||||
|
||||
private Context currentContext;
|
||||
|
||||
EJsonReader(JsonParser parser, boolean modifyAware) {
|
||||
this.parser = parser;
|
||||
this.modifyAware = modifyAware;
|
||||
this.modifyAwareOwner = (modifyAware) ? new ModifyAwareFlag() : null;
|
||||
}
|
||||
|
||||
private void startArray() {
|
||||
depth++;
|
||||
stack.push(currentContext);
|
||||
@@ -197,7 +187,6 @@ final class EJsonReader {
|
||||
*/
|
||||
private void processJsonToken(JsonToken token) throws IOException {
|
||||
switch (token) {
|
||||
|
||||
case START_ARRAY:
|
||||
startArray();
|
||||
break;
|
||||
@@ -273,17 +262,17 @@ final class EJsonReader {
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Context {
|
||||
private abstract static class Context {
|
||||
Context next;
|
||||
|
||||
abstract void popContext(Context temp);
|
||||
|
||||
abstract Object getValue();
|
||||
|
||||
abstract void setKey(String key);
|
||||
|
||||
abstract void setValue(Object value);
|
||||
|
||||
abstract void setKey(String key);
|
||||
|
||||
abstract void setValueNull();
|
||||
}
|
||||
|
||||
@@ -312,13 +301,13 @@ final class EJsonReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
void setKey(String key) {
|
||||
this.key = key;
|
||||
void setValue(Object value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setValue(Object value) {
|
||||
map.put(key, value);
|
||||
void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -364,5 +353,4 @@ final class EJsonReader {
|
||||
// not expected
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+5
-7
@@ -20,6 +20,11 @@ final class EJsonWriter {
|
||||
* Base jsonFactory implementation used when it is not passed in.
|
||||
*/
|
||||
static final JsonFactory jsonFactory = new JsonFactory();
|
||||
private final JsonGenerator jsonGenerator;
|
||||
|
||||
private EJsonWriter(JsonGenerator jsonGenerator) {
|
||||
this.jsonGenerator = jsonGenerator;
|
||||
}
|
||||
|
||||
static String write(Object object) throws IOException {
|
||||
StringWriter writer = new StringWriter(200);
|
||||
@@ -42,12 +47,6 @@ final class EJsonWriter {
|
||||
new EJsonWriter(jsonGenerator).writeCollection(null, collection);
|
||||
}
|
||||
|
||||
private final JsonGenerator jsonGenerator;
|
||||
|
||||
private EJsonWriter(JsonGenerator jsonGenerator) {
|
||||
this.jsonGenerator = jsonGenerator;
|
||||
}
|
||||
|
||||
private void writeJson(Object object) {
|
||||
writeJson(null, object);
|
||||
}
|
||||
@@ -210,5 +209,4 @@ final class EJsonWriter {
|
||||
}
|
||||
jsonGenerator.writeEndObject();
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -22,5 +22,4 @@ public final class ModifyAwareFlag implements ModifyAwareType, Serializable {
|
||||
public void setMarkedDirty(boolean markedDirty) {
|
||||
this.markedDirty = markedDirty;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-4
@@ -14,9 +14,8 @@ public final class ModifyAwareIterator<E> implements Iterator<E> {
|
||||
|
||||
/**
|
||||
* Create with an Owner and the underlying Iterator this wraps.
|
||||
* <p>
|
||||
* The owner is notified of the removals.
|
||||
* </p>
|
||||
*
|
||||
* <p>The owner is notified of the removals.
|
||||
*/
|
||||
public ModifyAwareIterator(ModifyAwareType owner, Iterator<E> it) {
|
||||
this.owner = owner;
|
||||
@@ -38,5 +37,4 @@ public final class ModifyAwareIterator<E> implements Iterator<E> {
|
||||
owner.setMarkedDirty(true);
|
||||
it.remove();
|
||||
}
|
||||
|
||||
}
|
||||
+2
-4
@@ -34,11 +34,11 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o instanceof ModifyAwareMap) {
|
||||
ModifyAwareMap<?,?> that = (ModifyAwareMap<?,?>) o;
|
||||
ModifyAwareMap<?, ?> that = (ModifyAwareMap<?, ?>) o;
|
||||
return Objects.equals(map, that.map);
|
||||
}
|
||||
if (!(o instanceof Map)) return false;
|
||||
Map<?,?> that = (Map<?,?>) o;
|
||||
Map<?, ?> that = (Map<?, ?>) o;
|
||||
return Objects.equals(map, that);
|
||||
}
|
||||
|
||||
@@ -112,7 +112,6 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
|
||||
map.putAll(m);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
if (!map.isEmpty()) {
|
||||
@@ -135,5 +134,4 @@ public final class ModifyAwareMap<K, V> implements Map<K, V>, ModifyAwareType, S
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
return new ModifyAwareSet<>(this, map.entrySet());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
module io.ebean.core.json {
|
||||
|
||||
requires io.ebean.api;
|
||||
|
||||
requires transitive com.fasterxml.jackson.core;
|
||||
exports io.ebeaninternal.json to io.ebean.test, io.ebean.core;
|
||||
|
||||
provides io.ebean.service.BootstrapService with io.ebeaninternal.json.DJsonService;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.ebeaninternal.json.DJsonService
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
+12
-6
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -22,7 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -46,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -159,21 +165,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>14.10.0</version>
|
||||
<version>16.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -13,10 +14,23 @@ public final class BindValuesKey {
|
||||
|
||||
private final List<Object> values = new ArrayList<>();
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
|
||||
public BindValuesKey(SpiEbeanServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a bind value.
|
||||
*/
|
||||
public BindValuesKey add(Object value) {
|
||||
if (value instanceof EntityBean) {
|
||||
// only interested in id to keep the memory footprint low
|
||||
Object id = server.beanId(value);
|
||||
if (id != null) {
|
||||
value = id;
|
||||
}
|
||||
}
|
||||
values.add(value);
|
||||
return this;
|
||||
}
|
||||
@@ -31,5 +45,4 @@ public final class BindValuesKey {
|
||||
return values.hashCode();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebean.config.AggregateFormulaContext;
|
||||
import io.ebeaninternal.server.query.STreeProperty;
|
||||
|
||||
public interface FormulaBuilder {
|
||||
|
||||
STreeProperty create(AggregateFormulaContext context, String formula, String path);
|
||||
|
||||
}
|
||||
@@ -41,7 +41,7 @@ final class NaturalKeyEntryBasic implements NaturalKeyEntry {
|
||||
* Create when query uses an IN PAIRS clause.
|
||||
*/
|
||||
NaturalKeyEntryBasic(BeanNaturalKey naturalKey, List<NaturalKeyEq> eqList,
|
||||
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
|
||||
String inMapProperty0, String inMapProperty1, Pairs.Entry pair) {
|
||||
load(eqList);
|
||||
map.put(inMapProperty0, pair.getA());
|
||||
map.put(inMapProperty1, pair.getB());
|
||||
@@ -49,6 +49,14 @@ final class NaturalKeyEntryBasic implements NaturalKeyEntry {
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
NaturalKeyEntryBasic(BeanNaturalKey naturalKey, List<NaturalKeyEq> eqList,
|
||||
Map<String, Object> properties, Object[] naturalKeyValue) {
|
||||
load(eqList);
|
||||
map.putAll(properties);
|
||||
this.inValue = naturalKeyValue;
|
||||
this.key = calculateKey(naturalKey);
|
||||
}
|
||||
|
||||
private void load(List<NaturalKeyEq> eqList) {
|
||||
if (eqList != null) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
|
||||
@@ -3,10 +3,7 @@ package io.ebeaninternal.api;
|
||||
import io.ebean.Pairs;
|
||||
import io.ebeaninternal.server.deploy.BeanNaturalKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Collects the data for processing the natural key cache processing.
|
||||
@@ -19,8 +16,9 @@ public final class NaturalKeyQueryData<T> {
|
||||
*/
|
||||
private boolean hasIn;
|
||||
// IN Pairs clause - only one allowed
|
||||
private String inProperty0, inProperty1;
|
||||
private List<Pairs.Entry> inPairs;
|
||||
private String[] properties;
|
||||
private List<Object[]> inTuples;
|
||||
// IN clause - only one allowed
|
||||
private List<Object> inValues;
|
||||
private String inProperty;
|
||||
@@ -47,14 +45,39 @@ public final class NaturalKeyQueryData<T> {
|
||||
}
|
||||
if (matchProperty(property0) && matchProperty(property1)) {
|
||||
this.hasIn = true;
|
||||
this.inProperty0 = property0;
|
||||
this.inProperty1 = property1;
|
||||
this.properties = new String[]{property0, property1};
|
||||
this.inPairs = new ArrayList<>(inPairs); // will be modified
|
||||
return this.inPairs;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for In Tuples expression. We only allow one IN clause.
|
||||
*/
|
||||
public List<Object[]> matchInTuples(String[] properties, List<Object[]> inTuples) {
|
||||
if (hasIn) {
|
||||
// only 1 IN allowed (to project naturalIds)
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean matchAll = true;
|
||||
|
||||
for (String property : properties) {
|
||||
if (!matchProperty(property)) {
|
||||
matchAll = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchAll) {
|
||||
this.hasIn = true;
|
||||
this.properties = Arrays.copyOf(properties, properties.length);
|
||||
this.inTuples = new ArrayList<>(inTuples);
|
||||
return this.inTuples;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Match for IN expression. We only allow one IN clause.
|
||||
*/
|
||||
@@ -100,6 +123,8 @@ public final class NaturalKeyQueryData<T> {
|
||||
addInValues();
|
||||
} else if (inPairs != null) {
|
||||
addInPairs();
|
||||
} else if (inTuples != null) {
|
||||
addInTuples();
|
||||
} else {
|
||||
addEqualsKey();
|
||||
}
|
||||
@@ -110,7 +135,17 @@ public final class NaturalKeyQueryData<T> {
|
||||
// a findList() with an IN Map clause so we project
|
||||
// for every IN value a natural key combination
|
||||
for (Pairs.Entry entry : inPairs) {
|
||||
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, inProperty0, inProperty1, entry));
|
||||
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, properties[0], properties[1], entry));
|
||||
}
|
||||
}
|
||||
|
||||
private void addInTuples() {
|
||||
for (Object[] inTuple : inTuples) {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
for (int i = 0; i < inTuple.length; i++) {
|
||||
map.put(properties[i], inTuple[i]);
|
||||
}
|
||||
set.add(new NaturalKeyEntryBasic(naturalKey, eqList, map, inTuple));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,11 +187,8 @@ public final class NaturalKeyQueryData<T> {
|
||||
if (inProperty != null) {
|
||||
exprProps.add(inProperty);
|
||||
}
|
||||
if (inProperty0 != null) {
|
||||
exprProps.add(inProperty0);
|
||||
}
|
||||
if (inProperty1 != null) {
|
||||
exprProps.add(inProperty1);
|
||||
if (properties != null) {
|
||||
exprProps.addAll(Arrays.asList(properties));
|
||||
}
|
||||
if (eqList != null) {
|
||||
for (NaturalKeyEq eq : eqList) {
|
||||
@@ -173,6 +205,7 @@ public final class NaturalKeyQueryData<T> {
|
||||
int defined = (inValues == null) ? 0 : 1;
|
||||
defined += (inPairs == null) ? 0 : 2;
|
||||
defined += (eqList == null) ? 0 : eqList.size();
|
||||
defined += (inTuples == null) ? 0 : properties.length;
|
||||
return defined == naturalKey.length();
|
||||
}
|
||||
|
||||
@@ -206,6 +239,9 @@ public final class NaturalKeyQueryData<T> {
|
||||
} else if (inPairs != null) {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
inPairs.remove(inValue);
|
||||
} else if (inTuples != null) {
|
||||
//noinspection SuspiciousMethodCalls
|
||||
inTuples.remove(inValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,4 +14,9 @@ public interface SpiBeanType {
|
||||
* or removals from the collection.
|
||||
*/
|
||||
boolean isToManyDirty(EntityBean bean);
|
||||
|
||||
/**
|
||||
* Return the FormulaBuilder for this type.
|
||||
*/
|
||||
FormulaBuilder formulaBuilder();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import io.ebeaninternal.server.query.STreeProperty;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import io.ebean.*;
|
||||
import io.ebean.bean.BeanCollectionLoader;
|
||||
@@ -348,6 +349,8 @@ public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {
|
||||
|
||||
<T> FutureList<T> findFutureList(SpiQuery<T> query);
|
||||
|
||||
<K, T> FutureMap<K, T> findFutureMap(SpiQuery<T> query);
|
||||
|
||||
<T> PagedList<T> findPagedList(SpiQuery<T> query);
|
||||
|
||||
<T> Set<T> findSet(SpiQuery<T> query);
|
||||
@@ -375,4 +378,6 @@ public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {
|
||||
|
||||
@Nullable
|
||||
SqlRow findOne(SpiSqlQuery query);
|
||||
|
||||
<T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,4 @@ public interface SpiQueryManyJoin {
|
||||
*/
|
||||
String fetchOrderBy();
|
||||
|
||||
/**
|
||||
* Wrap the filter many expression with a condition allowing lEFT JOIN null matching row.
|
||||
*/
|
||||
String idNullOr(String filterManyExpression);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Extends Transaction with additional API required on server.
|
||||
@@ -64,12 +65,12 @@ public interface SpiTransaction extends Transaction {
|
||||
* <p>
|
||||
* This is to handle bi-directional relationships where both sides Cascade.
|
||||
*/
|
||||
void registerDeleteBean(Integer hash);
|
||||
void registerDeleteBean(Class<?> type, Object id);
|
||||
|
||||
/**
|
||||
* Return true if this is a bean that has already been saved/deleted.
|
||||
*/
|
||||
boolean isRegisteredDeleteBean(Integer hash);
|
||||
boolean isRegisteredDeleteBean(Class<?> type, Object id);
|
||||
|
||||
/**
|
||||
* Unregister the persisted beans. Expected after persisting top level beans
|
||||
@@ -95,7 +96,7 @@ public interface SpiTransaction extends Transaction {
|
||||
/**
|
||||
* Return the start timestamp for the transaction (JVM side).
|
||||
*/
|
||||
long startNanoTime();
|
||||
Instant startTime();
|
||||
|
||||
/**
|
||||
* Return true if this transaction has updateAllLoadedProperties set.
|
||||
@@ -337,11 +338,6 @@ public interface SpiTransaction extends Transaction {
|
||||
*/
|
||||
boolean isNestedUseSavepoint();
|
||||
|
||||
/**
|
||||
* Return true if explicitly set to skip cache (ignores skipOnWrite).
|
||||
*/
|
||||
boolean isSkipCacheExplicit();
|
||||
|
||||
/**
|
||||
* Fire pre commit processing/listeners.
|
||||
*/
|
||||
@@ -361,4 +357,14 @@ public interface SpiTransaction extends Transaction {
|
||||
* Set the transaction to be inactive via external transaction manager.
|
||||
*/
|
||||
void deactivateExternal();
|
||||
|
||||
/**
|
||||
* Set autocommit to false for a findIterate query.
|
||||
* <p>
|
||||
* This is done for specific platforms that need it, in order to make
|
||||
* use cursors to stream a large or unbounded query result to the client.
|
||||
*/
|
||||
default void setAutoCommitOnFindIterate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.ebeanservice.docstore.api.DocStoreTransaction;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
* Proxy for an underlying SpiTransaction (most of the API).
|
||||
@@ -28,8 +29,8 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long startNanoTime() {
|
||||
return transaction.startNanoTime();
|
||||
public Instant startTime() {
|
||||
return transaction.startTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,11 +169,6 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
transaction.setSkipCache(skipCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipCacheExplicit() {
|
||||
return transaction.isSkipCacheExplicit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSkipCache() {
|
||||
return transaction.isSkipCache();
|
||||
@@ -194,13 +190,13 @@ public abstract class SpiTransactionProxy implements SpiTransaction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDeleteBean(Integer hash) {
|
||||
transaction.registerDeleteBean(hash);
|
||||
public void registerDeleteBean(Class<?> type, Object id) {
|
||||
transaction.registerDeleteBean(type, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegisteredDeleteBean(Integer hash) {
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
public boolean isRegisteredDeleteBean(Class<?> type, Object id) {
|
||||
return transaction.isRegisteredDeleteBean(type, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ public class DefaultServerQueryCache extends DefaultServerCache {
|
||||
|
||||
@Override
|
||||
protected Object unwrapEntry(CacheEntry entry) {
|
||||
return ((QueryCacheEntry) entry.getValue()).getValue();
|
||||
return ((QueryCacheEntry) entry.getValue()).value();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.CancelableQuery;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.util.JdbcClose;
|
||||
import io.ebeaninternal.api.*;
|
||||
import io.ebeaninternal.server.persist.Binder;
|
||||
@@ -98,6 +97,13 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery {
|
||||
public void setDefaultFetchBuffer(int fetchSize) {
|
||||
query.setDefaultFetchBuffer(fetchSize);
|
||||
}
|
||||
|
||||
public void setAutoCommitOnFindIterate() {
|
||||
if (createdTransaction) {
|
||||
transaction.setAutoCommitOnFindIterate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the underlying resources.
|
||||
*/
|
||||
|
||||
@@ -113,6 +113,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
private final long slowQueryMicros;
|
||||
private final SlowQueryListener slowQueryListener;
|
||||
private final boolean disableL2Cache;
|
||||
private final AggregateFormulaContext formulaContext;
|
||||
private boolean shutdown;
|
||||
|
||||
/**
|
||||
@@ -128,6 +129,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.backgroundExecutor = config.getBackgroundExecutor();
|
||||
this.extraMetrics = config.getExtraMetrics();
|
||||
this.serverName = this.config.getName();
|
||||
this.formulaContext = config.getConfig().aggregateFormulaContext();
|
||||
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
|
||||
this.cqueryEngine = config.getCQueryEngine();
|
||||
this.expressionFactory = config.getExpressionFactory();
|
||||
@@ -162,8 +164,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
this.scriptRunner = new DScriptRunner(this);
|
||||
|
||||
configureServerPlugins();
|
||||
// Register with the JVM Shutdown hook
|
||||
ShutdownManager.registerDatabase(this);
|
||||
if (this.config.shutdownHook() && "true".equalsIgnoreCase(System.getProperty("ebean.registerShutdownHook", "true"))) {
|
||||
// register with the JVM Shutdown hook
|
||||
ShutdownManager.registerDatabase(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,6 +282,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DataSource readOnlyDataSource() {
|
||||
return transactionManager.readOnlyDataSource();
|
||||
}
|
||||
@@ -925,6 +930,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
return findId(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> STreeProperty createFormulaProperty(SpiBeanType desc, String formula, String path) {
|
||||
return desc.formulaBuilder().create(formulaContext, formula, path);
|
||||
}
|
||||
|
||||
<T> SpiOrmQueryRequest<T> createQueryRequest(Type type, SpiQuery<T> query) {
|
||||
SpiOrmQueryRequest<T> request = buildQueryRequest(type, query);
|
||||
request.prepareQuery();
|
||||
@@ -1249,66 +1259,55 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
}
|
||||
}
|
||||
|
||||
private <T> SpiQuery<T> configureForFuture(SpiQuery<T> query) {
|
||||
query.usingFuture();
|
||||
if (query.transaction() == null) {
|
||||
// use a current transaction if active
|
||||
query.usingTransaction(currentServerTransaction());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> FutureRowCount<T> findFutureCount(SpiQuery<T> query) {
|
||||
SpiQuery<T> copy = query.copy();
|
||||
copy.usingFuture();
|
||||
boolean createdTransaction = false;
|
||||
SpiTransaction transaction = query.transaction();
|
||||
if (transaction == null) {
|
||||
transaction = currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
transaction = (SpiTransaction) createTransaction();
|
||||
createdTransaction = true;
|
||||
}
|
||||
copy.usingTransaction(transaction);
|
||||
}
|
||||
var queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy, createdTransaction));
|
||||
SpiQuery<T> copy = configureForFuture(query.copy());
|
||||
var queryFuture = new QueryFutureRowCount<>(new CallableQueryCount<>(this, copy));
|
||||
backgroundExecutor.execute(queryFuture.futureTask());
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> FutureIds<T> findFutureIds(SpiQuery<T> query) {
|
||||
SpiQuery<T> copy = query.copy();
|
||||
copy.usingFuture();
|
||||
boolean createdTransaction = false;
|
||||
SpiTransaction transaction = query.transaction();
|
||||
if (transaction == null) {
|
||||
transaction = currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
transaction = (SpiTransaction) createTransaction();
|
||||
createdTransaction = true;
|
||||
}
|
||||
copy.usingTransaction(transaction);
|
||||
}
|
||||
QueryFutureIds<T> queryFuture = new QueryFutureIds<>(new CallableQueryIds<>(this, copy, createdTransaction));
|
||||
SpiQuery<T> copy = configureForFuture(query.copy());
|
||||
final var queryFuture = new QueryFutureIds<T>(new CallableQueryIds<>(this, copy));
|
||||
backgroundExecutor.execute(queryFuture.futureTask());
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> FutureList<T> findFutureList(SpiQuery<T> query) {
|
||||
SpiQuery<T> spiQuery = query.copy();
|
||||
spiQuery.usingFuture();
|
||||
// FutureList query always run in it's own persistence content
|
||||
SpiQuery<T> spiQuery = configureForFuture(query.copy());
|
||||
// FutureList query always run in its own persistence content
|
||||
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
|
||||
if (!spiQuery.isDisableReadAudit()) {
|
||||
BeanDescriptor<T> desc = descriptorManager.descriptor(spiQuery.getBeanType());
|
||||
desc.readAuditFutureList(spiQuery);
|
||||
}
|
||||
// Create a new transaction solely to execute the findList() at some future time
|
||||
boolean createdTransaction = false;
|
||||
SpiTransaction transaction = query.transaction();
|
||||
if (transaction == null) {
|
||||
transaction = currentServerTransaction();
|
||||
if (transaction == null) {
|
||||
transaction = (SpiTransaction) createTransaction();
|
||||
createdTransaction = true;
|
||||
}
|
||||
spiQuery.usingTransaction(transaction);
|
||||
final var queryFuture = new QueryFutureList<T>(new CallableQueryList<>(this, spiQuery));
|
||||
backgroundExecutor.execute(queryFuture.futureTask());
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, T> FutureMap<K, T> findFutureMap(SpiQuery<T> query) {
|
||||
SpiQuery<T> spiQuery = configureForFuture(query.copy());
|
||||
// FutureMap query always run in it's own persistence content
|
||||
spiQuery.setPersistenceContext(new DefaultPersistenceContext());
|
||||
if (!spiQuery.isDisableReadAudit()) {
|
||||
BeanDescriptor<T> desc = descriptorManager.descriptor(spiQuery.getBeanType());
|
||||
desc.readAuditFutureList(spiQuery);
|
||||
}
|
||||
QueryFutureList<T> queryFuture = new QueryFutureList<>(new CallableQueryList<>(this, spiQuery, createdTransaction));
|
||||
final var queryFuture = new QueryFutureMap<K, T>(new CallableQueryMap<>(this, spiQuery));
|
||||
backgroundExecutor.execute(queryFuture.futureTask());
|
||||
return queryFuture;
|
||||
}
|
||||
|
||||
@@ -301,12 +301,12 @@ public final class InternalConfiguration {
|
||||
}
|
||||
|
||||
DtoQueryEngine createDtoQueryEngine() {
|
||||
return new DtoQueryEngine(binder, config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList());
|
||||
return new DtoQueryEngine(binder, config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList(), databasePlatform.autoCommitFalseOnFindIterate());
|
||||
}
|
||||
|
||||
RelationalQueryEngine createRelationalQueryEngine() {
|
||||
return new DefaultRelationalQueryEngine(binder, config.getDatabaseBooleanTrue(), config.getPlatformConfig().getDbUuid().useBinaryOptimized(),
|
||||
config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList());
|
||||
config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList(), databasePlatform.autoCommitFalseOnFindIterate());
|
||||
}
|
||||
|
||||
OrmQueryEngine createOrmQueryEngine() {
|
||||
@@ -585,27 +585,32 @@ public final class InternalConfiguration {
|
||||
return QueryPlanManager.NOOP;
|
||||
}
|
||||
long threshold = config.getQueryPlanThresholdMicros();
|
||||
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics);
|
||||
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.platform(), config), extraMetrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logger to log query plans for the given platform.
|
||||
*/
|
||||
QueryPlanLogger queryPlanLogger(Platform platform) {
|
||||
QueryPlanLogger queryPlanLogger(Platform platform, DatabaseBuilder.Settings config) {
|
||||
switch (platform.base()) {
|
||||
case SQLSERVER:
|
||||
return new QueryPlanLoggerSqlServer();
|
||||
case ORACLE:
|
||||
return new QueryPlanLoggerOracle();
|
||||
case POSTGRES:
|
||||
return new QueryPlanLoggerExplain("explain (analyze, buffers) ");
|
||||
return new QueryPlanLoggerExplain(explain(config, "explain (analyze, costs, verbose, buffers) "));
|
||||
case YUGABYTE:
|
||||
return new QueryPlanLoggerExplain("explain (analyze, buffers, dist) ");
|
||||
return new QueryPlanLoggerExplain(explain(config,"explain (analyze, buffers, dist) "));
|
||||
default:
|
||||
return new QueryPlanLoggerExplain("explain ");
|
||||
return new QueryPlanLoggerExplain(explain(config,"explain "));
|
||||
}
|
||||
}
|
||||
|
||||
private static String explain(DatabaseBuilder.Settings config, String defaultExplain) {
|
||||
String explain = config.getQueryPlanExplain();
|
||||
return explain == null ? defaultExplain : explain + ' ';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the DDL generator.
|
||||
*/
|
||||
|
||||
@@ -497,10 +497,6 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
return cacheKey != null && query.queryCacheMode().isPut();
|
||||
}
|
||||
|
||||
public boolean isBeanCachePutMany() {
|
||||
return !transaction.isSkipCacheExplicit() && query.isBeanCachePut();
|
||||
}
|
||||
|
||||
public boolean isBeanCachePut() {
|
||||
return !transaction.isSkipCache() && query.isBeanCachePut();
|
||||
}
|
||||
@@ -665,7 +661,7 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
}
|
||||
|
||||
public void putToQueryCache(Object result) {
|
||||
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startNanoTime()));
|
||||
beanDescriptor.queryCachePut(cacheKey, new QueryCacheEntry(result, dependentTables, transaction.startTime()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -786,4 +782,10 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
|
||||
beanDescriptor.freeze(bean);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAutoCommitOnFindIterate() {
|
||||
if (createdTransaction) {
|
||||
transaction.setAutoCommitOnFindIterate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,6 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
* The unique id used for logging summary.
|
||||
*/
|
||||
private Object idValue;
|
||||
/**
|
||||
* Hash value used to handle cascade delete both ways in a relationship.
|
||||
*/
|
||||
private Integer beanHash;
|
||||
private boolean statelessUpdate;
|
||||
private boolean notifyCache;
|
||||
/**
|
||||
@@ -285,7 +281,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
private void onFailedUpdateUndoGeneratedProperties() {
|
||||
for (BeanProperty prop : beanDescriptor.propertiesGenUpdate()) {
|
||||
Object oldVal = intercept.origValue(prop.propertyIndex());
|
||||
prop.setValue(entityBean, oldVal);
|
||||
if (oldVal != null) {
|
||||
prop.setValue(entityBean, oldVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,34 +551,17 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The hash used to register the bean with the transaction.
|
||||
* <p>
|
||||
* Takes into account the class type and id value.
|
||||
*/
|
||||
private Integer beanHash() {
|
||||
if (beanHash == null) {
|
||||
Object id = beanDescriptor.getId(entityBean);
|
||||
int hc = 92821 * bean.getClass().getName().hashCode();
|
||||
if (id != null) {
|
||||
hc += id.hashCode();
|
||||
}
|
||||
beanHash = hc;
|
||||
}
|
||||
return beanHash;
|
||||
}
|
||||
|
||||
public void registerDeleteBean() {
|
||||
Integer hash = beanHash();
|
||||
transaction.registerDeleteBean(hash);
|
||||
final Object id = beanDescriptor.id(entityBean);
|
||||
transaction.registerDeleteBean(beanDescriptor.type(), id);
|
||||
}
|
||||
|
||||
public boolean isRegisteredForDeleteBean() {
|
||||
if (transaction == null) {
|
||||
return false;
|
||||
} else {
|
||||
Integer hash = beanHash();
|
||||
return transaction.isRegisteredDeleteBean(hash);
|
||||
final Object id = beanDescriptor.id(entityBean);
|
||||
return transaction.isRegisteredDeleteBean(beanDescriptor.type(), id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,7 +790,9 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
|
||||
public void checkRowCount(int rowCount) {
|
||||
if (rowCount != 1 && rowCount != Statement.SUCCESS_NO_INFO) {
|
||||
if (ConcurrencyMode.VERSION == concurrencyMode) {
|
||||
onFailedUpdateUndoGeneratedProperties();
|
||||
if (type == Type.UPDATE) {
|
||||
onFailedUpdateUndoGeneratedProperties();
|
||||
}
|
||||
throw new OptimisticLockException("Data has changed. updated row count " + rowCount, null, bean);
|
||||
} else if (rowCount == 0 && type == Type.UPDATE) {
|
||||
throw new EntityNotFoundException("No rows updated");
|
||||
|
||||
@@ -1471,7 +1471,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list) {
|
||||
for (CQueryPlan queryPlan : queryPlanCache.values()) {
|
||||
if (request.includeHash(queryPlan.hash())) {
|
||||
queryPlan.queryPlanInit(request.thresholdMicros());
|
||||
queryPlan.queryPlanInit(request.thresholdMicros(queryPlan.hash()));
|
||||
list.add(queryPlan.createMeta(null, null));
|
||||
}
|
||||
}
|
||||
@@ -2410,7 +2410,12 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
*/
|
||||
private STreeProperty findSqlTreeFormula(String formula, String path) {
|
||||
String key = formula + "-" + path;
|
||||
return dynamicProperty.computeIfAbsent(key, (fullKey) -> FormulaPropertyPath.create(this, formula, path));
|
||||
return dynamicProperty.computeIfAbsent(key, (fullKey) -> ebeanServer.createFormulaProperty(this, formula, path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormulaBuilder formulaBuilder() {
|
||||
return new DFormulaBuilder(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ final class BeanEmbeddedMetaFactory {
|
||||
}
|
||||
|
||||
private static int dbLength(Column override, BeanProperty source) {
|
||||
return (override != null && (override.length() != 255)) ? override.length() : source.dbLength();
|
||||
return (override != null && (override.length() != 0)) ? override.length() : source.dbLength();
|
||||
}
|
||||
|
||||
private static int dbScale(Column override, BeanProperty source) {
|
||||
|
||||
@@ -36,11 +36,6 @@ public final class BeanFkeyProperty implements ElPropertyValue {
|
||||
return "prefix:" + prefix + " name:" + name + " dbColumn:" + dbColumn + " ph:" + placeHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String filterManyExpression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAggregation() {
|
||||
return false;
|
||||
|
||||
@@ -513,11 +513,6 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
return owningType.isAssignableFrom(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String filterManyExpression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadIgnore(DbReadContext ctx) {
|
||||
ctx.dataReader().incrementPos(1);
|
||||
|
||||
@@ -436,14 +436,15 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
|
||||
}
|
||||
TableJoinColumn[] cols = join.columns();
|
||||
if (!idProp.isEmbedded()) {
|
||||
// simple single scalar id
|
||||
if (cols.length != 1) {
|
||||
CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable());
|
||||
return null;
|
||||
} else {
|
||||
BeanProperty[] idProps = {idProp};
|
||||
return createImportedScalar(owner, cols[0], idProps, others);
|
||||
// simple single scalar id, match on the foreign column, allow extra TableJoinColumn for #3664
|
||||
String matchColumn = idProp.dbColumn();
|
||||
for (TableJoinColumn col : cols) {
|
||||
if (matchColumn.equals(col.getForeignDbColumn())) {
|
||||
return createImportedScalar(owner, col, new BeanProperty[]{idProp}, others);
|
||||
}
|
||||
}
|
||||
CoreLog.log.log(ERROR, "No Imported Id column for {0} in table {1}", idProp, join.getTable());
|
||||
return null;
|
||||
} else {
|
||||
// embedded id
|
||||
BeanPropertyAssocOne<?> embProp = (BeanPropertyAssocOne<?>) idProp;
|
||||
|
||||
@@ -634,11 +634,6 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
return fetchOrderBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String filterManyExpression) {
|
||||
return targetIdBinder.idNullOr(name, filterManyExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the order by for use when lazy loading the associated collection.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.Query;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.ValuePair;
|
||||
@@ -125,15 +124,9 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
|
||||
// no imported or exported information
|
||||
} else if (!oneToOneExported) {
|
||||
importedId = createImportedId(this, targetDescriptor, tableJoin);
|
||||
if (importedId.isScalar()) {
|
||||
// limit JoinColumn mapping to the @Id / primary key
|
||||
TableJoinColumn[] columns = tableJoin.columns();
|
||||
String foreignJoinColumn = columns[0].getForeignDbColumn();
|
||||
String foreignIdColumn = targetDescriptor.idProperty().dbColumn();
|
||||
if (!foreignJoinColumn.equalsIgnoreCase(foreignIdColumn)) {
|
||||
throw new PersistenceException("Mapping limitation - @JoinColumn on " + fullName() + " needs to map to a primary key as per Issue #529 "
|
||||
+ " - joining to " + foreignJoinColumn + " and not " + foreignIdColumn);
|
||||
}
|
||||
if (importedId == null) {
|
||||
throw new PersistenceException("Cannot find imported id for " + fullName() + " from " + targetDescriptor
|
||||
+ ". If using native-image, possibly missing reflect-config for the Id property.");
|
||||
}
|
||||
} else {
|
||||
exportedProperties = createExported();
|
||||
|
||||
+26
-7
@@ -10,7 +10,7 @@ import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.text.TextException;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
import io.ebeaninternal.server.util.Checksum;
|
||||
import io.ebeaninternal.server.util.JsonContentHash;
|
||||
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.sql.SQLException;
|
||||
@@ -141,7 +141,10 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
}
|
||||
|
||||
/**
|
||||
* Hold checksum of json source content to use for dirty detection.
|
||||
* Hold canonical hash of json content to use for dirty detection.
|
||||
* <p>
|
||||
* Uses an order-independent hash so that databases which reorder JSON object
|
||||
* keys (e.g. PostgreSQL JSONB) do not cause false dirty detection.
|
||||
* <p>
|
||||
* Does not support rebuilding 'oldValue' as no original json content.
|
||||
*/
|
||||
@@ -152,7 +155,7 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
|
||||
ChecksumMutableValue(ScalarType<?> parent, String json) {
|
||||
this.parent = parent;
|
||||
this.checksum = Checksum.checksum(json);
|
||||
this.checksum = JsonContentHash.hash(json);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,13 +168,13 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
|
||||
@Override
|
||||
public MutableValueNext nextDirty(String json) {
|
||||
final long nextChecksum = Checksum.checksum(json);
|
||||
final long nextChecksum = JsonContentHash.hash(json);
|
||||
return nextChecksum == checksum ? null : new NextPair(json, new ChecksumMutableValue(parent, nextChecksum));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualToObject(Object obj) {
|
||||
return Checksum.checksum(parent.format(obj)) == checksum;
|
||||
return JsonContentHash.hash(parent.format(obj)) == checksum;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,6 +185,10 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
|
||||
/**
|
||||
* Hold json source content. This supports rebuilding the 'oldValue'.
|
||||
* <p>
|
||||
* Uses fast string equality as primary check, with an order-independent
|
||||
* canonical hash as fallback to handle databases that reorder JSON object
|
||||
* keys (e.g. PostgreSQL JSONB).
|
||||
*/
|
||||
private static final class SourceMutableValue implements MutableValueInfo, MutableValueNext {
|
||||
|
||||
@@ -195,12 +202,15 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
|
||||
@Override
|
||||
public MutableValueNext nextDirty(String json) {
|
||||
return Objects.equals(originalJson, json) ? null : new SourceMutableValue(parent, json);
|
||||
if (jsonContentEqual(originalJson, json)) {
|
||||
return null;
|
||||
}
|
||||
return new SourceMutableValue(parent, json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualToObject(Object obj) {
|
||||
return Objects.equals(originalJson, parent.format(obj));
|
||||
return jsonContentEqual(originalJson, parent.format(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,4 +229,13 @@ public final class BeanPropertyJsonMapper extends BeanPropertyJsonBasic {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two JSON strings for content equality, ignoring key ordering.
|
||||
* Uses fast string equality first, falls back to order-independent hash comparison.
|
||||
*/
|
||||
private static boolean jsonContentEqual(String json1, String json2) {
|
||||
return Objects.equals(json1, json2)
|
||||
|| JsonContentHash.hash(json1) == JsonContentHash.hash(json2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.config.AggregateFormulaContext;
|
||||
import io.ebeaninternal.api.FormulaBuilder;
|
||||
import io.ebeaninternal.server.query.STreeProperty;
|
||||
|
||||
final class DFormulaBuilder implements FormulaBuilder {
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
|
||||
DFormulaBuilder(BeanDescriptor<?> descriptor) {
|
||||
this.descriptor = descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public STreeProperty create(AggregateFormulaContext context, String formula, String path) {
|
||||
return FormulaPropertyPath.create(descriptor, context, formula, path);
|
||||
}
|
||||
}
|
||||
@@ -154,4 +154,9 @@ public interface DbSqlContext {
|
||||
* as it was already added to the query.
|
||||
*/
|
||||
boolean joinAdded();
|
||||
|
||||
/**
|
||||
* Include the filter many predicates if specified into the JOIN clause.
|
||||
*/
|
||||
void includeFilterMany();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.config.AggregateFormulaContext;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.query.STreeProperty;
|
||||
|
||||
@@ -9,11 +10,10 @@ import java.util.Set;
|
||||
|
||||
final class FormulaPropertyPath {
|
||||
|
||||
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"};
|
||||
|
||||
private static final String DISTINCT_ = "distinct ";
|
||||
|
||||
private final BeanDescriptor<?> descriptor;
|
||||
private final AggregateFormulaContext context;
|
||||
private final String formula;
|
||||
private final String outerFunction;
|
||||
private final String internalExpression;
|
||||
@@ -24,12 +24,13 @@ final class FormulaPropertyPath {
|
||||
private String cast;
|
||||
private String alias;
|
||||
|
||||
static STreeProperty create(BeanDescriptor<?> descriptor, String formula, String path) {
|
||||
return new FormulaPropertyPath(descriptor, formula, path).build();
|
||||
static STreeProperty create(BeanDescriptor<?> descriptor, AggregateFormulaContext context, String formula, String path) {
|
||||
return new FormulaPropertyPath(descriptor, context, formula, path).build();
|
||||
}
|
||||
|
||||
FormulaPropertyPath(BeanDescriptor<?> descriptor, String formula, String path) {
|
||||
FormulaPropertyPath(BeanDescriptor<?> descriptor, AggregateFormulaContext context, String formula, String path) {
|
||||
this.descriptor = descriptor;
|
||||
this.context = context;
|
||||
this.formula = formula;
|
||||
int openBracket = formula.indexOf('(');
|
||||
int closeBracket = formula.lastIndexOf(')');
|
||||
@@ -106,10 +107,10 @@ final class FormulaPropertyPath {
|
||||
}
|
||||
return create(scalarType);
|
||||
}
|
||||
if (isCount()) {
|
||||
if (context.isCount(outerFunction)) {
|
||||
return create(descriptor.scalarType(Types.BIGINT));
|
||||
}
|
||||
if (isConcat()) {
|
||||
if (context.isConcat(outerFunction)) {
|
||||
return create(descriptor.scalarType(Types.VARCHAR));
|
||||
}
|
||||
if (firstProp == null) {
|
||||
@@ -144,12 +145,7 @@ final class FormulaPropertyPath {
|
||||
}
|
||||
|
||||
private boolean isAggregate() {
|
||||
for (String aggFunction : AGG_FUNCTIONS) {
|
||||
if (aggFunction.equals(outerFunction)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return context.isAggregate(outerFunction);
|
||||
}
|
||||
|
||||
private String buildFormula(String parsed) {
|
||||
|
||||
@@ -27,11 +27,6 @@ public interface IdBinder {
|
||||
*/
|
||||
void initialise();
|
||||
|
||||
/**
|
||||
* Wrap the filter many expression with a condition allowing lEFT JOIN null matching row.
|
||||
*/
|
||||
String idNullOr(String name, String filterManyExpression);
|
||||
|
||||
String idSelect();
|
||||
|
||||
/**
|
||||
|
||||
@@ -45,20 +45,6 @@ public final class IdBinderEmbedded implements IdBinder {
|
||||
this.idInValueSql = idInExpandedForm ? idInExpanded() : idInCompressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String prefix, String filterManyExpression) {
|
||||
StringBuilder sb = new StringBuilder(100);
|
||||
sb.append("((");
|
||||
for (int i = 0; i < props.length; i++) {
|
||||
if (i > 0) {
|
||||
sb.append(" and ");
|
||||
}
|
||||
sb.append("${").append(prefix).append('}').append(props[i].dbColumn()).append(" is null");
|
||||
}
|
||||
sb.append(") or (").append(filterManyExpression).append("))");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idSelect() {
|
||||
return embIdProperty.name();
|
||||
|
||||
@@ -28,11 +28,6 @@ final class IdBinderEmpty implements IdBinder {
|
||||
public void initialise() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String name, String filterManyExpression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idSelect() {
|
||||
return "";
|
||||
|
||||
@@ -261,9 +261,4 @@ public final class IdBinderSimple implements IdBinder {
|
||||
final Object value = idProperty.getValue(bean);
|
||||
return scalarType.format(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String idNullOr(String prefix, String filterManyExpression) {
|
||||
return "(${" + prefix + "}" + idProperty.name() + " is null or (" + filterManyExpression + "))";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -804,11 +804,9 @@ public class DeployBeanDescriptor<T> {
|
||||
* Return the defaultSelectClause using FetchType.LAZY and FetchType.EAGER.
|
||||
*/
|
||||
public String getDefaultSelectClause() {
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
boolean hasLazyFetch = false;
|
||||
|
||||
for (DeployBeanProperty prop : propMap.values()) {
|
||||
if (!prop.isTransient() && !(prop instanceof DeployBeanPropertyAssocMany<?>)) {
|
||||
if (prop.isFetchEager()) {
|
||||
|
||||
@@ -617,6 +617,8 @@ public class DeployBeanProperty {
|
||||
this.dbRead = true;
|
||||
this.dbInsertable = false;
|
||||
this.dbUpdateable = false;
|
||||
// aggregation by default not fetchEager
|
||||
this.fetchEager = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user