mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-20 11:17:36 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7fddf1981 | ||
|
|
f610d03a1d | ||
|
|
852f199ffb | ||
|
|
0e6cd9db8c | ||
|
|
484ed5d859 | ||
|
|
56d33b1f1b | ||
|
|
e9b00cbbd1 | ||
|
|
230d7a36a7 | ||
|
|
10bf5dbbbd | ||
|
|
bffe741642 | ||
|
|
a8189567dd | ||
|
|
1545e68c3e | ||
|
|
4fd32b45d2 | ||
|
|
313fdda857 | ||
|
|
d42f72c0a2 | ||
|
|
6d53e89a80 | ||
|
|
7c5ee5b555 | ||
|
|
a2337a096e | ||
|
|
7bf5fc2798 | ||
|
|
987798add9 | ||
|
|
6295faa351 | ||
|
|
d9252a9c85 | ||
|
|
ddd864852d | ||
|
|
7966d8bc1b | ||
|
|
66e6e83e60 | ||
|
|
0e7f68e75a | ||
|
|
7b8e1713dd | ||
|
|
3ea9a5fa7f | ||
|
|
4fab5dfe4b | ||
|
|
9fac046f39 | ||
|
|
50bfd04987 | ||
|
|
d71ced32e8 | ||
|
|
6722cf50e9 | ||
|
|
13cebbcacb | ||
|
|
e70777bdb6 | ||
|
|
86cfd185f3 | ||
|
|
3970847443 | ||
|
|
b440c5ea27 | ||
|
|
1836ff18a5 | ||
|
|
efdee053cc | ||
|
|
a1ee75ffec | ||
|
|
da3dd8b215 | ||
|
|
6d30e6ff82 | ||
|
|
453a320210 | ||
|
|
61cc5e3459 | ||
|
|
23f23fa32b | ||
|
|
f65c409bfe | ||
|
|
ec49824430 | ||
|
|
819aaece4d | ||
|
|
c275953582 | ||
|
|
82e8494f42 | ||
|
|
abacda2c8f | ||
|
|
a7d1253dba | ||
|
|
a081d08621 | ||
|
|
8c37b53bad | ||
|
|
3f6d565800 | ||
|
|
1408696912 | ||
|
|
e0531a6315 | ||
|
|
0023f0ce13 |
@@ -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 -Pdefault
|
||||
run: mvn -T 1C clean test -Pdefault
|
||||
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: db2
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-db2.properties
|
||||
run: mvn -T 1C clean test -Dprops.file=testconfig/ebean-db2.properties
|
||||
|
||||
@@ -37,5 +37,5 @@ jobs:
|
||||
- name: Maven version
|
||||
run: mvn --version
|
||||
- name: H2Database
|
||||
run: mvn -T 8 clean package
|
||||
run: mvn -T 1C clean package
|
||||
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: mariadb 10.11
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-mariadb.properties
|
||||
run: mvn -T 1C clean test -Dprops.file=testconfig/ebean-mariadb.properties
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: mysql
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-mysql.properties
|
||||
run: mvn -T 1C clean test -Dprops.file=testconfig/ebean-mysql.properties
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: oracle
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-oracle.properties
|
||||
run: mvn -T 1 clean test -Dprops.file=testconfig/ebean-oracle.properties
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: postgres
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-postgres.properties
|
||||
run: mvn -T 1C clean test -Dprops.file=testconfig/ebean-postgres.properties
|
||||
|
||||
@@ -35,4 +35,4 @@ jobs:
|
||||
~/.m2
|
||||
key: build-${{ env.cache-name }}
|
||||
- name: sqlserver 2022
|
||||
run: mvn -T 8 clean test -Dprops.file=testconfig/ebean-sqlserver.properties
|
||||
run: mvn -T 1C clean test -Dprops.file=testconfig/ebean-sqlserver.properties
|
||||
|
||||
@@ -13,6 +13,9 @@ ebean-profiling*.xml
|
||||
profiling/
|
||||
.DS_Store
|
||||
|
||||
# Local Redis integration test credentials
|
||||
ebean-redis/src/test/resources/redis-local.yml
|
||||
|
||||
# Intellij project files
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-clickhouse</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-db2</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-hana</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mariadb</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mysql</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-net-postgis-types</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-nuodb</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-oracle</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlite</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -60,13 +60,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-all</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>composites</artifactId>
|
||||
|
||||
@@ -183,6 +183,8 @@ database.save(customer);
|
||||
| Configure database and `Database` bean | [add-ebean-postgres-database-config.md](guides/add-ebean-postgres-database-config.md) |
|
||||
| Add PostgreSQL test container support | [add-ebean-postgres-test-container.md](guides/add-ebean-postgres-test-container.md) |
|
||||
| Generate DB migrations | [add-ebean-db-migration-generation.md](guides/add-ebean-db-migration-generation.md) |
|
||||
| Migrate JSON APIs from Jackson core to avaje-json-core | [migrating-json-jackson-core-to-avaje-json-core.md](guides/migrating-json-jackson-core-to-avaje-json-core.md) |
|
||||
| Know which `@DbJson` types need Jackson vs built-in | [dbjson-mapping-support.md](guides/dbjson-mapping-support.md) |
|
||||
| Model entity beans correctly | [entity-bean-creation.md](guides/entity-bean-creation.md) |
|
||||
| Use Lombok safely with entities | [lombok-with-ebean-entity-beans.md](guides/lombok-with-ebean-entity-beans.md) |
|
||||
| Write type-safe query bean queries | [writing-ebean-query-beans.md](guides/writing-ebean-query-beans.md) |
|
||||
|
||||
@@ -12,8 +12,11 @@ Key guides (fetch and follow when performing the relevant task):
|
||||
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-maven-pom.md
|
||||
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-database-config.md
|
||||
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
|
||||
- Migrate JSON APIs from Jackson core to avaje-json-core: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-json-jackson-core-to-avaje-json-core.md
|
||||
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
|
||||
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
|
||||
- Query metrics and naming: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-metrics.md
|
||||
- Query plan capture: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-plan-capture.md
|
||||
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-test-container.md
|
||||
- DB migration generation: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-db-migration-generation.md
|
||||
- Lombok with entity beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/lombok-with-ebean-entity-beans.md
|
||||
|
||||
@@ -23,12 +23,15 @@ existing Maven project. Complete the steps in order.
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Migrate to `Database.builder()`](migrating-to-database-builder.md) | Replace legacy `new DatabaseConfig()` and `DatabaseFactory.create(...)` code with `Database.builder()` and `DatabaseBuilder.build()`. Includes common rewrites, fluent builder equivalents, and manual-review cases for semi-automated upgrades |
|
||||
| [Migrate JSON APIs from Jackson core to avaje-json-core](migrating-json-jackson-core-to-avaje-json-core.md) | Cut over `JsonParser`/`JsonGenerator`/`JsonFactory` usage to `JsonReader`/`JsonWriter`/`JsonStream`, including `DatabaseBuilder`/`DatabaseConfig` JSON config changes and validation checklist |
|
||||
|
||||
## Observability
|
||||
|
||||
| Guide | Description |
|
||||
|-------|-------------|
|
||||
| [Ebean OpenTelemetry tracing](add-ebean-opentelemetry.md) | Add `ebean-opentelemetry`, register `GlobalOpenTelemetry` once before Ebean databases are built, and troubleshoot missing spans or double-registration errors |
|
||||
| [Ebean query metrics and naming](ebean-query-metrics.md) | How Ebean query metric names are derived from `setLabel(..)` and profile locations; secondary (lazy/query) load naming; inline SQL comments; collecting metrics at runtime; mapping to avaje-metrics tags |
|
||||
| [Ebean query plan capture](ebean-query-plan-capture.md) | Enable and configure database query plan (`EXPLAIN`) capture for slow queries; bind capture vs plan capture; periodic and on-demand collection; thresholds, load limits, EXPLAIN dialect, and listeners |
|
||||
|
||||
## Entity beans
|
||||
|
||||
@@ -36,6 +39,7 @@ existing Maven project. Complete the steps in order.
|
||||
|-------|-------------|
|
||||
| [Entity Bean Creation](entity-bean-creation.md) | How to generate clean, idiomatic Ebean entity beans for AI agents; patterns and anti-patterns; field visibility and accessor guidance; minimal boilerplate |
|
||||
| [Lombok with Ebean entity beans](lombok-with-ebean-entity-beans.md) | Which Lombok annotations to use and avoid on entity beans; why `@Data` is incompatible with Ebean; how to use `@Getter` + `@Setter` + `@Accessors(chain = true)` |
|
||||
| [`@DbJson` mapping support (built-in vs Jackson)](dbjson-mapping-support.md) | Which `@DbJson` / `@DbJsonB` property types are handled by the built-in avaje-json-core support versus which require `ebean-jackson-mapper` (Jackson `ObjectMapper`); supported `String`/`List`/`Set`/`Map` matrix; enum-key and `@DbArray` notes |
|
||||
|
||||
## Querying
|
||||
|
||||
@@ -149,6 +153,8 @@ Key guides (fetch and follow these when performing the relevant task):
|
||||
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
|
||||
- Immutable bean cache for read-only references: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/immutable-bean-cache.md
|
||||
- Ebean OpenTelemetry tracing: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-opentelemetry.md
|
||||
- Query metrics and naming: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-metrics.md
|
||||
- Query plan capture: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/ebean-query-plan-capture.md
|
||||
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
|
||||
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-test-container.md
|
||||
- DB migration generation: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-db-migration-generation.md
|
||||
|
||||
@@ -250,6 +250,40 @@ For each future set of entity bean changes:
|
||||
4. Review the generated `.sql` to confirm it reflects the intended changes
|
||||
5. Commit both files
|
||||
|
||||
### Protecting hand-edited and non-versioned migrations across regeneration
|
||||
|
||||
`GenerateDbMigration` regenerates the apply SQL and model XML from the **current
|
||||
entity model**. It can therefore overwrite content you did not change in the
|
||||
entity beans, including:
|
||||
|
||||
- **hand-edited DDL** in a generated versioned `.sql` file, and
|
||||
- **repeatable** (`R__*.sql`) scripts that the generator also derives from the
|
||||
model (e.g. view definitions in `extra-ddl.xml`, built-in partitioning helpers).
|
||||
|
||||
**Init scripts (`I__*.sql`) are write-once.** If an init script already exists on
|
||||
disk the generator **does not** rewrite it, so hand-tuned init DDL (partition
|
||||
functions, `UNLOGGED` tables, triggers, seed data) is preserved across
|
||||
regeneration. The trade-off: to pick up an upstream change to a built-in init
|
||||
script (e.g. the partition helper) you must **delete the file first**, then
|
||||
regenerate. Repeatable scripts are always regenerated.
|
||||
|
||||
To avoid losing manual work:
|
||||
|
||||
- Prefer an **init** (`I__`) script for hand-maintained DDL the entity model
|
||||
cannot express — it is isolated and now protected from regeneration.
|
||||
- For **versioned** `.sql` and **repeatable** `R__` scripts that the generator
|
||||
produces, review the diff after **every** regeneration and **restore** any
|
||||
clobbered hand-tuning (e.g. `git checkout dbmigration/...`) before committing.
|
||||
- If your build maintains a migration index file (e.g. `idx_*.migrations`),
|
||||
re-check that the new migration is listed and filenames match after renaming a
|
||||
generated file.
|
||||
|
||||
> **Run the generator from the module directory.** The output path set via
|
||||
> `setPathToResources(...)` is resolved relative to the **working directory**.
|
||||
> Run `GenerateDbMigration` with the working directory set to the module that owns
|
||||
> `src/main/resources` (e.g. `cd server` first). Note that `mvn exec:java` does
|
||||
> **not** honour a configured `workingDirectory`, so set the cwd yourself.
|
||||
|
||||
---
|
||||
|
||||
## Understanding the output files
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# Guide: `@DbJson` / `@DbJsonB` mapping support — built-in vs Jackson ObjectMapper
|
||||
|
||||
## Purpose
|
||||
|
||||
Ebean can map `@DbJson` and `@DbJsonB` properties in two ways:
|
||||
|
||||
- **Built-in** JSON support, backed by **avaje-json-core** — no extra dependency.
|
||||
- **Jackson `ObjectMapper`**, provided by the **`ebean-jackson-mapper`** module — used
|
||||
for everything the built-in support does not handle.
|
||||
|
||||
This guide lists exactly which property types are handled built-in and which require
|
||||
`ebean-jackson-mapper`.
|
||||
|
||||
> If a property type is **not** handled built-in and `ebean-jackson-mapper` is not on the
|
||||
> classpath, Ebean fails fast at startup:
|
||||
>
|
||||
> ```text
|
||||
> Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper?
|
||||
> Jackson ObjectMapper not present for <property>
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Property type | Built-in (avaje-json-core) | Needs `ebean-jackson-mapper` |
|
||||
|---|:---:|:---:|
|
||||
| `String` | ✅ | |
|
||||
| `List<String>`, `List<Long>` | ✅ | |
|
||||
| `Set<String>`, `Set<Long>` | ✅ | |
|
||||
| `Map<String, Object>`, `Map<String, ?>` | ✅ | |
|
||||
| `Map<String, String>` | ✅ | |
|
||||
| `Map<Enum, Object>`, `Map<Enum, String>` | ✅ | |
|
||||
| `List`/`Set` of any other element type (`Integer`, `Double`, `UUID`, `LocalDate`, an enum, a POJO, …) | | ✅ |
|
||||
| `Map` with a typed value other than `String`/`Object` (`Map<String,Integer>`, `Map<String,UUID>`, …) | | ✅ |
|
||||
| `Map` with a key other than `String` or an enum (`Map<Integer, …>`, `Map<UUID, …>`) | | ✅ |
|
||||
| POJOs, records, or any other type | | ✅ |
|
||||
|
||||
---
|
||||
|
||||
## Built-in support (no Jackson required)
|
||||
|
||||
The built-in path materialises JSON into the *natural* JSON value types
|
||||
(`String`, `Long`, `BigDecimal`, `Boolean`, `Map`, `List`). It is therefore type-safe only
|
||||
for the following declared property types:
|
||||
|
||||
- **`String`** — stored as raw JSON text.
|
||||
- **`List<String>`** and **`List<Long>`**.
|
||||
- **`Set<String>`** and **`Set<Long>`**.
|
||||
- **`Map<K, V>`** where:
|
||||
- the key `K` is `String` or an **enum**, and
|
||||
- the value `V` is `Object`, `String`, or a wildcard `?`.
|
||||
|
||||
So `Map<String,Object>`, `Map<String,String>`, `Map<Enum,Object>` and `Map<Enum,String>`
|
||||
are all built-in.
|
||||
|
||||
These mappings work across all supported storage types — `VARCHAR`, `CLOB`, `BLOB`, and
|
||||
Postgres `json` / `jsonb` — without `ebean-jackson-mapper`.
|
||||
|
||||
---
|
||||
|
||||
## Everything else → Jackson `ObjectMapper`
|
||||
|
||||
Any other `@DbJson` / `@DbJsonB` property routes to the Jackson `ObjectMapper` path, which
|
||||
requires `ebean-jackson-mapper`:
|
||||
|
||||
- **Typed collections** — `List`/`Set` whose element type is not `String` or `Long`
|
||||
(for example `List<Integer>`, `List<UUID>`, `List<LocalDate>`, `List<MyEnum>`, `List<MyPojo>`).
|
||||
- **Typed-value maps** — a `Map` value type other than `String`/`Object`
|
||||
(for example `Map<String,Integer>`, `Map<String,UUID>`, `Map<String,MyPojo>`).
|
||||
- **Non-`String`/non-enum map keys** — for example `Map<Integer,Object>`, `Map<UUID,String>`.
|
||||
- **POJOs, records, and any other custom type.**
|
||||
|
||||
> **Jackson marker annotation override:** if the **field or getter** carries a Jackson annotation
|
||||
> (anything meta-annotated with `com.fasterxml.jackson.annotation.JacksonAnnotation`), Ebean
|
||||
> uses the `ObjectMapper` path even when the type would otherwise be handled built-in.
|
||||
|
||||
---
|
||||
|
||||
## Adding `ebean-jackson-mapper`
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
A Jackson `ObjectMapper` must be available (via `jackson-databind`). Ebean detects it and
|
||||
registers the mapper-based JSON support automatically.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Enum map keys** are serialised using the enum `name()` (for example `ACTIVE`), not any
|
||||
`@DbEnumValue` mapping. Round-trips are correct; the DB value mapping is not applied to
|
||||
JSON keys.
|
||||
- **`@DbArray` alternative:** for typed *scalar* collections (`List`/`Set` of `Integer`,
|
||||
`Long`, `UUID`, `Double`, an enum, …) consider `@DbArray`, which maps to a native DB array
|
||||
(with a JSON fallback on platforms without array support) and supports more element types
|
||||
than built-in `@DbJson` collections.
|
||||
- The reason typed value/element collections need a real mapper is that the built-in path
|
||||
only produces natural JSON types — for example a JSON number always parses to `Long`, so a
|
||||
declared `List<Integer>` or `Map<String,Integer>` could not be populated safely without a
|
||||
type-aware mapper.
|
||||
|
||||
---
|
||||
|
||||
## Choosing
|
||||
|
||||
- Prefer the **built-in** mappings for the common cases (`String`, string/long lists and sets,
|
||||
object/string maps) to avoid pulling in Jackson.
|
||||
- Add **`ebean-jackson-mapper`** when you need rich POJO JSON columns or typed collections /
|
||||
typed-value maps.
|
||||
@@ -0,0 +1,262 @@
|
||||
# Guide: Ebean query metrics and naming
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide explains the metrics Ebean captures, how the metric **name** for a query
|
||||
is derived, and how you influence that name with `setLabel(..)` and **profile
|
||||
locations**. It also covers secondary (lazy / query) load naming, the inline SQL
|
||||
comment, collecting metrics at runtime, and how the names map to avaje-metrics tags.
|
||||
|
||||
Use this guide when you want to identify a query in metrics/telemetry, when a query
|
||||
shows up under an unexpected metric name, or when wiring Ebean metrics into a reporter.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Ebean records timing and counter metrics for the work it does. Every metric has a
|
||||
**name** whose leading segment identifies the kind of work:
|
||||
|
||||
| Prefix | What it measures | Example name |
|
||||
|---|---|---|
|
||||
| `orm.` | Entity (ORM) query | `orm.Customer.findList`, `orm.CustomerFinder.byName` |
|
||||
| `dto.` | DTO query | `dto.CustomerDto.byEmail` |
|
||||
| `sql.query.` | Raw SQL query | `sql.query.<label>` |
|
||||
| `sql.update.` / `sql.call.` | Raw SQL update / stored procedure call | `sql.update.<label>` |
|
||||
| `orm.update.` | ORM update statement | `orm.update.<label>` |
|
||||
| `iud.` | Bean insert / update / delete | `iud.Customer.insert` |
|
||||
| `txn.main` / `txn.readonly` / `txn.named.` | Transactions | `txn.main`, `txn.named.processOrders` |
|
||||
| `l2n.` | L2 cache region | `l2n.customer.hit` |
|
||||
|
||||
The rest of this guide focuses on **`orm.` query names**, which is where labels and
|
||||
profile locations apply.
|
||||
|
||||
---
|
||||
|
||||
## How an ORM query name is derived
|
||||
|
||||
An entity query name has the form `orm.<identifier>`. The `<identifier>` comes from one
|
||||
of three sources, in priority order:
|
||||
|
||||
1. **An explicit `setLabel(..)`** — prefixed with the bean type for disambiguation.
|
||||
2. **A profile location** — used as-is (it is already a unique `Class.method` identifier).
|
||||
3. **Neither** — the bean type plus the query type (e.g. `findList`).
|
||||
|
||||
| Root query source | Resulting name |
|
||||
|---|---|
|
||||
| `setLabel("custMain")` on `Customer` | `orm.Customer.custMain` |
|
||||
| Profile location `CustomerFinder.byName` | `orm.CustomerFinder.byName` |
|
||||
| Unlabelled `DB.find(Customer.class).findList()` | `orm.Customer.findList` |
|
||||
|
||||
The asymmetry is intentional: an explicit label is a short, ambiguous token (`custMain`
|
||||
could be used for any bean), so the bean type is prefixed. A profile location is already
|
||||
unique and type-independent, so it is used as-is.
|
||||
|
||||
### Step 1 - Label a query explicitly
|
||||
|
||||
```java
|
||||
List<Customer> customers = DB.find(Customer.class)
|
||||
.setLabel("custMain")
|
||||
.findList();
|
||||
// metric name: orm.Customer.custMain
|
||||
```
|
||||
|
||||
DTO queries support `setLabel(..)` too, and follow the **same naming convention** as
|
||||
ORM queries — an explicit label is prefixed with the DTO type, a profile location is
|
||||
used as-is, and an unlabelled DTO query uses just the DTO type:
|
||||
|
||||
```java
|
||||
DB.findDto(CustomerDto.class, sql)
|
||||
.setLabel("byEmail")
|
||||
.findList();
|
||||
// metric name: dto.CustomerDto.byEmail
|
||||
// profile location only -> dto.<location> (no type prefix)
|
||||
// unlabelled -> dto.CustomerDto
|
||||
```
|
||||
|
||||
### Step 2 - Use a profile location (preferred for finders / query beans)
|
||||
|
||||
A profile location identifies a query by its **call site** (`Class.method`) instead of a
|
||||
hand-written label.
|
||||
|
||||
**The common case is automatic.** With Ebean's byte-code enhancement enabled (the normal
|
||||
setup when using query beans / finders), Ebean assigns each query a profile location
|
||||
derived from its call site — no code is required:
|
||||
|
||||
```java
|
||||
List<Customer> customers = new QCustomer()
|
||||
.status.eq(Status.ACTIVE)
|
||||
.findList();
|
||||
// metric name: orm.<CallingClass>.<method> (often with a line number, see below)
|
||||
```
|
||||
|
||||
The enhancer derives the location from the calling code (the method that runs the query),
|
||||
and for many call sites it includes the **source line number** (e.g.
|
||||
`CustomerService.find:42`), so distinct call sites — even in the same method — get distinct
|
||||
names automatically.
|
||||
|
||||
**Setting one explicitly.** You can also set a profile location yourself, which is useful
|
||||
without enhancement or to control the identity:
|
||||
|
||||
```java
|
||||
ProfileLocation LOC = ProfileLocation.create();
|
||||
|
||||
List<Customer> customers = DB.find(Customer.class)
|
||||
.setProfileLocation(LOC)
|
||||
.where().eq("status", Status.ACTIVE)
|
||||
.findList();
|
||||
// metric name: orm.<DeclaringClass>.<method>
|
||||
```
|
||||
|
||||
Factory choices:
|
||||
|
||||
- `ProfileLocation.create()` — call site as `Class.method`, **no line number**.
|
||||
- `ProfileLocation.createWithLine()` — includes the source line number
|
||||
(e.g. `CustomerService.find:42`), so two queries in the **same method** get
|
||||
**distinct** names.
|
||||
- `ProfileLocation.create("label")` — a named location (used for named transactions).
|
||||
|
||||
> Note: a location with no line number (`create()`, or a call site the enhancer emits
|
||||
> without a line) means two different queries in the same method share one name. The
|
||||
> queried entity is still distinguishable downstream via the avaje-metrics `type` tag
|
||||
> (see "Mapping to avaje-metrics tags" below). Use `createWithLine()` to separate
|
||||
> same-method call sites in the name itself.
|
||||
|
||||
---
|
||||
|
||||
## Secondary (lazy / query) load naming
|
||||
|
||||
When a query lazy-loads or `fetchQuery()`-loads an association, Ebean issues a
|
||||
**secondary** query. Its name **extends the parent query's full name** with the relative
|
||||
path and the load mode (`lazy` or `query`), joined with `.`:
|
||||
|
||||
```
|
||||
orm.<parent name without the "orm." prefix>.<path>.<loadMode>
|
||||
```
|
||||
|
||||
So a secondary load is always an exact extension of its parent metric name, which makes
|
||||
the relationship obvious in dashboards.
|
||||
|
||||
Example — root labelled `custMain` on `Customer`, chain `Customer -> orders -> details`:
|
||||
|
||||
Lazy loading:
|
||||
```
|
||||
orm.Customer.custMain
|
||||
orm.Customer.custMain.orders.lazy
|
||||
orm.Customer.custMain.orders.lazy.details.lazy
|
||||
```
|
||||
|
||||
Secondary eager `fetchQuery()` loading:
|
||||
```
|
||||
orm.Customer.custMain
|
||||
orm.Customer.custMain.orders.query
|
||||
orm.Customer.custMain.orders.query.details.query
|
||||
```
|
||||
|
||||
The same applies with a **profile-location** root (no explicit `setLabel`):
|
||||
```
|
||||
orm.CustomerFinder.byName
|
||||
orm.CustomerFinder.byName.contacts.lazy
|
||||
```
|
||||
|
||||
Unlike the root query, the secondary name is **not** bean-type prefixed by the loaded
|
||||
type — it inherits the parent's name so it relates back to where the load originated.
|
||||
|
||||
---
|
||||
|
||||
## Inline SQL comment
|
||||
|
||||
When `includeLabelInSql` is enabled (the default), Ebean prepends the query's label (or
|
||||
profile-location label) as an inline SQL comment, which is useful for matching slow
|
||||
queries in database logs back to application code:
|
||||
|
||||
```sql
|
||||
select /* CustomerFinder.byName */ t0.id, t0.name from be_customer t0 where ...
|
||||
```
|
||||
|
||||
The comment uses the explicit `setLabel(..)` if present, otherwise the profile-location
|
||||
label. Secondary queries use their full extended name
|
||||
(e.g. `/* Customer.custMain.contacts.query */`). `EXISTS` / subquery forms are not
|
||||
commented.
|
||||
|
||||
Disable it via the builder:
|
||||
|
||||
```java
|
||||
Database.builder()
|
||||
.includeLabelInSql(false)
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Collecting metrics at runtime
|
||||
|
||||
Read collected metrics through `Database.metaInfo()`:
|
||||
|
||||
```java
|
||||
import io.ebean.meta.MetaQueryMetric;
|
||||
import io.ebean.meta.ServerMetrics;
|
||||
|
||||
ServerMetrics metrics = database.metaInfo().collectMetrics(); // resets counters
|
||||
|
||||
for (MetaQueryMetric q : metrics.queryMetrics()) {
|
||||
System.out.printf("%s type=%s count=%d total=%d mean=%d%n",
|
||||
q.name(), // e.g. orm.Customer.custMain
|
||||
q.type().getSimpleName(), // the queried bean/DTO type, e.g. Customer
|
||||
q.count(), q.total(), q.mean());
|
||||
}
|
||||
```
|
||||
|
||||
Key API:
|
||||
|
||||
- `database.metaInfo()` → `MetaInfoManager`.
|
||||
- `collectMetrics()` collects and **resets**; `collectMetrics(false)` collects without
|
||||
reset; `visitMetrics(visitor)` for streaming.
|
||||
- `ServerMetrics` exposes `queryMetrics()`, `timedMetrics()`, `countMetrics()`.
|
||||
- `MetaQueryMetric` exposes `name()`, `label()`, `type()` (the queried `Class<?>`),
|
||||
`sql()`, `hash()`, plus timing `count()` / `total()` / `max()` / `mean()`.
|
||||
|
||||
---
|
||||
|
||||
## Mapping to avaje-metrics tags
|
||||
|
||||
When integrating with **avaje-metrics** (`avaje-metrics-ebean`
|
||||
`DatabaseMetricSupplier`), the flat `orm.`/`dto.`/`sql.` names are translated to a tagged
|
||||
form, with the bean type carried as a `type` tag:
|
||||
|
||||
```
|
||||
ebean.query{kind=orm|dto|sql, type=<BeanSimpleName>, label=<rest of the name>}
|
||||
```
|
||||
|
||||
Because the entity is available as the `type` tag, two different-entity queries that
|
||||
share a profile-location name remain distinct series on tag-aware backends (OpenTelemetry,
|
||||
Prometheus, StatsD) without needing the bean type in the name.
|
||||
|
||||
For the integration setup, see the avaje-metrics guide
|
||||
[`add-ebean-metrics.md`](https://github.com/avaje/avaje-metrics/blob/master/docs/guides/add-ebean-metrics.md).
|
||||
|
||||
To capture the database execution plan (`EXPLAIN`) for slow queries identified by these
|
||||
metrics, see [Ebean query plan capture](ebean-query-plan-capture.md).
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### A query shows up as `orm.<Bean>.findList` (no useful identity)
|
||||
|
||||
It has neither a label nor a profile location. Add `setLabel(..)` or a
|
||||
`ProfileLocation`, or apply a profile location on the finder / query bean.
|
||||
|
||||
### Two queries in one method share a metric name
|
||||
|
||||
This happens when the profile location for those call sites has no line number. With
|
||||
enhancement, many call sites already include a line number; for those that don't, use
|
||||
`ProfileLocation.createWithLine()` to separate them by line, or give each an explicit
|
||||
`setLabel(..)`. On tag-aware backends the avaje-metrics `type` tag already separates
|
||||
different entity types.
|
||||
|
||||
### A secondary (lazy / query) load isn't grouped under its parent
|
||||
|
||||
Secondary names extend the parent's full name. If the parent has no label or profile
|
||||
location, its name falls back to `orm.<Bean>.<queryType>` and the secondary extends
|
||||
that. Give the root query a label or profile location for a stable parent name.
|
||||
@@ -0,0 +1,242 @@
|
||||
# Guide: Ebean query plan capture
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide explains how to enable and configure **query plan capture** in Ebean — the
|
||||
mechanism that captures the database's actual execution plan (via `EXPLAIN`) for slow
|
||||
queries, so you can diagnose missing indexes and poor plans in production.
|
||||
|
||||
Use this guide when you want Ebean to record real query plans, when tuning the capture
|
||||
thresholds and load limits, or when wiring a listener to ship captured plans somewhere.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Query plan capture is a **two-phase** mechanism:
|
||||
|
||||
1. **Bind capture** — when enabled, Ebean watches query executions and, for queries
|
||||
slower than a threshold, captures the actual **bind values** that were used. This is
|
||||
cheap: it just remembers the parameters of a slow execution.
|
||||
2. **Plan capture** — using those captured bind values, Ebean runs `EXPLAIN <sql>`
|
||||
against the database to obtain the execution plan, producing `MetaQueryPlan` results
|
||||
that are handed to a `QueryPlanListener`.
|
||||
|
||||
Plan capture is split this way so the expensive `EXPLAIN` work (actual database load)
|
||||
happens periodically or on demand, against representative bind values, rather than on
|
||||
every slow query.
|
||||
|
||||
Two ways to trigger phase 2:
|
||||
|
||||
- **Automatic periodic capture** — a background timer collects plans on a schedule.
|
||||
- **On demand** — call the `MetaInfoManager` API to arm and collect plans yourself
|
||||
(this is what remote tooling such as ebean-insight uses).
|
||||
|
||||
Plan capable queries are:
|
||||
|
||||
- **ORM entity SELECT queries** (`orm.*` metrics) — captured via the per-entity `BeanDescriptor`.
|
||||
- **Native-SQL `DtoQuery`** (`dto.*` metrics) — a `DtoQuery` created from a SQL string
|
||||
(`DB.findDto(MyDto.class, "select ...")`) has its own bind capture and is `EXPLAIN`'d directly.
|
||||
- **ORM-backed `DtoQuery`** (`Query.asDto(...)`) — captured via the *underlying* ORM query plan
|
||||
(`orm.*`), not the `dto.*` plan. The `dto.*` plan itself is **not** armed in this case, so it
|
||||
does not double-count in `queryPlanInit`.
|
||||
- **Native-SQL `SqlQuery`** (`sql.query.*` metrics) — a **labelled** `SqlQuery`
|
||||
(`DB.sqlQuery("select ...").setLabel("myLabel")`) has its own bind capture and is `EXPLAIN`'d
|
||||
directly. A label is required: without `setLabel(...)` the query produces no metric and no plan.
|
||||
|
||||
Specifically **excluded** are:
|
||||
|
||||
- **Update / DML** — `orm.update.*`, `iud.*`, `sql.update.*`, `sql.call.*`.
|
||||
|
||||
Bind capture is wired into the ORM query path (per-entity `BeanDescriptor`), the native-SQL DTO
|
||||
path (per-DTO `DtoBeanDescriptor`), and the native-SQL `SqlQuery` path (the relational query
|
||||
engine); the init/collect API iterates all three. DML — even though it produces timing metrics —
|
||||
never captures bind values and cannot be `EXPLAIN`'d.
|
||||
|
||||
> **Cost when disabled:** SqlQuery plan capture is fully gated on the `queryPlan.enable` master
|
||||
> switch. When capture is disabled no `SqlQuery` plans are created or cached, so labelled queries
|
||||
> incur no extra cost beyond their existing timing metric.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 - Enable bind capture
|
||||
|
||||
Bind capture is the master switch; nothing is captured until it is on.
|
||||
|
||||
> **Security — bind values may contain PII.** Bind capture records the **actual
|
||||
> parameter values** used by slow query executions, and those values are stored
|
||||
> and shown verbatim in the captured plan output (alongside the SQL and EXPLAIN
|
||||
> plan). They can therefore contain personal or otherwise sensitive data. Capture
|
||||
> is opt-in and off by default (`queryPlan.enable=false`): only enable it where
|
||||
> that data exposure is acceptable, restrict who can read captured plans, and
|
||||
> prefer arming specific query hashes (Step 3) over a low global threshold so you
|
||||
> capture the minimum needed.
|
||||
|
||||
```java
|
||||
Database database = Database.builder()
|
||||
.queryPlanEnable(true) // turn on bind capture
|
||||
.queryPlanThresholdMicros(100_000) // capture binds for queries slower than 100ms
|
||||
.build();
|
||||
```
|
||||
|
||||
- `queryPlanEnable(boolean)` — enable bind capture. Default **false**.
|
||||
- `queryPlanThresholdMicros(long)` — global execution-time threshold (microseconds) a
|
||||
query must exceed before its bind values are captured. Default **`Long.MAX_VALUE`**
|
||||
(effectively off), so you must either lower it or arm specific plans by hash (Step 3).
|
||||
|
||||
Equivalent `application.properties` (avaje-config / properties):
|
||||
|
||||
```properties
|
||||
queryPlan.enable=true
|
||||
queryPlan.thresholdMicros=100000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 - Enable automatic periodic capture (optional)
|
||||
|
||||
To have Ebean periodically run `EXPLAIN` for armed queries and report the plans:
|
||||
|
||||
```java
|
||||
Database database = Database.builder()
|
||||
.queryPlanEnable(true)
|
||||
.queryPlanThresholdMicros(100_000)
|
||||
.queryPlanCapture(true) // turn on the periodic capture timer
|
||||
.queryPlanCapturePeriodSecs(600) // every 10 minutes (default)
|
||||
.queryPlanCaptureMaxTimeMillis(10_000) // stop after 10s of capturing per cycle
|
||||
.queryPlanCaptureMaxCount(10) // at most 10 plans per cycle
|
||||
.queryPlanListener(capture -> {
|
||||
for (var plan : capture.plans()) {
|
||||
System.out.println(plan.label() + "\n" + plan.plan());
|
||||
}
|
||||
})
|
||||
.build();
|
||||
```
|
||||
|
||||
- `queryPlanCapture(boolean)` — enable the background periodic capture. Default **false**.
|
||||
- `queryPlanCapturePeriodSecs(long)` — capture frequency in seconds. Default **600** (10 min).
|
||||
- `queryPlanCaptureMaxTimeMillis(long)` — per-cycle time budget; capture stops once
|
||||
exceeded, bounding the database load. Default **10000** (10s).
|
||||
- `queryPlanCaptureMaxCount(int)` — max plans captured per cycle. Default **10**.
|
||||
- `queryPlanListener(QueryPlanListener)` — receives each `QueryPlanCapture`. If not set,
|
||||
the default listener logs plans to the `io.ebean.QUERYPLAN` logger at `INFO`.
|
||||
|
||||
Properties form:
|
||||
|
||||
```properties
|
||||
queryPlan.enable=true
|
||||
queryPlan.thresholdMicros=100000
|
||||
queryPlan.capture=true
|
||||
queryPlan.capturePeriodSecs=600
|
||||
queryPlan.captureMaxTimeMillis=10000
|
||||
queryPlan.captureMaxCount=10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3 - Capture on demand (foreground)
|
||||
|
||||
Instead of (or in addition to) the periodic timer, drive capture through
|
||||
`database.metaInfo()`. This is useful for targeted capture and is how remote tooling
|
||||
arms specific slow queries by their plan hash.
|
||||
|
||||
```java
|
||||
import io.ebean.meta.MetaInfoManager;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.QueryPlanInit;
|
||||
import io.ebean.meta.QueryPlanRequest;
|
||||
|
||||
MetaInfoManager meta = database.metaInfo();
|
||||
|
||||
// Phase 1: arm bind capture - either all plans or specific hashes
|
||||
QueryPlanInit init = new QueryPlanInit();
|
||||
init.setAll(true); // or init.add("<planHash>", 50_000);
|
||||
init.thresholdMicros(100_000);
|
||||
List<MetaQueryPlan> armed = meta.queryPlanInit(init);
|
||||
|
||||
// ... let the application run so slow executions capture their bind values ...
|
||||
|
||||
// Phase 2: collect plans now (runs EXPLAIN)
|
||||
QueryPlanRequest request = new QueryPlanRequest();
|
||||
request.maxCount(10);
|
||||
request.maxTimeMillis(10_000);
|
||||
request.since(System.currentTimeMillis() - 300_000); // binds at least ~5 min old
|
||||
List<MetaQueryPlan> plans = meta.queryPlanCollectNow(request);
|
||||
```
|
||||
|
||||
- `QueryPlanInit` arms bind capture. `setAll(true)` arms every plan; `add(hash, micros)`
|
||||
arms a specific plan (a hash of `"all"` is treated as all).
|
||||
- `QueryPlanRequest.since(epochMillis)` ensures the captured bind values have existed for
|
||||
a while, so they better represent the slowest executions. `maxCount` / `maxTimeMillis`
|
||||
bound the work, mirroring the periodic settings.
|
||||
|
||||
`MetaQueryPlan` exposes `beanType()`, `label()`, `profileLocation()`, `sql()`, `hash()`,
|
||||
`bind()`, `plan()` (the raw EXPLAIN output), `queryTimeMicros()`, `captureCount()`,
|
||||
`captureMicros()`, and `whenCaptured()`.
|
||||
|
||||
---
|
||||
|
||||
## Step 4 - EXPLAIN dialect
|
||||
|
||||
Ebean chooses the `EXPLAIN` statement per database platform:
|
||||
|
||||
| Platform | EXPLAIN used |
|
||||
|---|---|
|
||||
| PostgreSQL | `explain (analyze, costs, verbose, buffers) <sql>` |
|
||||
| YugabyteDB | `explain (analyze, buffers, dist) <sql>` |
|
||||
| Oracle | `EXPLAIN PLAN FOR <sql>` |
|
||||
| SQL Server | platform-specific logger |
|
||||
| H2 / MySQL / other | `explain <sql>` |
|
||||
|
||||
Override the prefix with `queryPlanExplain(..)` (or `queryPlan.explain`):
|
||||
|
||||
```java
|
||||
Database.builder()
|
||||
.queryPlanExplain("explain (costs, verbose)") // omit ANALYZE on Postgres
|
||||
.build();
|
||||
```
|
||||
|
||||
> **Caution (PostgreSQL / Yugabyte):** the default includes `ANALYZE`, which **actually
|
||||
> executes** the query to produce real timings. For non-idempotent or expensive queries,
|
||||
> override with a non-ANALYZE `explain` to avoid side effects and extra load.
|
||||
|
||||
---
|
||||
|
||||
## Related setting: internal plan TTL
|
||||
|
||||
`queryPlanTTLSeconds(int)` (default **300**) is a **different** concept — it is the time to
|
||||
live for Ebean's *internal* query plan (the object that knows how to execute a query, read
|
||||
the result set and collect metrics). It is not part of EXPLAIN capture, but is set through
|
||||
the same builder.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No plans are captured
|
||||
|
||||
1. `queryPlanEnable(true)` must be set — it is the master switch.
|
||||
2. `queryPlanThresholdMicros` defaults to `Long.MAX_VALUE`. Lower it, or arm specific
|
||||
plans via `QueryPlanInit`, otherwise no execution is ever "slow enough".
|
||||
3. For periodic capture, also set `queryPlanCapture(true)`.
|
||||
4. Queries must actually run slower than the threshold to have their binds captured.
|
||||
|
||||
### Plans appear but nothing is reported anywhere
|
||||
|
||||
No `queryPlanListener` is configured, so plans go to the default `io.ebean.QUERYPLAN`
|
||||
logger. Set a listener, or enable `INFO` logging for `io.ebean.QUERYPLAN`.
|
||||
|
||||
### Capture adds noticeable database load
|
||||
|
||||
`EXPLAIN ANALYZE` executes the query. Reduce `queryPlanCaptureMaxCount`, increase
|
||||
`queryPlanCapturePeriodSecs`, tighten `queryPlanCaptureMaxTimeMillis`, or override
|
||||
`queryPlanExplain` to a non-ANALYZE form.
|
||||
|
||||
### An unlabelled SqlQuery or update metric never offers plan capture
|
||||
|
||||
ORM entity SELECT queries (`orm.*`), native-SQL `DtoQuery` (`dto.*`) and native-SQL
|
||||
**labelled** `SqlQuery` (`sql.query.*`) are plan capable. ORM-backed DTO queries
|
||||
(`Query.asDto(...)`) are captured via their underlying ORM plan (`orm.*`), not the `dto.*` plan.
|
||||
An unlabelled `SqlQuery` produces no metric and no plan — add `setLabel(...)` to make it
|
||||
capturable. Write metrics (`orm.update.*`, `iud.*`, `sql.update.*`, `sql.call.*`) have no bind
|
||||
capture and are intentionally excluded.
|
||||
@@ -0,0 +1,91 @@
|
||||
# Guide: Migrate JSON APIs from Jackson core to avaje-json-core
|
||||
|
||||
## Purpose
|
||||
|
||||
This guide covers the one-step cutover in Ebean from Jackson core JSON APIs to
|
||||
avaje-json-core APIs.
|
||||
|
||||
Use this when upgrading code that references:
|
||||
|
||||
- `com.fasterxml.jackson.core.JsonParser`
|
||||
- `com.fasterxml.jackson.core.JsonGenerator`
|
||||
- `com.fasterxml.jackson.core.JsonFactory`
|
||||
|
||||
The replacement types are:
|
||||
|
||||
- `io.avaje.json.JsonReader`
|
||||
- `io.avaje.json.JsonWriter`
|
||||
- `io.avaje.json.stream.JsonStream`
|
||||
|
||||
---
|
||||
|
||||
## Breaking API changes
|
||||
|
||||
| Previous API | New API |
|
||||
|---|---|
|
||||
| `JsonParser` | `JsonReader` |
|
||||
| `JsonGenerator` | `JsonWriter` |
|
||||
| `JsonFactory` | `JsonStream` |
|
||||
| `DatabaseBuilder.jsonFactory(...)` | `DatabaseBuilder.jsonStream(...)` |
|
||||
| `DatabaseConfig.getJsonFactory()/setJsonFactory(...)` | `DatabaseConfig.getJsonStream()/setJsonStream(...)` |
|
||||
|
||||
---
|
||||
|
||||
## Typical migration rewrites
|
||||
|
||||
### Parser and generator signatures
|
||||
|
||||
```java
|
||||
// before
|
||||
void read(JsonParser parser)
|
||||
void write(JsonGenerator generator)
|
||||
|
||||
// after
|
||||
void read(JsonReader parser)
|
||||
void write(JsonWriter generator)
|
||||
```
|
||||
|
||||
### Database configuration
|
||||
|
||||
```java
|
||||
// before
|
||||
Database.builder().jsonFactory(factory)
|
||||
|
||||
// after
|
||||
Database.builder().jsonStream(stream)
|
||||
```
|
||||
|
||||
### JSON utility calls
|
||||
|
||||
`EJson` and `JsonContext` APIs now operate on `JsonReader` and `JsonWriter` types.
|
||||
If your code was calling those APIs with Jackson core types, switch to avaje types.
|
||||
|
||||
---
|
||||
|
||||
## Dependency and module notes
|
||||
|
||||
- `ebean-core` no longer requires a direct `jackson-core` dependency for JSON
|
||||
parsing/writing.
|
||||
- `jackson-databind` remains optional for `ObjectMapper` compatibility paths.
|
||||
- `ebean-jackson-mapper` remains the compatibility bridge module for mapper-based
|
||||
integrations.
|
||||
|
||||
---
|
||||
|
||||
## Behavior notes to verify during upgrade
|
||||
|
||||
1. Parser token handling is now based on avaje `JsonReader.Token`.
|
||||
2. Scalar JSON reads (for example booleans, date-time, array scalar types) should
|
||||
be validated in your tests if you previously depended on Jackson token quirks.
|
||||
3. If your integration uses transient assoc-many JSON mapping with ObjectMapper,
|
||||
keep ObjectMapper wiring enabled.
|
||||
|
||||
---
|
||||
|
||||
## Validation checklist
|
||||
|
||||
1. Compile all modules that implement or consume `io.ebean.text.json` APIs.
|
||||
2. Run module tests that cover JSON scalar conversion and bean JSON round-trips.
|
||||
3. Confirm no remaining `com.fasterxml.jackson.core.*` imports in migrated code.
|
||||
4. Keep `ObjectMapper` compatibility tests if your project depends on mapper paths.
|
||||
|
||||
@@ -77,6 +77,7 @@ often the right query shape.
|
||||
| Check if at least one row exists | `exists()` | Cheapest choice for boolean existence checks |
|
||||
| Load exactly one row by ID or unique key | `findOne()` | Only use when the predicate is truly unique |
|
||||
| Load a list of entity beans | `findList()` | Default for list screens and domain logic |
|
||||
| Stream rows, usually to map into another type | `findStream()` | For large/unbounded results streamed from the JDBC cursor; close via try-with-resources. For small/bounded results prefer `findList().stream()` |
|
||||
| Count matching rows | `findCount()` | Prefer over loading entities just to count |
|
||||
| Load a page plus optional total row count | `findPagedList()` | Use when the caller needs pagination metadata |
|
||||
| Return DTO/read-model rows | `asDto(...).findList()` | Prefer this over partially loaded entities for API/view models |
|
||||
@@ -99,6 +100,43 @@ Customer customer = new QCustomer()
|
||||
|
||||
Do **not** use `findOne()` for predicates that can match multiple rows.
|
||||
|
||||
### Example - stream and map to another type
|
||||
|
||||
Choose based on result size and how you consume it:
|
||||
|
||||
- **`findList().stream()`** — executes the query, materialises the rows,
|
||||
**releases the connection**, then streams over an in-memory list. No open
|
||||
database resources and no try-with-resources needed. Prefer this for small or
|
||||
bounded results (e.g. when you apply `setMaxRows`) that you collect anyway.
|
||||
- **`findStream()`** — streams rows directly from the JDBC cursor, holding a
|
||||
connection (and an implicit transaction) open for the **whole lifetime of the
|
||||
stream pipeline**. It must be closed with try-with-resources. Prefer it when
|
||||
the result may be large, when you want constant memory, or when you want to
|
||||
short-circuit (`limit`, `findFirst`, `takeWhile`) without loading everything.
|
||||
|
||||
```java
|
||||
// small, bounded result fully collected -> findList().stream()
|
||||
List<PendingPlan> pending = new QCaptureRequest()
|
||||
.collectedAt.isNull()
|
||||
.orderBy().requestedAt.asc()
|
||||
.findList()
|
||||
.stream()
|
||||
.map(r -> new PendingPlan(r.app().getName(), r.hash()))
|
||||
.toList();
|
||||
|
||||
// large/unbounded result streamed from the cursor -> findStream() + try-with-resources
|
||||
try (Stream<Customer> stream = new QCustomer()
|
||||
.status.equalTo(Status.NEW)
|
||||
.findStream()) {
|
||||
stream
|
||||
.map(...)
|
||||
.forEach(...);
|
||||
}
|
||||
```
|
||||
|
||||
For processing large results one bean at a time, `findEach()` is often the
|
||||
simplest choice because it closes the underlying resources automatically.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 - Build predicates by traversing properties and associations
|
||||
@@ -131,6 +169,49 @@ List<Customer> customers = new QCustomer()
|
||||
.findList();
|
||||
```
|
||||
|
||||
### Optional predicates - prefer conditional helpers over `if` blocks
|
||||
|
||||
When a filter is driven by a nullable/optional parameter, use the built-in
|
||||
conditional helpers instead of wrapping predicates in `if` blocks. The query
|
||||
stays fluent and reads top-to-bottom, and no predicate is added when the value
|
||||
is absent.
|
||||
|
||||
| Helper | Adds predicate when | Resulting SQL |
|
||||
|--------|---------------------|---------------|
|
||||
| `eqIfPresent(v)` | `v != null` | `prop = ?` |
|
||||
| `eqIfNotBlank(v)` (String) | `v` non-null and not blank (value is trimmed) | `prop = ?` |
|
||||
| `eqOrNull(v)` | always | `(prop = ? or prop is null)` |
|
||||
| `inOrEmpty(coll)` | `coll` non-empty | `prop in (...)` (no predicate when empty) |
|
||||
| `likeIfPresent` / `ilikeIfPresent` / `startsWithIfPresent` / `istartsWithIfPresent` / `containsIfPresent` / `icontainsIfPresent` (String) | `v != null` | the match expression |
|
||||
|
||||
```java
|
||||
// Instead of building the query with if blocks:
|
||||
QCustomer q = new QCustomer();
|
||||
if (name != null && !name.isBlank()) {
|
||||
q.name.eq(name.trim());
|
||||
}
|
||||
if (status != null) {
|
||||
q.status.eq(status);
|
||||
}
|
||||
List<Customer> customers = q.findList();
|
||||
|
||||
// Prefer the conditional helpers:
|
||||
List<Customer> customers = new QCustomer()
|
||||
.name.eqIfNotBlank(name)
|
||||
.status.eqIfPresent(status)
|
||||
.findList();
|
||||
```
|
||||
|
||||
Use `eqOrNull(v)` when a null column value should also match - for example an
|
||||
"any environment" row stored with `env_id is null` should surface under any env
|
||||
filter - instead of a hand-rolled `or()/eq()/isNull()/endOr()` block:
|
||||
|
||||
```java
|
||||
List<CaptureRequest> rows = new QCaptureRequest()
|
||||
.env.name.eqOrNull(envFilter) // env_name = ? or env_name is null
|
||||
.findList();
|
||||
```
|
||||
|
||||
### Agent rule
|
||||
|
||||
When adding a new query:
|
||||
@@ -140,6 +221,10 @@ When adding a new query:
|
||||
3. Traverse relationships instead of writing manual join SQL
|
||||
4. Keep property references type-safe; avoid string property names unless the API
|
||||
specifically requires them
|
||||
5. For optional filters, reach for `eqIfPresent` / `eqIfNotBlank` / `inOrEmpty`
|
||||
before writing an `if (param != null)` block, and use `eqOrNull` instead of a
|
||||
manual `or()/eq()/isNull()/endOr()` when the intent is "match this value or a
|
||||
null column"
|
||||
|
||||
---
|
||||
|
||||
|
||||
+5
-7
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
@@ -70,15 +70,13 @@
|
||||
<optional>true</optional>
|
||||
</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>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-core</artifactId>
|
||||
<version>${avaje-json-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope for JsonNode support -->
|
||||
<!-- Jackson databind remains for ObjectMapper compatibility paths -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.annotation.*;
|
||||
import io.ebean.cache.ServerCachePlugin;
|
||||
import io.ebean.config.*;
|
||||
@@ -360,18 +360,18 @@ public interface DatabaseBuilder {
|
||||
DatabaseBuilder putServiceObject(Object configObject);
|
||||
|
||||
/**
|
||||
* Set the Jackson JsonFactory to use.
|
||||
* Set the JsonStream to use.
|
||||
* <p>
|
||||
* If not set a default implementation will be used.
|
||||
*/
|
||||
default DatabaseBuilder jsonFactory(JsonFactory jsonFactory) {
|
||||
return setJsonFactory(jsonFactory);
|
||||
default DatabaseBuilder jsonStream(JsonStream jsonStream) {
|
||||
return setJsonStream(jsonStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated migrate to {@link #jsonFactory(JsonFactory)}.
|
||||
* @deprecated migrate to {@link #jsonStream(JsonStream)}.
|
||||
*/
|
||||
DatabaseBuilder setJsonFactory(JsonFactory jsonFactory);
|
||||
DatabaseBuilder setJsonStream(JsonStream jsonStream);
|
||||
|
||||
/**
|
||||
* Set the JSON format to use for DateTime types.
|
||||
@@ -2254,11 +2254,11 @@ public interface DatabaseBuilder {
|
||||
boolean isAutoLoadModuleInfo();
|
||||
|
||||
/**
|
||||
* Return the Jackson JsonFactory to use.
|
||||
* Return the JsonStream to use.
|
||||
* <p>
|
||||
* If not set a default implementation will be used.
|
||||
*/
|
||||
JsonFactory getJsonFactory();
|
||||
JsonStream getJsonStream();
|
||||
|
||||
/**
|
||||
* Get the clock used for setting the timestamps (e.g. @UpdatedTimestamp) on objects.
|
||||
|
||||
@@ -60,7 +60,8 @@ public class ClassLoadConfig {
|
||||
}
|
||||
|
||||
public boolean isJacksonCorePresent() {
|
||||
return isPresent("com.fasterxml.jackson.core.JsonParser");
|
||||
// Legacy method name retained for compatibility; now checks avaje JSON core.
|
||||
return isPresent("io.avaje.json.JsonReader");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,4 +159,3 @@ public class ClassLoadConfig {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.config;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import io.avaje.config.Config;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.*;
|
||||
import io.ebean.annotation.MutationDetection;
|
||||
import io.ebean.annotation.PersistBatch;
|
||||
@@ -420,7 +420,7 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
* The default PersistenceContextScope used if one is not explicitly set on a query.
|
||||
*/
|
||||
private PersistenceContextScope persistenceContextScope = PersistenceContextScope.TRANSACTION;
|
||||
private JsonFactory jsonFactory;
|
||||
private JsonStream jsonStream;
|
||||
private boolean localTimeWithNanos;
|
||||
private boolean durationWithNanos;
|
||||
private int maxCallStack = 5;
|
||||
@@ -631,13 +631,13 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonFactory getJsonFactory() {
|
||||
return jsonFactory;
|
||||
public JsonStream getJsonStream() {
|
||||
return jsonStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DatabaseConfig setJsonFactory(JsonFactory jsonFactory) {
|
||||
this.jsonFactory = jsonFactory;
|
||||
public DatabaseConfig setJsonStream(JsonStream jsonStream) {
|
||||
this.jsonStream = jsonStream;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Canonical "v2" mapping of Ebean's internal flat metric names (e.g.
|
||||
* {@code orm.Customer.findList}, {@code iud.User.save}, {@code txn.named.X},
|
||||
* {@code l2.<region>.<op>}) into a metric family name plus a tag string following
|
||||
* the label-tag convention.
|
||||
*
|
||||
* <p>This is the source-of-truth mapping for the v2 metrics JSON form
|
||||
* ({@link ServerMetricsAsJson#writeV2(Appendable)}). The tag string is a canonical,
|
||||
* sorted, comma separated list of {@code key:value} pairs, e.g.
|
||||
* {@code "kind:orm,label:Customer.findList,type:Customer"}.
|
||||
*
|
||||
* <table>
|
||||
* <caption>Ebean prefix → family name + tags</caption>
|
||||
* <tr><th>Ebean prefix</th><th>name</th><th>tags</th></tr>
|
||||
* <tr><td>{@code iud.X}</td><td>{@code ebean.dml}</td><td>{@code label=X}</td></tr>
|
||||
* <tr><td>{@code orm.X}</td><td>{@code ebean.query}</td><td>{@code kind=orm, type=<bean>, label=X}</td></tr>
|
||||
* <tr><td>{@code dto.X}</td><td>{@code ebean.query}</td><td>{@code kind=dto, type=<bean>, label=X}</td></tr>
|
||||
* <tr><td>{@code sql.X}</td><td>{@code ebean.query}</td><td>{@code kind=sql, type=<bean>, label=X}</td></tr>
|
||||
* <tr><td>{@code txn.named.X} / {@code txn.X}</td><td>{@code ebean.txn}</td><td>{@code label=X}</td></tr>
|
||||
* <tr><td>{@code l2.<region>.<op>}</td><td>{@code ebean.l2}</td><td>{@code op=<op>, region=<region>}</td></tr>
|
||||
* <tr><td>(unrecognised)</td><td>{@code ebean.other}</td><td>{@code label=<original name>}</td></tr>
|
||||
* </table>
|
||||
*
|
||||
* <p>The {@code kind} tag is the query category (orm/dto/sql) while the {@code type}
|
||||
* tag is the queried bean/entity simple name. The {@code type} tag is omitted when
|
||||
* the bean type is unknown.
|
||||
*/
|
||||
final class MetricNamingV2 {
|
||||
|
||||
/** Result of a name mapping: family name plus canonical tag string. */
|
||||
static final class Mapped {
|
||||
private final String name;
|
||||
private final String tags;
|
||||
|
||||
Mapped(String name, String tags) {
|
||||
this.name = name;
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
String tags() {
|
||||
return tags;
|
||||
}
|
||||
}
|
||||
|
||||
private MetricNamingV2() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an Ebean flat metric name (and optional bean type for query metrics) into
|
||||
* the canonical family name plus tag string.
|
||||
*/
|
||||
static Mapped map(String ebeanName, String beanType) {
|
||||
if (ebeanName == null || ebeanName.isEmpty()) {
|
||||
return new Mapped("ebean.other", "");
|
||||
}
|
||||
int firstDot = ebeanName.indexOf('.');
|
||||
if (firstDot <= 0) {
|
||||
return new Mapped("ebean.other", tags("label", ebeanName));
|
||||
}
|
||||
String prefix = ebeanName.substring(0, firstDot);
|
||||
String rest = ebeanName.substring(firstDot + 1);
|
||||
switch (prefix) {
|
||||
case "iud":
|
||||
return new Mapped("ebean.dml", tags("label", rest));
|
||||
case "orm":
|
||||
return query("orm", rest, beanType);
|
||||
case "dto":
|
||||
return query("dto", rest, beanType);
|
||||
case "sql":
|
||||
return query("sql", rest, beanType);
|
||||
case "txn":
|
||||
String txnLabel = rest.startsWith("named.") ? rest.substring("named.".length()) : rest;
|
||||
return new Mapped("ebean.txn", tags("label", txnLabel));
|
||||
case "l2":
|
||||
return l2(rest);
|
||||
default:
|
||||
return new Mapped("ebean.other", tags("label", ebeanName));
|
||||
}
|
||||
}
|
||||
|
||||
private static Mapped query(String kind, String label, String beanType) {
|
||||
if (beanType == null || beanType.isEmpty()) {
|
||||
return new Mapped("ebean.query", tags("kind", kind, "label", label));
|
||||
}
|
||||
return new Mapped("ebean.query", tags("kind", kind, "type", beanType, "label", label));
|
||||
}
|
||||
|
||||
private static Mapped l2(String rest) {
|
||||
int dot = rest.indexOf('.');
|
||||
if (dot <= 0) {
|
||||
return new Mapped("ebean.l2", tags("op", rest));
|
||||
}
|
||||
String region = rest.substring(0, dot);
|
||||
String op = rest.substring(dot + 1);
|
||||
return new Mapped("ebean.l2", tags("op", op, "region", region));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a canonical (sorted) {@code key:value,key2:value2} tag string from the given
|
||||
* key/value pairs, skipping null/empty values and sanitising the reserved
|
||||
* delimiter characters from values.
|
||||
*/
|
||||
private static String tags(String... keyValues) {
|
||||
List<String> pairs = new ArrayList<>(keyValues.length / 2);
|
||||
for (int i = 0; i + 1 < keyValues.length; i += 2) {
|
||||
String value = keyValues[i + 1];
|
||||
if (value != null && !value.isEmpty()) {
|
||||
pairs.add(keyValues[i] + ':' + sanitize(value));
|
||||
}
|
||||
}
|
||||
Collections.sort(pairs);
|
||||
return String.join(",", pairs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the reserved tag delimiter characters ({@code ,} and {@code :}) so they
|
||||
* cannot break the {@code key:value,key2:value2} encoding.
|
||||
*/
|
||||
private static String sanitize(String value) {
|
||||
if (value.indexOf(',') < 0 && value.indexOf(':') < 0) {
|
||||
return value;
|
||||
}
|
||||
return value.replace(',', '_').replace(':', '_');
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ final class MetricsAsJson implements ServerMetricsAsJson {
|
||||
private Comparator<MetaTimedMetric> sortBy = SortMetric.NAME;
|
||||
private int listCounter;
|
||||
private int objKeyCounter;
|
||||
private boolean v2;
|
||||
|
||||
MetricsAsJson(ServerMetrics metrics) {
|
||||
this.metrics = metrics;
|
||||
@@ -67,6 +68,13 @@ final class MetricsAsJson implements ServerMetricsAsJson {
|
||||
collect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeV2(Appendable buffer) {
|
||||
this.v2 = true;
|
||||
this.writer = buffer;
|
||||
collect();
|
||||
}
|
||||
|
||||
private void collect() {
|
||||
try {
|
||||
start();
|
||||
@@ -151,12 +159,26 @@ final class MetricsAsJson implements ServerMetricsAsJson {
|
||||
}
|
||||
|
||||
private void metricStart(MetaMetric metric) throws IOException {
|
||||
metricStart(metric, null);
|
||||
}
|
||||
|
||||
private void metricStart(MetaMetric metric, String beanType) throws IOException {
|
||||
if (listCounter++ > 0) {
|
||||
writer.append(',').append(newLine);
|
||||
}
|
||||
objStart();
|
||||
key("name");
|
||||
val(metric.name());
|
||||
if (v2) {
|
||||
MetricNamingV2.Mapped mapped = MetricNamingV2.map(metric.name(), beanType);
|
||||
key("name");
|
||||
val(mapped.name());
|
||||
if (!mapped.tags().isEmpty()) {
|
||||
key("tags");
|
||||
val(mapped.tags());
|
||||
}
|
||||
} else {
|
||||
key("name");
|
||||
val(metric.name());
|
||||
}
|
||||
}
|
||||
|
||||
private void metricEnd() throws IOException {
|
||||
@@ -180,7 +202,8 @@ final class MetricsAsJson implements ServerMetricsAsJson {
|
||||
}
|
||||
|
||||
private void logQuery(MetaQueryMetric metric) throws IOException {
|
||||
metricStart(metric);
|
||||
Class<?> beanType = metric.type();
|
||||
metricStart(metric, beanType == null ? null : beanType.getSimpleName());
|
||||
appendTiming(metric);
|
||||
if (withHash) {
|
||||
append("hash", metric.hash());
|
||||
|
||||
@@ -41,6 +41,17 @@ public interface ServerMetricsAsJson {
|
||||
*/
|
||||
void write(Appendable buffer);
|
||||
|
||||
/**
|
||||
* Collect and write metrics as "v2" JSON to the given buffer.
|
||||
* <p>
|
||||
* The v2 form uses the canonical label-tag convention: each metric is written with
|
||||
* a family {@code name} (e.g. {@code ebean.query}, {@code ebean.dml}) plus a
|
||||
* {@code tags} string of sorted {@code key:value} pairs (e.g.
|
||||
* {@code "kind:orm,label:Customer.findList,type:Customer"}) rather than the flat
|
||||
* prefixed name. Timing, hash, location and sql attributes are unchanged.
|
||||
*/
|
||||
void writeV2(Appendable buffer);
|
||||
|
||||
/**
|
||||
* Return the metrics in raw JSON.
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,6 @@ public interface MetricFactory extends BootstrapService {
|
||||
/**
|
||||
* Create a Timed metric.
|
||||
*/
|
||||
QueryPlanMetric createQueryPlanMetric(Class<?> type, String label, ProfileLocation profileLocation, String sql);
|
||||
QueryPlanMetric createQueryPlanMetric(Class<?> type, String name, String label, ProfileLocation profileLocation, String sql, String hash);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
@@ -32,12 +32,12 @@ public interface SpiJsonService extends BootstrapService {
|
||||
/**
|
||||
* Write the nested Map/List as json to the jsonGenerator.
|
||||
*/
|
||||
void write(Object object, JsonGenerator jsonGenerator) throws IOException;
|
||||
void write(Object object, JsonWriter jsonGenerator) throws IOException;
|
||||
|
||||
/**
|
||||
* Write the collection as json array to the jsonGenerator.
|
||||
*/
|
||||
void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException;
|
||||
void writeCollection(Collection<Object> collection, JsonWriter jsonGenerator) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map additionally specifying if the returned map should
|
||||
@@ -61,17 +61,17 @@ public interface SpiJsonService extends BootstrapService {
|
||||
Map<String, Object> parseObject(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser.
|
||||
* Parse the json and return as a Map taking a JsonReader.
|
||||
*/
|
||||
Map<String, Object> parseObject(JsonParser parser) throws IOException;
|
||||
Map<String, Object> parseObject(JsonReader parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser and a starting token.
|
||||
* Parse the json and return as a Map taking a JsonReader and a starting token.
|
||||
* <p>
|
||||
* Used when the first token is checked to see if the value is null prior to calling this.
|
||||
* </p>
|
||||
*/
|
||||
Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException;
|
||||
Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a modify aware List.
|
||||
@@ -89,14 +89,14 @@ public interface SpiJsonService extends BootstrapService {
|
||||
List<Object> parseList(Reader reader) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a JsonParser.
|
||||
* Parse the json and return as a List taking a JsonReader.
|
||||
*/
|
||||
List<Object> parseList(JsonParser parser) throws IOException;
|
||||
List<Object> parseList(JsonReader parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
<T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException;
|
||||
<T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
@@ -111,7 +111,7 @@ public interface SpiJsonService extends BootstrapService {
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
Object parse(JsonParser parser) throws IOException;
|
||||
Object parse(JsonReader parser) throws IOException;
|
||||
|
||||
/**
|
||||
* Parse the json returning a Set that might be modify aware.
|
||||
@@ -121,5 +121,5 @@ public interface SpiJsonService extends BootstrapService {
|
||||
/**
|
||||
* Parse the json returning as a Set taking into account the current token.
|
||||
*/
|
||||
<T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException;
|
||||
<T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package io.ebean.text.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.XBootstrapService;
|
||||
import io.ebean.service.SpiJsonService;
|
||||
|
||||
@@ -38,14 +37,14 @@ public class EJson {
|
||||
/**
|
||||
* Write the nested Map/List as json to the jsonGenerator.
|
||||
*/
|
||||
public static void write(Object object, JsonGenerator jsonGenerator) throws IOException {
|
||||
public static void write(Object object, io.avaje.json.JsonWriter jsonGenerator) throws IOException {
|
||||
plugin.write(object, jsonGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the collection as json array to the jsonGenerator.
|
||||
*/
|
||||
public static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
|
||||
public static void writeCollection(Collection<Object> collection, io.avaje.json.JsonWriter jsonGenerator) throws IOException {
|
||||
plugin.writeCollection(collection, jsonGenerator);
|
||||
}
|
||||
|
||||
@@ -79,19 +78,19 @@ public class EJson {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser.
|
||||
* Parse the json and return as a Map taking a JsonReader.
|
||||
*/
|
||||
public static Map<String, Object> parseObject(JsonParser parser) throws IOException {
|
||||
public static Map<String, Object> parseObject(JsonReader parser) throws IOException {
|
||||
return plugin.parseObject(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser and a starting token.
|
||||
* Parse the json and return as a Map taking a JsonReader and a starting token.
|
||||
* <p>
|
||||
* Used when the first token is checked to see if the value is null prior to calling this.
|
||||
* </p>
|
||||
*/
|
||||
public static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
public static Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
|
||||
return plugin.parseObject(parser, token);
|
||||
}
|
||||
|
||||
@@ -117,16 +116,16 @@ public class EJson {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a JsonParser.
|
||||
* Parse the json and return as a List taking a JsonReader.
|
||||
*/
|
||||
public static List<Object> parseList(JsonParser parser) throws IOException {
|
||||
public static List<Object> parseList(JsonReader parser) throws IOException {
|
||||
return plugin.parseList(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
public static <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
public static <T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException {
|
||||
return plugin.parseList(parser, currentToken);
|
||||
}
|
||||
|
||||
@@ -147,7 +146,7 @@ public class EJson {
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
public static Object parse(JsonParser parser) throws IOException {
|
||||
public static Object parse(JsonReader parser) throws IOException {
|
||||
return plugin.parse(parser);
|
||||
}
|
||||
|
||||
@@ -161,7 +160,7 @@ public class EJson {
|
||||
/**
|
||||
* Parse the json returning as a Set taking into account the current token.
|
||||
*/
|
||||
public static <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
public static <T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException {
|
||||
return plugin.parseSet(parser, currentToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean.text.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
|
||||
/**
|
||||
@@ -25,9 +25,9 @@ public interface JsonBeanReader<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new reader taking the context from the existing one but using a new JsonParser.
|
||||
* Create a new reader taking the context from the existing one but using a new JsonReader.
|
||||
*/
|
||||
JsonBeanReader<T> forJson(JsonParser moreJson);
|
||||
JsonBeanReader<T> forJson(JsonReader moreJson);
|
||||
|
||||
/**
|
||||
* Add a bean explicitly to the persistence context.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package io.ebean.text.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.plugin.BeanType;
|
||||
|
||||
@@ -49,14 +48,14 @@ public interface JsonContext {
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException;
|
||||
<T> T toBean(Class<T> cls, JsonReader parser) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json parser input into a Bean of a specific type additionally using JsonReadOptions..
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
|
||||
<T> T toBean(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Read json parser input into a given Bean. <br>
|
||||
@@ -65,19 +64,19 @@ public interface JsonContext {
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> void toBean(T target, JsonParser parser) throws JsonIOException;
|
||||
<T> void toBean(T target, JsonReader parser) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Read json parser input into a given Bean additionally using JsonReadOptions.<br>
|
||||
* See {@link #toBean(Class, JsonParser)} for details modified.
|
||||
* See {@link #toBean(Class, JsonReader)} for details modified.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException;
|
||||
<T> void toBean(T target, JsonReader parser, JsonReadOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Read json reader input into a given Bean.<br>
|
||||
* See {@link #toBean(Class, JsonParser)} for details
|
||||
* See {@link #toBean(Class, JsonReader)} for details
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
@@ -85,7 +84,7 @@ public interface JsonContext {
|
||||
|
||||
/**
|
||||
* Read json reader input into a given Bean additionally using JsonReadOptions.<br>
|
||||
* See {@link #toBean(Class, JsonParser)} for details modified.
|
||||
* See {@link #toBean(Class, JsonReader)} for details modified.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
@@ -93,7 +92,7 @@ public interface JsonContext {
|
||||
|
||||
/**
|
||||
* Read json string input into a given Bean.<br>
|
||||
* See {@link #toBean(Class, JsonParser)} for details
|
||||
* See {@link #toBean(Class, JsonReader)} for details
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
@@ -101,7 +100,7 @@ public interface JsonContext {
|
||||
|
||||
/**
|
||||
* Read json string input into a given Bean additionally using JsonReadOptions.<br>
|
||||
* See {@link #toBean(Class, JsonParser)} for details
|
||||
* See {@link #toBean(Class, JsonReader)} for details
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
@@ -113,7 +112,7 @@ public interface JsonContext {
|
||||
* Note that JsonOption provides an option for setting a persistence context and also enabling further lazy loading. Further lazy
|
||||
* loading requires a persistence context so if that is set on then a persistence context is created if there is not one set.
|
||||
*/
|
||||
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException;
|
||||
<T> JsonBeanReader<T> createBeanReader(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Create and return a new bean reading for the bean type given the JSON options and source.
|
||||
@@ -122,7 +121,7 @@ public interface JsonContext {
|
||||
* further lazy loading. Further lazy loading requires a persistence context so if that is set
|
||||
* on then a persistence context is created if there is not one set.
|
||||
*/
|
||||
<T> JsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException;
|
||||
<T> JsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonReader parser, JsonReadOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json string input into a list of beans of a specific type.
|
||||
@@ -157,14 +156,14 @@ public interface JsonContext {
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> List<T> toList(Class<T> cls, JsonParser json) throws JsonIOException;
|
||||
<T> List<T> toList(Class<T> cls, JsonReader json) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Convert json parser input into a list of beans of a specific type additionally using JsonReadOptions.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
<T> List<T> toList(Class<T> cls, JsonParser json, JsonReadOptions options) throws JsonIOException;
|
||||
<T> List<T> toList(Class<T> cls, JsonReader json, JsonReadOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Use the genericType to determine if this should be converted into a List or
|
||||
@@ -188,7 +187,7 @@ public interface JsonContext {
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException;
|
||||
Object toObject(Type genericType, JsonReader jsonParser) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Return the bean or collection as JSON string.
|
||||
@@ -212,11 +211,11 @@ public interface JsonContext {
|
||||
void toJson(Object value, Writer writer) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Write the bean or collection to the JsonGenerator.
|
||||
* Write the bean or collection to the JsonWriter.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
void toJson(Object value, JsonGenerator generator) throws JsonIOException;
|
||||
void toJson(Object value, io.avaje.json.JsonWriter generator) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Return the bean or collection as JSON string using FetchPath.
|
||||
@@ -231,15 +230,15 @@ public interface JsonContext {
|
||||
void toJson(Object value, Writer writer, FetchPath fetchPath) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Write the bean or collection to the JsonGenerator using the FetchPath.
|
||||
* Write the bean or collection to the JsonWriter using the FetchPath.
|
||||
*/
|
||||
void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException;
|
||||
void toJson(Object value, io.avaje.json.JsonWriter generator, FetchPath fetchPath) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Deprecated in favour of using PathProperties by itself.
|
||||
* Write json to the JsonGenerator using the JsonWriteOptions.
|
||||
* Write json to the JsonWriter using the JsonWriteOptions.
|
||||
*/
|
||||
void toJson(Object value, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException;
|
||||
void toJson(Object value, io.avaje.json.JsonWriter generator, JsonWriteOptions options) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Deprecated in favour of using PathProperties by itself.
|
||||
@@ -264,27 +263,27 @@ public interface JsonContext {
|
||||
boolean isSupportedType(Type genericType);
|
||||
|
||||
/**
|
||||
* Create and return a new JsonGenerator for the given writer.
|
||||
* Create and return a new JsonWriter for the given writer.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
JsonGenerator createGenerator(Writer writer) throws JsonIOException;
|
||||
io.avaje.json.JsonWriter createGenerator(Writer writer) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Create and return a new JsonParser for the given reader.
|
||||
* Create and return a new JsonReader for the given reader.
|
||||
*
|
||||
* @throws JsonIOException When IOException occurs
|
||||
*/
|
||||
JsonParser createParser(Reader reader) throws JsonIOException;
|
||||
JsonReader createParser(Reader reader) throws JsonIOException;
|
||||
|
||||
/**
|
||||
* Write a scalar types known to Ebean to Jackson.
|
||||
* Write scalar types known to Ebean to JsonWriter.
|
||||
* <p>
|
||||
* Ebean has built in support for java8 and Joda types as well as the other
|
||||
* standard JDK types like URI, URL, UUID etc. This is a fast simple way to
|
||||
* write any of those types to Jackson.
|
||||
* write any of those types.
|
||||
* </p>
|
||||
*/
|
||||
void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException;
|
||||
void writeScalar(io.avaje.json.JsonWriter generator, Object scalarValue) throws IOException;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package io.ebean.text.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* Wraps an underlying JsonGenerator taking into account null suppression and exposing isIncludeEmpty() etc.
|
||||
* Wraps an underlying JsonWriter taking into account null suppression and exposing isIncludeEmpty() etc.
|
||||
*/
|
||||
public interface JsonWriter {
|
||||
|
||||
/**
|
||||
* Return the Jackson core JsonGenerator.
|
||||
* Return the underlying JsonWriter.
|
||||
*/
|
||||
JsonGenerator gen();
|
||||
io.avaje.json.JsonWriter gen();
|
||||
|
||||
/**
|
||||
* Return true if null values should be included in JSON output.
|
||||
|
||||
@@ -8,6 +8,7 @@ module io.ebean.api {
|
||||
|
||||
requires transitive java.sql;
|
||||
requires transitive io.avaje.config;
|
||||
requires transitive io.avaje.json;
|
||||
requires transitive org.jspecify;
|
||||
requires transitive jakarta.persistence.api;
|
||||
requires transitive io.ebean.annotation;
|
||||
@@ -16,7 +17,6 @@ module io.ebean.api {
|
||||
|
||||
requires static org.slf4j;
|
||||
requires static io.ebean.types;
|
||||
requires static com.fasterxml.jackson.core;
|
||||
requires static com.fasterxml.jackson.databind;
|
||||
|
||||
exports io.ebean;
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class MetricNamingV2Test {
|
||||
|
||||
private MetricNamingV2.Mapped map(String name, String beanType) {
|
||||
return MetricNamingV2.map(name, beanType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orm_withBeanType() {
|
||||
MetricNamingV2.Mapped m = map("orm.Customer.findList", "Customer");
|
||||
assertThat(m.name()).isEqualTo("ebean.query");
|
||||
assertThat(m.tags()).isEqualTo("kind:orm,label:Customer.findList,type:Customer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void orm_withoutBeanType() {
|
||||
MetricNamingV2.Mapped m = map("orm.Customer.findList", null);
|
||||
assertThat(m.name()).isEqualTo("ebean.query");
|
||||
assertThat(m.tags()).isEqualTo("kind:orm,label:Customer.findList");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dto_andSql() {
|
||||
assertThat(map("dto.CustomerDto.findRecent", "CustomerDto").tags())
|
||||
.isEqualTo("kind:dto,label:CustomerDto.findRecent,type:CustomerDto");
|
||||
assertThat(map("sql.query.fooBar", "Customer").tags())
|
||||
.isEqualTo("kind:sql,label:query.fooBar,type:Customer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void iud() {
|
||||
MetricNamingV2.Mapped m = map("iud.User.save", null);
|
||||
assertThat(m.name()).isEqualTo("ebean.dml");
|
||||
assertThat(m.tags()).isEqualTo("label:User.save");
|
||||
}
|
||||
|
||||
@Test
|
||||
void txn_named_and_plain() {
|
||||
assertThat(map("txn.named.ProcessJob", null).name()).isEqualTo("ebean.txn");
|
||||
assertThat(map("txn.named.ProcessJob", null).tags()).isEqualTo("label:ProcessJob");
|
||||
assertThat(map("txn.main", null).tags()).isEqualTo("label:main");
|
||||
}
|
||||
|
||||
@Test
|
||||
void l2_regionAndOp() {
|
||||
MetricNamingV2.Mapped m = map("l2.customer.hit", null);
|
||||
assertThat(m.name()).isEqualTo("ebean.l2");
|
||||
assertThat(m.tags()).isEqualTo("op:hit,region:customer");
|
||||
}
|
||||
|
||||
@Test
|
||||
void l2_opOnly() {
|
||||
assertThat(map("l2.hit", null).tags()).isEqualTo("op:hit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unrecognisedPrefix_isOther() {
|
||||
MetricNamingV2.Mapped m = map("l2n.Customer.hit", null);
|
||||
assertThat(m.name()).isEqualTo("ebean.other");
|
||||
assertThat(m.tags()).isEqualTo("label:l2n.Customer.hit");
|
||||
}
|
||||
|
||||
@Test
|
||||
void noDot_isOther() {
|
||||
assertThat(map("jvm", null).name()).isEqualTo("ebean.other");
|
||||
assertThat(map("jvm", null).tags()).isEqualTo("label:jvm");
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullOrEmpty() {
|
||||
assertThat(map(null, null).name()).isEqualTo("ebean.other");
|
||||
assertThat(map(null, null).tags()).isEmpty();
|
||||
assertThat(map("", null).tags()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanitisesReservedChars() {
|
||||
MetricNamingV2.Mapped m = map("orm.Customer.weird", "Cust:om,er");
|
||||
assertThat(m.tags()).isEqualTo("kind:orm,label:Customer.weird,type:Cust_om_er");
|
||||
}
|
||||
|
||||
@Test
|
||||
void tagsAreSortedByKey() {
|
||||
// kind < label < type alphabetically regardless of build order
|
||||
assertThat(map("orm.X.find", "Bean").tags())
|
||||
.isEqualTo("kind:orm,label:X.find,type:Bean");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package io.ebean.meta;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class MetricsAsJsonV2Test {
|
||||
|
||||
@Test
|
||||
void writeV2_usesFamilyNamesAndTags() {
|
||||
ServerMetrics metrics = new FakeServerMetrics();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
new MetricsAsJson(metrics).writeV2(sb);
|
||||
String json = sb.toString();
|
||||
|
||||
assertThat(json).contains("\"db\":\"db1\"");
|
||||
// query metric -> ebean.query with kind/type/label tags
|
||||
assertThat(json).contains("\"name\":\"ebean.query\"");
|
||||
assertThat(json).contains("\"tags\":\"kind:orm,label:Customer.findList,type:Customer\"");
|
||||
// timed iud metric -> ebean.dml
|
||||
assertThat(json).contains("\"name\":\"ebean.dml\"");
|
||||
assertThat(json).contains("\"tags\":\"label:User.save\"");
|
||||
// count metric (l2n not specially mapped) -> ebean.other
|
||||
assertThat(json).contains("\"name\":\"ebean.other\"");
|
||||
assertThat(json).contains("\"tags\":\"label:l2n.Customer.hit\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void write_v1_unchanged_usesFlatNames() {
|
||||
ServerMetrics metrics = new FakeServerMetrics();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
new MetricsAsJson(metrics).write(sb);
|
||||
String json = sb.toString();
|
||||
|
||||
assertThat(json).contains("\"name\":\"orm.Customer.findList\"");
|
||||
assertThat(json).contains("\"name\":\"iud.User.save\"");
|
||||
assertThat(json).doesNotContain("\"tags\"");
|
||||
}
|
||||
|
||||
static final class FakeServerMetrics implements ServerMetrics {
|
||||
@Override
|
||||
public String name() {
|
||||
return "db1";
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerMetricsAsJson asJson() {
|
||||
return new MetricsAsJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetricData> asData() {
|
||||
return new java.util.ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaTimedMetric> timedMetrics() {
|
||||
return new java.util.ArrayList<>(List.of(new FakeTimed("iud.User.save")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaQueryMetric> queryMetrics() {
|
||||
return new java.util.ArrayList<>(List.of(new FakeQuery("orm.Customer.findList", Customer.class)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MetaCountMetric> countMetrics() {
|
||||
return new java.util.ArrayList<>(List.of(new FakeCount("l2n.Customer.hit")));
|
||||
}
|
||||
}
|
||||
|
||||
static class Customer {
|
||||
}
|
||||
|
||||
static class FakeTimed implements MetaTimedMetric {
|
||||
private final String name;
|
||||
|
||||
FakeTimed(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String location() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long total() {
|
||||
return 30;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long max() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long mean() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialCollection() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static final class FakeQuery extends FakeTimed implements MetaQueryMetric {
|
||||
private final Class<?> type;
|
||||
|
||||
FakeQuery(String name, Class<?> type) {
|
||||
super(name);
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String label() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sql() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash() {
|
||||
return "h1";
|
||||
}
|
||||
}
|
||||
|
||||
static final class FakeCount implements MetaCountMetric {
|
||||
private final String name;
|
||||
|
||||
FakeCount(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long count() {
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-bench</artifactId>
|
||||
|
||||
+28
-28
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -89,25 +89,25 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -125,13 +125,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -155,37 +155,37 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-spring-txn</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- platforms -->
|
||||
@@ -193,91 +193,91 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-clickhouse</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-db2</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-h2</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-hana</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mariadb</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mysql</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-nuodb</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-oracle</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlite</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlserver</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<name>ebean-core-json</name>
|
||||
@@ -16,15 +16,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.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>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-core</artifactId>
|
||||
<version>${avaje-json-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -1,190 +1,170 @@
|
||||
package io.ebeaninternal.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.avaje.json.mapper.JsonMapper;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.service.SpiJsonService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Utility that converts between JSON content and simple java Maps/Lists.
|
||||
* <p>
|
||||
* Backed by avaje {@link JsonMapper} using {@link EbeanJsonAdapter} which
|
||||
* preserves Ebean's modify-aware collection and number semantics.
|
||||
*/
|
||||
public final class DJsonService implements SpiJsonService {
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json.
|
||||
*/
|
||||
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
|
||||
private static final JsonMapper MAPPER = JsonMapper.builder().jsonStream(JSON_STREAM).build();
|
||||
|
||||
private static final JsonMapper.Type<Object> PLAIN = MAPPER.type(EbeanJsonAdapter.PLAIN);
|
||||
private static final JsonMapper.Type<Object> MODIFY_AWARE = MAPPER.type(EbeanJsonAdapter.MODIFY_AWARE);
|
||||
|
||||
private static JsonMapper.Type<Object> type(boolean modifyAware) {
|
||||
return modifyAware ? MODIFY_AWARE : PLAIN;
|
||||
}
|
||||
|
||||
private static boolean blank(String content) {
|
||||
return content == null || content.trim().isEmpty();
|
||||
}
|
||||
|
||||
private static String readAll(Reader reader) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char[] buffer = new char[2048];
|
||||
int len;
|
||||
while ((len = reader.read(buffer)) != -1) {
|
||||
builder.append(buffer, 0, len);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String write(Object object) throws IOException {
|
||||
return EJsonWriter.write(object);
|
||||
StringWriter writer = new StringWriter();
|
||||
write(object, writer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the writer.
|
||||
*/
|
||||
@Override
|
||||
public void write(Object object, Writer writer) throws IOException {
|
||||
EJsonWriter.write(object, writer);
|
||||
JsonWriter jsonWriter = JSON_STREAM.writer(writer);
|
||||
jsonWriter.serializeNulls(true);
|
||||
PLAIN.toJson(object, jsonWriter);
|
||||
jsonWriter.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the nested Map/List as json to the jsonGenerator.
|
||||
*/
|
||||
@Override
|
||||
public void write(Object object, JsonGenerator jsonGenerator) throws IOException {
|
||||
EJsonWriter.write(object, jsonGenerator);
|
||||
public void write(Object object, JsonWriter jsonWriter) throws IOException {
|
||||
PLAIN.toJson(object, jsonWriter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the collection as json array to the jsonGenerator.
|
||||
*/
|
||||
@Override
|
||||
public void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
|
||||
EJsonWriter.writeCollection(collection, jsonGenerator);
|
||||
public void writeCollection(Collection<Object> collection, JsonWriter jsonWriter) throws IOException {
|
||||
EbeanJsonAdapter.writeCollection(jsonWriter, collection);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
return EJsonReader.parseObject(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(String json) throws IOException {
|
||||
return EJsonReader.parseObject(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseObject(reader, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a reader.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(Reader reader) throws IOException {
|
||||
return EJsonReader.parseObject(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a Map taking a JsonParser.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parseObject(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
return EJsonReader.parseObject(parser, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a modify aware List.
|
||||
*/
|
||||
@Override
|
||||
public <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
|
||||
return EJsonReader.parseList(json, modifyAware);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List.
|
||||
*/
|
||||
@Override
|
||||
public List<Object> parseList(String json) throws IOException {
|
||||
return EJsonReader.parseList(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a Reader.
|
||||
*/
|
||||
@Override
|
||||
public List<Object> parseList(Reader reader) throws IOException {
|
||||
return EJsonReader.parseList(reader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List taking a JsonParser.
|
||||
*/
|
||||
@Override
|
||||
public List<Object> parseList(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parseList(parser, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning as a List taking into account the current token.
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> parseList(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
return (List<T>) EJsonReader.parse(parser, currentToken, false);
|
||||
public Map<String, Object> parseObject(String json, boolean modifyAware) throws IOException {
|
||||
return blank(json) ? null : (Map<String, Object>) type(modifyAware).fromJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> parseObject(String json) throws IOException {
|
||||
return parseObject(json, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return parseObject(readAll(reader), modifyAware);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> parseObject(Reader reader) throws IOException {
|
||||
return parseObject(reader, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> parseObject(JsonReader parser) throws IOException {
|
||||
return (Map<String, Object>) PLAIN.fromJson(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Object> parseObject(JsonReader parser, Token token) throws IOException {
|
||||
return (Map<String, Object>) EbeanJsonAdapter.read(parser, token, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
|
||||
return blank(json) ? null : (List<T>) type(modifyAware).fromJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Object> parseList(String json) throws IOException {
|
||||
return (List<Object>) parseList(json, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> parseList(Reader reader) throws IOException {
|
||||
return parseList(readAll(reader));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<Object> parseList(JsonReader parser) throws IOException {
|
||||
return (List<Object>) PLAIN.fromJson(parser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> parseList(JsonReader parser, Token currentToken) throws IOException {
|
||||
return (List<T>) EbeanJsonAdapter.read(parser, currentToken, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
@Override
|
||||
public Object parse(String json) throws IOException {
|
||||
return EJsonReader.parse(json);
|
||||
return blank(json) ? null : PLAIN.fromJson(json);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
@Override
|
||||
public Object parse(Reader reader) throws IOException {
|
||||
return EJsonReader.parse(reader);
|
||||
return parse(readAll(reader));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json and return as a List or Map.
|
||||
*/
|
||||
@Override
|
||||
public Object parse(JsonParser parser) throws IOException {
|
||||
return EJsonReader.parse(parser);
|
||||
public Object parse(JsonReader parser) throws IOException {
|
||||
return PLAIN.fromJson(parser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning a Set that might be modify aware.
|
||||
*/
|
||||
@Override
|
||||
public <T> Set<T> parseSet(String json, boolean modifyAware) throws IOException {
|
||||
List<T> list = parseList(json, modifyAware);
|
||||
if (list == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (modifyAware) {
|
||||
return ((ModifyAwareList<T>) list).asSet();
|
||||
} else {
|
||||
return new LinkedHashSet<>(list);
|
||||
}
|
||||
return new LinkedHashSet<>(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the json returning as a Set taking into account the current token.
|
||||
*/
|
||||
@Override
|
||||
public <T> Set<T> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
|
||||
public <T> Set<T> parseSet(JsonReader parser, Token currentToken) throws IOException {
|
||||
return new LinkedHashSet<>(parseList(parser, currentToken));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
package io.ebeaninternal.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.ebean.ModifyAwareType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
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 {
|
||||
return (Map<String, Object>) parse(json, modifyAware);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(String json) throws IOException {
|
||||
return (Map<String, Object>) parse(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(Reader reader) throws IOException {
|
||||
return (Map<String, Object>) parse(reader);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(Reader reader, boolean modifyAware) throws IOException {
|
||||
return (Map<String, Object>) parse(reader, modifyAware);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(JsonParser parser) throws IOException {
|
||||
return (Map<String, Object>) parse(parser);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static Map<String, Object> parseObject(JsonParser parser, JsonToken token) throws IOException {
|
||||
return (Map<String, Object>) parse(parser, token, false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> List<T> parseList(String json, boolean modifyAware) throws IOException {
|
||||
return (List<T>) parse(json, modifyAware);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Object> parseList(String json) throws IOException {
|
||||
return (List<Object>) parse(json);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Object> parseList(Reader reader) throws IOException {
|
||||
return (List<Object>) parse(reader);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<Object> parseList(JsonParser parser, boolean modifyAware) throws IOException {
|
||||
return (List<Object>) parse(parser, modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(String json) throws IOException {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return parse(new StringReader(json));
|
||||
}
|
||||
|
||||
static Object parse(String json, boolean modifyAware) throws IOException {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
return parse(new StringReader(json), modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(Reader reader) throws IOException {
|
||||
return parse(json.createParser(reader));
|
||||
}
|
||||
|
||||
static Object parse(Reader reader, boolean modifyAware) throws IOException {
|
||||
return parse(json.createParser(reader), modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser) throws IOException {
|
||||
return parse(parser, null, false);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser, boolean modifyAware) throws IOException {
|
||||
return parse(parser, null, modifyAware);
|
||||
}
|
||||
|
||||
static Object parse(JsonParser parser, JsonToken token, boolean modifyAware) throws IOException {
|
||||
return new EJsonReader(parser, modifyAware).parseJson(token);
|
||||
}
|
||||
|
||||
private void startArray() {
|
||||
depth++;
|
||||
stack.push(currentContext);
|
||||
currentContext = modifyAware ? new ArrayContext(modifyAwareOwner) : new ArrayContext();
|
||||
}
|
||||
|
||||
private void startObject() {
|
||||
depth++;
|
||||
stack.push(currentContext);
|
||||
currentContext = modifyAware ? new ObjectContext(modifyAwareOwner) : new ObjectContext();
|
||||
}
|
||||
|
||||
private void endArray() {
|
||||
end();
|
||||
}
|
||||
|
||||
private void endObject() {
|
||||
end();
|
||||
}
|
||||
|
||||
private void end() {
|
||||
depth--;
|
||||
if (!stack.isEmpty()) {
|
||||
currentContext = stack.pop(currentContext);
|
||||
}
|
||||
if (modifyAwareOwner != null) {
|
||||
modifyAwareOwner.setMarkedDirty(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void setValue(Object value) {
|
||||
currentContext.setValue(value);
|
||||
}
|
||||
|
||||
private void setValueNull() {
|
||||
currentContext.setValueNull();
|
||||
}
|
||||
|
||||
private Object parseJson(JsonToken token) throws IOException {
|
||||
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
// if it is a simple value just return it
|
||||
switch (token) {
|
||||
case VALUE_NULL:
|
||||
return null;
|
||||
case VALUE_FALSE:
|
||||
return Boolean.FALSE;
|
||||
case VALUE_TRUE:
|
||||
return Boolean.TRUE;
|
||||
case VALUE_STRING:
|
||||
return parser.getText();
|
||||
case VALUE_NUMBER_INT:
|
||||
return parser.getLongValue();
|
||||
case VALUE_NUMBER_FLOAT:
|
||||
return parser.getDecimalValue();
|
||||
}
|
||||
}
|
||||
|
||||
// it is a object or array, process the first JsonToken
|
||||
stack = new Stack();
|
||||
processJsonToken(token);
|
||||
|
||||
// process the rest of the object or array
|
||||
while (depth > 0) {
|
||||
token = parser.nextToken();
|
||||
processJsonToken(token);
|
||||
}
|
||||
|
||||
return currentContext.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the JsonToken for objects and arrays.
|
||||
*/
|
||||
private void processJsonToken(JsonToken token) throws IOException {
|
||||
switch (token) {
|
||||
case START_ARRAY:
|
||||
startArray();
|
||||
break;
|
||||
|
||||
case START_OBJECT:
|
||||
startObject();
|
||||
break;
|
||||
|
||||
case FIELD_NAME:
|
||||
currentContext.setKey(parser.getCurrentName());
|
||||
break;
|
||||
|
||||
case VALUE_STRING:
|
||||
setValue(parser.getValueAsString());
|
||||
break;
|
||||
|
||||
case VALUE_NUMBER_INT:
|
||||
setValue(parser.getLongValue());
|
||||
break;
|
||||
|
||||
case VALUE_NUMBER_FLOAT:
|
||||
setValue(parser.getDecimalValue());
|
||||
break;
|
||||
|
||||
case VALUE_TRUE:
|
||||
setValue(Boolean.TRUE);
|
||||
break;
|
||||
|
||||
case VALUE_FALSE:
|
||||
setValue(Boolean.FALSE);
|
||||
break;
|
||||
|
||||
case VALUE_NULL:
|
||||
setValueNull();
|
||||
break;
|
||||
|
||||
case END_OBJECT:
|
||||
endObject();
|
||||
break;
|
||||
|
||||
case END_ARRAY:
|
||||
endArray();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class Stack {
|
||||
|
||||
private Context head;
|
||||
|
||||
private void push(Context context) {
|
||||
if (context != null) {
|
||||
context.next = head;
|
||||
head = context;
|
||||
}
|
||||
}
|
||||
|
||||
private Context pop(Context endingContext) {
|
||||
if (head == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
Context temp = head;
|
||||
head = head.next;
|
||||
temp.popContext(endingContext);
|
||||
return temp;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return head == null;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class Context {
|
||||
Context next;
|
||||
|
||||
abstract void popContext(Context temp);
|
||||
|
||||
abstract Object getValue();
|
||||
|
||||
abstract void setValue(Object value);
|
||||
|
||||
abstract void setKey(String key);
|
||||
|
||||
abstract void setValueNull();
|
||||
}
|
||||
|
||||
private static class ObjectContext extends Context {
|
||||
|
||||
private final Map<String, Object> map;
|
||||
|
||||
private String key;
|
||||
|
||||
ObjectContext() {
|
||||
map = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
ObjectContext(ModifyAwareType owner) {
|
||||
map = new ModifyAwareMap<>(owner, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popContext(Context temp) {
|
||||
setValue(temp.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getValue() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setValue(Object value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setKey(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setValueNull() {
|
||||
map.put(key, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ArrayContext extends Context {
|
||||
|
||||
private final List<Object> values;
|
||||
|
||||
ArrayContext() {
|
||||
values = new ArrayList<>();
|
||||
}
|
||||
|
||||
ArrayContext(ModifyAwareType owner) {
|
||||
values = new ModifyAwareList<>(owner, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popContext(Context temp) {
|
||||
values.add(temp.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getValue() {
|
||||
return values;
|
||||
}
|
||||
|
||||
@Override
|
||||
void setValue(Object value) {
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
void setValueNull() {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
void setKey(String key) {
|
||||
// not expected
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package io.ebeaninternal.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
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);
|
||||
write(object, writer).close();
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
static JsonGenerator write(Object object, Writer writer) throws IOException {
|
||||
JsonGenerator generator = jsonFactory.createGenerator(writer);
|
||||
write(object, generator);
|
||||
generator.flush();
|
||||
return generator;
|
||||
}
|
||||
|
||||
static void write(Object object, JsonGenerator jsonGenerator) {
|
||||
new EJsonWriter(jsonGenerator).writeJson(object);
|
||||
}
|
||||
|
||||
static void writeCollection(Collection<Object> collection, JsonGenerator jsonGenerator) throws IOException {
|
||||
new EJsonWriter(jsonGenerator).writeCollection(null, collection);
|
||||
}
|
||||
|
||||
private void writeJson(Object object) {
|
||||
writeJson(null, object);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writeJson(String name, Object object) {
|
||||
try {
|
||||
if (object == null) {
|
||||
writeNull(name);
|
||||
|
||||
} else if (object instanceof Number) {
|
||||
writeNumber(name, (Number) object);
|
||||
|
||||
} else if (object instanceof String) {
|
||||
writeString(name, (String) object);
|
||||
|
||||
} else if (object instanceof Map) {
|
||||
writeMap(name, (Map<Object, Object>) object);
|
||||
|
||||
} else if (object instanceof Collection) {
|
||||
writeCollection(name, (Collection<Object>) object);
|
||||
|
||||
} else if (object instanceof Boolean) {
|
||||
writeBoolean(name, (Boolean) object);
|
||||
|
||||
} else if (object instanceof Date) {
|
||||
writeDate(name, (Date) object);
|
||||
|
||||
} else if (object instanceof Map.Entry<?, ?>) {
|
||||
Map.Entry<?, ?> entry = (Map.Entry<?, ?>) object;
|
||||
writeJson(entry.getKey().toString(), entry.getValue());
|
||||
|
||||
} else {
|
||||
writeString(name, object.toString());
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBoolean(String name, Boolean object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeBoolean(object);
|
||||
} else {
|
||||
jsonGenerator.writeBooleanField(name, object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDate(String name, Date object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber(object.getTime());
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, object.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeNumber(String name, Number object) throws IOException {
|
||||
|
||||
if (object instanceof Long) {
|
||||
writeLong(name, object);
|
||||
|
||||
} else if (object instanceof Integer) {
|
||||
writeInteger(name, object);
|
||||
|
||||
} else if (object instanceof Double) {
|
||||
writeDouble(name, object);
|
||||
|
||||
} else if (object instanceof BigDecimal) {
|
||||
writeBigDecimal(name, object);
|
||||
|
||||
} else if (object instanceof BigInteger) {
|
||||
writeBigInteger(name, object);
|
||||
|
||||
} else {
|
||||
writeGeneralNumber(name, object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeGeneralNumber(String name, Number object) throws IOException {
|
||||
writeBigDecimal(name, new BigDecimal(object.toString()));
|
||||
}
|
||||
|
||||
private void writeBigDecimal(String name, Number object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber((BigDecimal) object);
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, (BigDecimal) object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeBigInteger(String name, Number object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber((BigInteger) object);
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, object.longValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeDouble(String name, Number object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber((Double) object);
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, (Double) object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLong(String name, Number object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber((Long) object);
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, (Long) object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeInteger(String name, Number object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNumber((Integer) object);
|
||||
} else {
|
||||
jsonGenerator.writeNumberField(name, (Integer) object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeNull(String name) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeNull();
|
||||
} else {
|
||||
jsonGenerator.writeNullField(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeString(String name, String object) throws IOException {
|
||||
if (name == null) {
|
||||
jsonGenerator.writeString(object);
|
||||
} else {
|
||||
jsonGenerator.writeStringField(name, object);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCollection(String name, Collection<Object> collection) throws IOException {
|
||||
if (name != null) {
|
||||
jsonGenerator.writeFieldName(name);
|
||||
}
|
||||
jsonGenerator.writeStartArray();
|
||||
for (Object object : collection) {
|
||||
writeJson(null, object);
|
||||
}
|
||||
jsonGenerator.writeEndArray();
|
||||
}
|
||||
|
||||
private void writeMap(String name, Map<Object, Object> map) throws IOException {
|
||||
|
||||
if (name != null) {
|
||||
jsonGenerator.writeFieldName(name);
|
||||
}
|
||||
jsonGenerator.writeStartObject();
|
||||
Set<Entry<Object, Object>> entrySet = map.entrySet();
|
||||
for (Entry<Object, Object> entry : entrySet) {
|
||||
writeJson(entry.getKey().toString(), entry.getValue());
|
||||
}
|
||||
jsonGenerator.writeEndObject();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
package io.ebeaninternal.json;
|
||||
|
||||
import io.avaje.json.JsonAdapter;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.ModifyAwareType;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Ebean specific {@link JsonAdapter} that materializes JSON into plain Java
|
||||
* Map/List/scalar values - optionally wrapped in modify-aware collections so
|
||||
* that mutations after load are tracked as dirty.
|
||||
* <p>
|
||||
* This consolidates the prior EJsonReader/EJsonWriter behavior into a single
|
||||
* adapter that plugs into avaje {@code JsonMapper}.
|
||||
*/
|
||||
final class EbeanJsonAdapter implements JsonAdapter<Object> {
|
||||
|
||||
static final EbeanJsonAdapter PLAIN = new EbeanJsonAdapter(false);
|
||||
static final EbeanJsonAdapter MODIFY_AWARE = new EbeanJsonAdapter(true);
|
||||
|
||||
private static final JsonStream JSON_STREAM = JsonStream.builder().build();
|
||||
|
||||
private final boolean modifyAware;
|
||||
|
||||
private EbeanJsonAdapter(boolean modifyAware) {
|
||||
this.modifyAware = modifyAware;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object fromJson(JsonReader reader) {
|
||||
return read(reader, null, modifyAware);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(JsonWriter writer, Object value) {
|
||||
write(writer, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value honoring an explicitly supplied current token (or current token when null).
|
||||
*/
|
||||
static Object read(JsonReader parser, Token token, boolean modifyAware) {
|
||||
ModifyAwareType owner = modifyAware ? new ModifyAwareFlag() : null;
|
||||
Token effectiveToken = token == null ? parser.currentToken() : token;
|
||||
Object value;
|
||||
if (effectiveToken == null) {
|
||||
value = parseRawJson(parser.readRaw(), owner);
|
||||
} else {
|
||||
value = parseValue(parser, effectiveToken, owner);
|
||||
}
|
||||
if (owner != null) {
|
||||
owner.setMarkedDirty(false);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private static Object parseValue(JsonReader parser, Token token, ModifyAwareType owner) {
|
||||
if (token == null) {
|
||||
token = parser.currentToken();
|
||||
if (token == null) {
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
return parseRawJson(parser.readRaw(), owner);
|
||||
}
|
||||
}
|
||||
switch (token) {
|
||||
case BEGIN_OBJECT:
|
||||
return parseObjectValue(parser, owner);
|
||||
case BEGIN_ARRAY:
|
||||
return parseArrayValue(parser, owner);
|
||||
case NUMBER:
|
||||
BigDecimal value = parser.readDecimal();
|
||||
return value.scale() <= 0 ? value.longValue() : value;
|
||||
case STRING:
|
||||
return parser.readString();
|
||||
case BOOLEAN:
|
||||
return parser.readBoolean();
|
||||
case NULL:
|
||||
parser.isNullValue();
|
||||
return null;
|
||||
default:
|
||||
return parseRawJson(parser.readRaw(), owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static Object parseRawJson(String json, ModifyAwareType owner) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
String content = json.trim();
|
||||
if (content.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try (JsonReader parser = JSON_STREAM.reader(content)) {
|
||||
return parseValue(parser, parser.currentToken(), owner);
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> parseObjectValue(JsonReader parser, ModifyAwareType owner) {
|
||||
Map<String, Object> map = owner == null
|
||||
? new LinkedHashMap<>()
|
||||
: new ModifyAwareMap<>(owner, new LinkedHashMap<>());
|
||||
|
||||
parser.beginObject();
|
||||
while (parser.hasNextField()) {
|
||||
String fieldName = parser.nextField();
|
||||
map.put(fieldName, parseValue(parser, parser.currentToken(), owner));
|
||||
}
|
||||
parser.endObject();
|
||||
return map;
|
||||
}
|
||||
|
||||
private static List<Object> parseArrayValue(JsonReader parser, ModifyAwareType owner) {
|
||||
List<Object> list = owner == null
|
||||
? new ArrayList<>()
|
||||
: new ModifyAwareList<>(owner, new ArrayList<>());
|
||||
|
||||
parser.beginArray();
|
||||
while (parser.hasNextElement()) {
|
||||
list.add(parseValue(parser, parser.currentToken(), owner));
|
||||
}
|
||||
parser.endArray();
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the value to an existing JsonWriter (used for the raw stream paths).
|
||||
*/
|
||||
static void write(JsonWriter jsonWriter, Object object) {
|
||||
if (object == null) {
|
||||
jsonWriter.nullValue();
|
||||
} else if (object instanceof String) {
|
||||
jsonWriter.value((String) object);
|
||||
} else if (object instanceof Integer) {
|
||||
jsonWriter.value((Integer) object);
|
||||
} else if (object instanceof Long) {
|
||||
jsonWriter.value((Long) object);
|
||||
} else if (object instanceof Double) {
|
||||
jsonWriter.value((Double) object);
|
||||
} else if (object instanceof Float) {
|
||||
jsonWriter.value((Float) object);
|
||||
} else if (object instanceof BigDecimal) {
|
||||
jsonWriter.value((BigDecimal) object);
|
||||
} else if (object instanceof Boolean) {
|
||||
jsonWriter.value((Boolean) object);
|
||||
} else if (object instanceof Map<?, ?>) {
|
||||
writeMap(jsonWriter, (Map<?, ?>) object);
|
||||
} else if (object instanceof Collection<?>) {
|
||||
writeCollection(jsonWriter, (Collection<?>) object);
|
||||
} else {
|
||||
jsonWriter.value(object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeMap(JsonWriter jsonWriter, Map<?, ?> map) {
|
||||
jsonWriter.beginObject();
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
jsonWriter.name((String) entry.getKey());
|
||||
write(jsonWriter, entry.getValue());
|
||||
}
|
||||
jsonWriter.endObject();
|
||||
}
|
||||
|
||||
static void writeCollection(JsonWriter jsonWriter, Collection<?> collection) {
|
||||
jsonWriter.beginArray();
|
||||
for (Object element : collection) {
|
||||
write(jsonWriter, element);
|
||||
}
|
||||
jsonWriter.endArray();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
module io.ebean.core.json {
|
||||
|
||||
requires io.ebean.api;
|
||||
|
||||
requires transitive com.fasterxml.jackson.core;
|
||||
requires transitive io.avaje.json;
|
||||
exports io.ebeaninternal.json to io.ebean.test, io.ebean.core;
|
||||
|
||||
provides io.ebean.service.BootstrapService with io.ebeaninternal.json.DJsonService;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,14 +16,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
<optional>true</optional>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-core</artifactId>
|
||||
<version>${avaje-json-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Provided scope for Postgres JSON/JSONB support -->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.core.type;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.text.StringFormatter;
|
||||
import io.ebean.text.StringParser;
|
||||
|
||||
@@ -177,13 +177,44 @@ public interface ScalarType<T> extends StringParser, StringFormatter, ScalarData
|
||||
void writeData(DataOutput dataOutput, T value) throws IOException;
|
||||
|
||||
/**
|
||||
* Read the value from JsonParser.
|
||||
* Read the value from JsonReader.
|
||||
*/
|
||||
T jsonRead(JsonParser parser) throws IOException;
|
||||
default T jsonRead(JsonReader parser) throws IOException {
|
||||
JsonReader.Token token = parser.currentToken();
|
||||
if (token == JsonReader.Token.NULL) {
|
||||
parser.isNullValue();
|
||||
return null;
|
||||
}
|
||||
if (token == JsonReader.Token.STRING) {
|
||||
return parse(parser.readString());
|
||||
}
|
||||
return parse(parser.readRaw());
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the value to the JsonGenerator.
|
||||
* Write the value to the JsonWriter.
|
||||
*/
|
||||
void jsonWrite(JsonGenerator writer, T value) throws IOException;
|
||||
default void jsonWrite(JsonWriter writer, T value) throws IOException {
|
||||
if (value == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
String formatted = formatValue(value);
|
||||
if (formatted == null) {
|
||||
writer.nullValue();
|
||||
return;
|
||||
}
|
||||
DocPropertyType docType = docType();
|
||||
if (docType == DocPropertyType.OBJECT || docType == DocPropertyType.LIST || docType == DocPropertyType.ROOT || likelyRawJson(formatted)) {
|
||||
writer.rawValue(formatted);
|
||||
} else {
|
||||
writer.value(formatted);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean likelyRawJson(String formatted) {
|
||||
String trimmed = formatted.trim();
|
||||
return !trimmed.isEmpty() && (trimmed.charAt(0) == '{' || trimmed.charAt(0) == '[');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebean.core.type;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
@@ -83,20 +83,31 @@ public abstract class ScalarTypeBaseDate<T> extends ScalarTypeBase<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T jsonRead(JsonParser parser) throws IOException {
|
||||
if (JsonToken.VALUE_NUMBER_INT == parser.getCurrentToken()) {
|
||||
return convertFromMillis(parser.getLongValue());
|
||||
} else {
|
||||
return convertFromDate(Date.valueOf(parser.getText()));
|
||||
public T jsonRead(JsonReader parser) throws IOException {
|
||||
Token token = parser.currentToken();
|
||||
if (Token.NUMBER == token) {
|
||||
return convertFromMillis(parser.readLong());
|
||||
}
|
||||
if (Token.STRING == token) {
|
||||
return convertFromDate(Date.valueOf(parser.readString()));
|
||||
}
|
||||
|
||||
String raw = parser.readRaw();
|
||||
if (raw == null || "null".equals(raw)) {
|
||||
return null;
|
||||
}
|
||||
if (raw.length() > 1 && raw.charAt(0) == '"' && raw.charAt(raw.length() - 1) == '"') {
|
||||
return convertFromDate(Date.valueOf(raw.substring(1, raw.length() - 1)));
|
||||
}
|
||||
return convertFromMillis(Long.parseLong(raw));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
|
||||
public void jsonWrite(JsonWriter writer, T value) throws IOException {
|
||||
if (mode == JsonConfig.Date.ISO8601) {
|
||||
writer.writeString(toIsoFormat(value));
|
||||
writer.value(toIsoFormat(value));
|
||||
} else {
|
||||
writer.writeNumber(convertToMillis(value));
|
||||
writer.value(convertToMillis(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package io.ebean.core.type;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.config.JsonConfig;
|
||||
|
||||
import java.io.DataInput;
|
||||
@@ -99,35 +100,53 @@ public abstract class ScalarTypeBaseDateTime<T> extends ScalarTypeBase<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T jsonRead(JsonParser parser) throws IOException {
|
||||
switch (parser.getCurrentToken()) {
|
||||
case VALUE_NUMBER_INT: {
|
||||
return convertFromMillis(parser.getLongValue());
|
||||
}
|
||||
case VALUE_NUMBER_FLOAT: {
|
||||
BigDecimal value = parser.getDecimalValue();
|
||||
Timestamp timestamp = ScalarTypeUtils.toTimestamp(value);
|
||||
return convertFromTimestamp(timestamp);
|
||||
}
|
||||
default: {
|
||||
return fromJsonISO8601(parser.getText());
|
||||
}
|
||||
public T jsonRead(JsonReader parser) throws IOException {
|
||||
Token token = parser.currentToken();
|
||||
if (token == Token.NUMBER) {
|
||||
return readNumber(parser.readDecimal());
|
||||
}
|
||||
if (token == Token.STRING) {
|
||||
return fromStringValue(parser.readString());
|
||||
}
|
||||
|
||||
String raw = parser.readRaw();
|
||||
if (raw == null || "null".equals(raw)) {
|
||||
return null;
|
||||
}
|
||||
if (raw.length() > 1 && raw.charAt(0) == '"' && raw.charAt(raw.length() - 1) == '"') {
|
||||
return fromStringValue(raw.substring(1, raw.length() - 1));
|
||||
}
|
||||
return readNumber(new BigDecimal(raw));
|
||||
}
|
||||
|
||||
private T fromStringValue(String value) {
|
||||
if (value.indexOf('-') == -1 && Character.isDigit(value.charAt(0))) {
|
||||
return readNumber(new BigDecimal(value));
|
||||
}
|
||||
return fromJsonISO8601(value);
|
||||
}
|
||||
|
||||
private T readNumber(BigDecimal value) {
|
||||
if (value.scale() <= 0) {
|
||||
return convertFromMillis(value.longValue());
|
||||
}
|
||||
Timestamp timestamp = ScalarTypeUtils.toTimestamp(value);
|
||||
return convertFromTimestamp(timestamp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
|
||||
public void jsonWrite(JsonWriter writer, T value) throws IOException {
|
||||
switch (mode) {
|
||||
case ISO8601: {
|
||||
writer.writeString(toJsonISO8601(value));
|
||||
writer.value(toJsonISO8601(value));
|
||||
break;
|
||||
}
|
||||
case NANOS: {
|
||||
writer.writeNumber(toJsonNanos(value));
|
||||
writer.value(toJsonNanos(value));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
writer.writeNumber(convertToMillis(value));
|
||||
writer.value(convertToMillis(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebean.core.type;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonWriter;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
@@ -104,13 +104,16 @@ public abstract class ScalarTypeBaseVarchar<T> extends ScalarTypeBase<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T jsonRead(JsonParser parser) throws IOException {
|
||||
return parse(parser.getValueAsString());
|
||||
public T jsonRead(JsonReader parser) throws IOException {
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
return parse(parser.readString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void jsonWrite(JsonGenerator writer, T value) throws IOException {
|
||||
writer.writeString(format(value));
|
||||
public void jsonWrite(JsonWriter writer, T value) throws IOException {
|
||||
writer.value(format(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,8 +4,7 @@ module io.ebean.core.type {
|
||||
|
||||
requires transitive java.sql;
|
||||
requires transitive io.ebean.api;
|
||||
requires transitive io.avaje.json;
|
||||
requires static org.postgresql.jdbc;
|
||||
|
||||
requires static com.fasterxml.jackson.core;
|
||||
|
||||
}
|
||||
|
||||
+7
-15
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -138,14 +138,6 @@
|
||||
<!-- <optional>true</optional>-->
|
||||
<!-- </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>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
@@ -165,21 +157,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>16.8.0</version>
|
||||
<version>18.0.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ public interface SpiDtoQuery<T> extends DtoQuery<T>, SpiSqlBinding {
|
||||
*/
|
||||
String planLabel();
|
||||
|
||||
/**
|
||||
* Return the explicit label (from {@code setLabel}) or null. Unlike
|
||||
* {@link #planLabel()} this does not fall back to the profile location.
|
||||
*/
|
||||
String explicitLabel();
|
||||
|
||||
/**
|
||||
* Obtain the location if necessary.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.api;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.plugin.BeanType;
|
||||
import io.ebean.text.json.JsonContext;
|
||||
import io.ebean.text.json.JsonWriteOptions;
|
||||
@@ -17,7 +17,7 @@ public interface SpiJsonContext extends JsonContext {
|
||||
/**
|
||||
* Create a Json Writer for writing beans as JSON.
|
||||
*/
|
||||
SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options);
|
||||
SpiJsonWriter createJsonWriter(JsonWriter gen, JsonWriteOptions options);
|
||||
|
||||
/**
|
||||
* Create a Json Writer for writing beans as JSON supplying a writer.
|
||||
|
||||
@@ -345,9 +345,18 @@ public interface SpiQuery<T> extends Query<T>, SpiQueryFetch, TxnProfileEventCod
|
||||
void fetchProperties(String path, OrmQueryProperties other);
|
||||
|
||||
/**
|
||||
* Set the on a secondary query given the label, relativePath and profile location of the parent query.
|
||||
* Set the label on a secondary query by extending the parent query's full plan
|
||||
* name with the relative path and load mode (joined with '.').
|
||||
* <p>
|
||||
* The {@code parentName} is the parent query's full plan name (without the
|
||||
* leading "orm."), so for a root query labelled "custMain" on Customer the
|
||||
* secondary lazy load of contacts becomes {@code Customer.custMain.contacts.lazy}.
|
||||
* The profile location of the parent query is also propagated.
|
||||
*
|
||||
* @param parentName the parent query's full plan name (no "orm." prefix)
|
||||
* @param relativePath the path to the loaded property plus the load mode, e.g. {@code contacts.lazy}
|
||||
*/
|
||||
void setProfilePath(String label, String relativePath, @Nullable ProfileLocation profileLocation);
|
||||
void setProfilePath(String parentName, String relativePath, @Nullable ProfileLocation profileLocation);
|
||||
|
||||
/**
|
||||
* Set the query mode.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package io.ebeaninternal.api.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
@@ -14,7 +14,9 @@ public interface SpiJsonReader {
|
||||
|
||||
PersistenceContext persistenceContext();
|
||||
|
||||
SpiJsonReader forJson(JsonParser moreJson);
|
||||
SpiJsonReader forJson(JsonReader moreJson);
|
||||
|
||||
SpiJsonReader forJson(String moreJson);
|
||||
|
||||
<T> void persistenceContextPut(Object beanId, T currentBean);
|
||||
|
||||
@@ -22,9 +24,9 @@ public interface SpiJsonReader {
|
||||
|
||||
ObjectMapper mapper();
|
||||
|
||||
JsonParser parser();
|
||||
JsonReader parser();
|
||||
|
||||
JsonToken nextToken() throws IOException;
|
||||
Token nextToken() throws IOException;
|
||||
|
||||
void pushPath(String path);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.changelog;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.event.changelog.BeanChange;
|
||||
import io.ebean.event.changelog.ChangeSet;
|
||||
import io.ebean.event.changelog.ChangeType;
|
||||
@@ -15,13 +15,13 @@ import java.util.Map;
|
||||
*/
|
||||
final class ChangeJsonBuilder {
|
||||
|
||||
private final JsonFactory jsonFactory = new JsonFactory();
|
||||
private final JsonStream jsonStream = JsonStream.builder().build();
|
||||
|
||||
/**
|
||||
* Write the bean change as JSON.
|
||||
*/
|
||||
void writeBeanJson(Writer writer, BeanChange bean, ChangeSet changeSet) throws IOException {
|
||||
try (JsonGenerator generator = jsonFactory.createGenerator(writer)) {
|
||||
try (JsonWriter generator = jsonStream.writer(writer)) {
|
||||
writeBeanChange(generator, bean, changeSet);
|
||||
generator.flush();
|
||||
}
|
||||
@@ -30,58 +30,67 @@ final class ChangeJsonBuilder {
|
||||
/**
|
||||
* Write the bean change as JSON document containing the transaction header details.
|
||||
*/
|
||||
private void writeBeanChange(JsonGenerator gen, BeanChange bean, ChangeSet changeSet) throws IOException {
|
||||
gen.writeStartObject();
|
||||
gen.writeNumberField("ts", bean.getEventTime());
|
||||
gen.writeStringField("change", bean.getEvent().getCode());
|
||||
gen.writeStringField("type", bean.getType());
|
||||
gen.writeStringField("id", bean.getId().toString());
|
||||
private void writeBeanChange(JsonWriter gen, BeanChange bean, ChangeSet changeSet) {
|
||||
gen.beginObject();
|
||||
gen.name("ts");
|
||||
gen.value(bean.getEventTime());
|
||||
gen.name("change");
|
||||
gen.value(bean.getEvent().getCode());
|
||||
gen.name("type");
|
||||
gen.value(bean.getType());
|
||||
gen.name("id");
|
||||
gen.value(bean.getId().toString());
|
||||
if (bean.getTenantId() != null) {
|
||||
gen.writeStringField("tenantId", bean.getTenantId().toString());
|
||||
gen.name("tenantId");
|
||||
gen.value(bean.getTenantId().toString());
|
||||
}
|
||||
writeBeanTransactionDetails(gen, changeSet);
|
||||
writeBeanValues(gen, bean);
|
||||
gen.writeEndObject();
|
||||
gen.endObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Denormalise by writing the transaction header details.
|
||||
*/
|
||||
private void writeBeanTransactionDetails(JsonGenerator gen, ChangeSet changeSet) throws IOException {
|
||||
private void writeBeanTransactionDetails(JsonWriter gen, ChangeSet changeSet) {
|
||||
String source = changeSet.getSource();
|
||||
if (source != null) {
|
||||
gen.writeStringField("source", source);
|
||||
gen.name("source");
|
||||
gen.value(source);
|
||||
}
|
||||
String userId = changeSet.getUserId();
|
||||
if (userId != null) {
|
||||
gen.writeStringField("userId", userId);
|
||||
gen.name("userId");
|
||||
gen.value(userId);
|
||||
}
|
||||
String userIpAddress = changeSet.getUserIpAddress();
|
||||
if (userIpAddress != null) {
|
||||
gen.writeStringField("userIpAddress", userIpAddress);
|
||||
gen.name("userIpAddress");
|
||||
gen.value(userIpAddress);
|
||||
}
|
||||
Map<String, String> userContext = changeSet.getUserContext();
|
||||
if (userContext != null && !userContext.isEmpty()) {
|
||||
gen.writeObjectFieldStart("userContext");
|
||||
gen.name("userContext");
|
||||
gen.beginObject();
|
||||
for (Map.Entry<String, String> entry : userContext.entrySet()) {
|
||||
gen.writeStringField(entry.getKey(), entry.getValue());
|
||||
gen.name(entry.getKey());
|
||||
gen.value(entry.getValue());
|
||||
}
|
||||
gen.writeEndObject();
|
||||
gen.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For insert and update write the new/old values.
|
||||
*/
|
||||
private void writeBeanValues(JsonGenerator gen, BeanChange bean) throws IOException {
|
||||
private void writeBeanValues(JsonWriter gen, BeanChange bean) {
|
||||
if (bean.getEvent() != ChangeType.DELETE) {
|
||||
gen.writeFieldName("data");
|
||||
gen.writeRaw(":");
|
||||
gen.writeRaw(bean.getData());
|
||||
gen.name("data");
|
||||
gen.rawValue(bean.getData());
|
||||
String oldData = bean.getOldData();
|
||||
if (oldData != null) {
|
||||
gen.writeRaw(",\"oldData\":");
|
||||
gen.writeRaw(oldData);
|
||||
gen.name("oldData");
|
||||
gen.rawValue(oldData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery {
|
||||
protected String bindLog = "";
|
||||
protected PreparedStatement pstmt;
|
||||
protected long startNano;
|
||||
protected Binder binder;
|
||||
private final ReentrantLock lock = new ReentrantLock();
|
||||
|
||||
/**
|
||||
@@ -144,6 +145,7 @@ public abstract class AbstractSqlQueryRequest implements CancelableQuery {
|
||||
}
|
||||
|
||||
protected void executeAsSql(Binder binder) throws SQLException {
|
||||
this.binder = binder;
|
||||
lock.lock();
|
||||
try {
|
||||
query.checkCancelled();
|
||||
|
||||
@@ -188,7 +188,10 @@ final class DefaultBeanLoader {
|
||||
query.setLazyLoadProperty(ebi.lazyLoadProperty());
|
||||
if (draft) {
|
||||
query.asDraft();
|
||||
} else if (mode == SpiQuery.Mode.LAZYLOAD_BEAN && desc.isSoftDelete()) {
|
||||
} else if (desc.isSoftDelete()
|
||||
&& (mode == SpiQuery.Mode.LAZYLOAD_BEAN || mode == SpiQuery.Mode.REFRESH_BEAN)) {
|
||||
// include soft-deleted rows when lazy loading or refreshing so a
|
||||
// refresh() on a soft-deleted bean can reload it (issue #3641)
|
||||
query.setIncludeSoftDeletes();
|
||||
}
|
||||
if (embeddedOwnerIndex > -1) {
|
||||
|
||||
@@ -2276,7 +2276,10 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
if (initRequest.isAll()) {
|
||||
queryPlanManager.setDefaultThreshold(initRequest.thresholdMicros());
|
||||
}
|
||||
return descriptorManager.queryPlanInit(initRequest);
|
||||
List<MetaQueryPlan> list = descriptorManager.queryPlanInit(initRequest);
|
||||
dtoBeanManager.queryPlanInit(initRequest, list);
|
||||
relationalQueryEngine.queryPlanInit(initRequest, list);
|
||||
return list;
|
||||
}
|
||||
|
||||
List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
|
||||
|
||||
@@ -2,9 +2,11 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.QueryIterator;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQuery;
|
||||
import io.ebeaninternal.server.bind.DataBindCapture;
|
||||
import io.ebeaninternal.server.dto.DtoColumn;
|
||||
import io.ebeaninternal.server.dto.DtoMappingRequest;
|
||||
import io.ebeaninternal.server.dto.DtoQueryPlan;
|
||||
@@ -19,6 +21,8 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.lang.System.Logger.Level.ERROR;
|
||||
|
||||
/**
|
||||
* Wraps the objects involved in executing a DtoQuery.
|
||||
*/
|
||||
@@ -87,6 +91,22 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
|
||||
if (plan != null) {
|
||||
long exeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
plan.collect(exeMicros);
|
||||
// native SQL only (binder set in executeAsSql); ORM-backed DTO queries
|
||||
// capture the query plan via the underlying ORM query plan instead.
|
||||
if (binder != null && plan.collectFor(exeMicros)) {
|
||||
captureBindForQueryPlan(exeMicros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void captureBindForQueryPlan(long exeMicros) {
|
||||
final long startNanos = System.nanoTime();
|
||||
try {
|
||||
DataBindCapture capture = DataBindCapture.of(server.dataTimeZone());
|
||||
binder.bind(query.getBindParams(), capture, new StringBuilder());
|
||||
plan.setBind(capture.bindCapture(), exeMicros, startNanos);
|
||||
} catch (SQLException e) {
|
||||
CoreLog.log.log(ERROR, "Error capturing DTO bind values", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +147,7 @@ public final class DtoQueryRequest<T> extends AbstractSqlQueryRequest {
|
||||
}
|
||||
|
||||
private DtoMappingRequest mappingRequest() throws SQLException {
|
||||
return new DtoMappingRequest(query, sql, readMeta());
|
||||
return new DtoMappingRequest(server, query, sql, readMeta());
|
||||
}
|
||||
|
||||
private DtoColumn[] readMeta() throws SQLException {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.DatabaseBuilder;
|
||||
import io.ebean.ExpressionFactory;
|
||||
import io.ebean.annotation.Platform;
|
||||
@@ -89,7 +89,7 @@ public final class InternalConfiguration {
|
||||
private final boolean jacksonCorePresent;
|
||||
private final ExpressionFactory expressionFactory;
|
||||
private final SpiBackgroundExecutor backgroundExecutor;
|
||||
private final JsonFactory jsonFactory;
|
||||
private final JsonStream jsonStream;
|
||||
private final DocStoreFactory docStoreFactory;
|
||||
private final List<Plugin> plugins = new ArrayList<>();
|
||||
private final MultiValueBind multiValueBind;
|
||||
@@ -108,7 +108,7 @@ public final class InternalConfiguration {
|
||||
this.tableModState = new TableModState();
|
||||
this.logManager = initLogManager();
|
||||
this.docStoreFactory = initDocStoreFactory(service(DocStoreFactory.class));
|
||||
this.jsonFactory = config.getJsonFactory();
|
||||
this.jsonStream = config.getJsonStream();
|
||||
this.clusterManager = clusterManager;
|
||||
this.backgroundExecutor = backgroundExecutor;
|
||||
this.bootupClasses = bootupClasses;
|
||||
@@ -292,7 +292,7 @@ public final class InternalConfiguration {
|
||||
}
|
||||
|
||||
SpiJsonContext createJsonContext(SpiEbeanServer server) {
|
||||
return jacksonCorePresent ? new DJsonContext(server, jsonFactory, typeManager) : null;
|
||||
return jacksonCorePresent ? new DJsonContext(server, jsonStream, typeManager) : null;
|
||||
}
|
||||
|
||||
AutoTuneService createAutoTuneService(SpiEbeanServer server) {
|
||||
@@ -306,7 +306,7 @@ public final class InternalConfiguration {
|
||||
|
||||
RelationalQueryEngine createRelationalQueryEngine() {
|
||||
return new DefaultRelationalQueryEngine(binder, config.getDatabaseBooleanTrue(), config.getPlatformConfig().getDbUuid().useBinaryOptimized(),
|
||||
config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList(), databasePlatform.autoCommitFalseOnFindIterate());
|
||||
config.getJdbcFetchSizeFindEach(), config.getJdbcFetchSizeFindList(), databasePlatform.autoCommitFalseOnFindIterate(), config.isQueryPlanEnable());
|
||||
}
|
||||
|
||||
OrmQueryEngine createOrmQueryEngine() {
|
||||
|
||||
@@ -4,7 +4,11 @@ package io.ebeaninternal.server.core;
|
||||
import io.ebean.RowConsumer;
|
||||
import io.ebean.RowMapper;
|
||||
import io.ebean.SqlRow;
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanInit;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.server.query.SqlQueryPlan;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
@@ -57,6 +61,23 @@ public interface RelationalQueryEngine {
|
||||
*/
|
||||
void collect(String label, long exeMicros);
|
||||
|
||||
/**
|
||||
* Return true if query plan capture is enabled (the master switch). When false
|
||||
* no SqlQuery plans are created or cached, so labelled queries incur no extra cost.
|
||||
*/
|
||||
boolean captureActive();
|
||||
|
||||
/**
|
||||
* Obtain (creating and caching if necessary) the query plan for a labelled
|
||||
* native SqlQuery, used to capture bind values for database query plan collection.
|
||||
*/
|
||||
SqlQueryPlan obtainPlan(String label, String sql, SpiEbeanServer server);
|
||||
|
||||
/**
|
||||
* Initiate query plan bind capture for matching SqlQuery plans.
|
||||
*/
|
||||
void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list);
|
||||
|
||||
/**
|
||||
* Visit the metrics.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.*;
|
||||
import io.ebeaninternal.api.CoreLog;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiSqlQuery;
|
||||
import io.ebeaninternal.server.bind.DataBindCapture;
|
||||
import io.ebeaninternal.server.query.SqlQueryPlan;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.ResultSetMetaData;
|
||||
@@ -12,6 +15,8 @@ import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.lang.System.Logger.Level.ERROR;
|
||||
|
||||
/**
|
||||
* Wraps the objects involved in executing a SqlQuery.
|
||||
*/
|
||||
@@ -39,9 +44,28 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
|
||||
@Override
|
||||
protected void requestComplete() {
|
||||
String label = query.getLabel();
|
||||
if (label != null) {
|
||||
long exeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryEngine.collect(label, exeMicros);
|
||||
if (label == null) {
|
||||
return;
|
||||
}
|
||||
long exeMicros = (System.nanoTime() - startNano) / 1000L;
|
||||
queryEngine.collect(label, exeMicros);
|
||||
// capture bind values to later collect the database query plan
|
||||
if (binder != null && queryEngine.captureActive()) {
|
||||
SqlQueryPlan plan = queryEngine.obtainPlan(label, sql, server);
|
||||
if (plan.collectFor(exeMicros)) {
|
||||
captureBindForQueryPlan(plan, exeMicros);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void captureBindForQueryPlan(SqlQueryPlan plan, long exeMicros) {
|
||||
final long startNanos = System.nanoTime();
|
||||
try {
|
||||
DataBindCapture capture = DataBindCapture.of(server.dataTimeZone());
|
||||
binder.bind(query.getBindParams(), capture, new StringBuilder());
|
||||
plan.setBind(capture.bindCapture(), exeMicros, startNanos);
|
||||
} catch (SQLException e) {
|
||||
CoreLog.log.log(ERROR, "Error capturing SqlQuery bind values", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,11 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.bean.BeanDiffVisitor;
|
||||
import io.ebean.config.JsonConfig;
|
||||
import io.ebeaninternal.api.json.SpiJsonWriter;
|
||||
import io.ebeaninternal.server.json.WriteJson;
|
||||
import io.ebeaninternal.server.util.ArrayStack;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
@@ -13,26 +16,18 @@ import java.io.StringWriter;
|
||||
*/
|
||||
final class BeanChangeJson implements BeanDiffVisitor {
|
||||
|
||||
private final StringWriter newData;
|
||||
private final StringWriter oldData;
|
||||
private final SpiJsonWriter newJson;
|
||||
private final SpiJsonWriter oldJson;
|
||||
private final StringWriter data;
|
||||
private final SpiJsonWriter json;
|
||||
private final boolean writeNew;
|
||||
private final ArrayStack<BeanDescriptor<?>> stack = new ArrayStack<>();
|
||||
private BeanDescriptor<?> descriptor;
|
||||
|
||||
BeanChangeJson(BeanDescriptor<?> descriptor, boolean statelessUpdate) {
|
||||
BeanChangeJson(BeanDescriptor<?> descriptor, boolean writeNew) {
|
||||
this.descriptor = descriptor;
|
||||
this.newData = new StringWriter(200);
|
||||
this.newJson = descriptor.createJsonWriter(newData);
|
||||
newJson.writeStartObject();
|
||||
if (statelessUpdate) {
|
||||
this.oldJson = null;
|
||||
this.oldData = null;
|
||||
} else {
|
||||
this.oldData = new StringWriter(200);
|
||||
this.oldJson = descriptor.createJsonWriter(oldData);
|
||||
oldJson.writeStartObject();
|
||||
}
|
||||
this.writeNew = writeNew;
|
||||
this.data = new StringWriter(200);
|
||||
this.json = new WriteJson(JsonStream.builder().build().writer(data), JsonConfig.Include.ALL);
|
||||
json.writeStartObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,10 +35,7 @@ final class BeanChangeJson implements BeanDiffVisitor {
|
||||
try {
|
||||
BeanProperty prop = descriptor.propertiesIndex[position];
|
||||
if (prop.isDbUpdatable()) {
|
||||
prop.jsonWriteValue(newJson, newVal);
|
||||
if (oldJson != null) {
|
||||
prop.jsonWriteValue(oldJson, oldVal);
|
||||
}
|
||||
prop.jsonWriteValue(json, writeNew ? newVal : oldVal);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
@@ -55,18 +47,12 @@ final class BeanChangeJson implements BeanDiffVisitor {
|
||||
stack.push(descriptor);
|
||||
BeanPropertyAssocOne<?> embedded = (BeanPropertyAssocOne<?>)descriptor.propertiesIndex[position];
|
||||
descriptor = embedded.targetDescriptor();
|
||||
newJson.writeStartObject(embedded.name());
|
||||
if (oldJson != null) {
|
||||
oldJson.writeStartObject(embedded.name());
|
||||
}
|
||||
json.writeStartObject(embedded.name());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPop() {
|
||||
newJson.writeEndObject();
|
||||
if (oldJson != null) {
|
||||
oldJson.writeEndObject();
|
||||
}
|
||||
json.writeEndObject();
|
||||
descriptor = stack.pop();
|
||||
}
|
||||
|
||||
@@ -75,28 +61,14 @@ final class BeanChangeJson implements BeanDiffVisitor {
|
||||
*/
|
||||
void flush() {
|
||||
try {
|
||||
newJson.writeEndObject();
|
||||
newJson.flush();
|
||||
if (oldJson != null) {
|
||||
oldJson.writeEndObject();
|
||||
oldJson.flush();
|
||||
}
|
||||
json.writeEndObject();
|
||||
json.flush();
|
||||
} catch (IOException e) {
|
||||
throw new PersistenceIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the new values JSON.
|
||||
*/
|
||||
String newJson() {
|
||||
return newData.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the old values JSON.
|
||||
*/
|
||||
String oldJson() {
|
||||
return oldData == null ? null : oldData.toString();
|
||||
String json() {
|
||||
return data.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,10 +792,17 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType, SpiBeanType {
|
||||
*/
|
||||
private BeanChange updateBeanChange(PersistRequestBean<T> request) {
|
||||
try {
|
||||
BeanChangeJson changeJson = new BeanChangeJson(this, request.isStatelessUpdate());
|
||||
request.intercept().addDirtyPropertyValues(changeJson);
|
||||
changeJson.flush();
|
||||
return beanChange(ChangeType.UPDATE, request.beanId(), changeJson.newJson(), changeJson.oldJson());
|
||||
BeanChangeJson newValues = new BeanChangeJson(this, true);
|
||||
request.intercept().addDirtyPropertyValues(newValues);
|
||||
newValues.flush();
|
||||
String oldData = null;
|
||||
if (!request.isStatelessUpdate()) {
|
||||
BeanChangeJson oldValues = new BeanChangeJson(this, false);
|
||||
request.intercept().addDirtyPropertyValues(oldValues);
|
||||
oldValues.flush();
|
||||
oldData = oldValues.json();
|
||||
}
|
||||
return beanChange(ChangeType.UPDATE, request.beanId(), newValues.json(), oldData);
|
||||
} catch (RuntimeException e) {
|
||||
log.log(ERROR, "Failed to write ChangeLog entry for update", e);
|
||||
return null;
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
@@ -49,25 +49,25 @@ class BeanDescriptorElementEmbeddedMap<T> extends BeanDescriptorElementEmbedded<
|
||||
|
||||
@Override
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
JsonParser parser = readJson.parser();
|
||||
JsonReader parser = readJson.parser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
String fieldName = parser.nextFieldName();
|
||||
if (fieldName == null) {
|
||||
break;
|
||||
}
|
||||
parser.beginObject();
|
||||
while (parser.hasNextField()) {
|
||||
String fieldName = parser.nextField();
|
||||
if (stringKey) {
|
||||
parser.nextToken();
|
||||
Object val = readJsonElement(readJson, null, null); // CHECKME: Update existing map entry here?
|
||||
add.addKeyValue(fieldName, val);
|
||||
} else {
|
||||
parser.nextFieldName();
|
||||
parser.beginObject();
|
||||
parser.nextField();
|
||||
Object key = scalarTypeKey.jsonRead(parser);
|
||||
parser.nextFieldName();
|
||||
parser.nextField();
|
||||
Object val = readJsonElement(readJson, null, null); // CHECKME: Update existing map entry here?
|
||||
parser.endObject();
|
||||
add.addKeyValue(key, val);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
parser.endObject();
|
||||
return add.collection();
|
||||
}
|
||||
|
||||
|
||||
+6
-9
@@ -1,7 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.bean.EntityBean;
|
||||
@@ -40,15 +39,13 @@ class BeanDescriptorElementScalar<T> extends BeanDescriptorElement<T> {
|
||||
|
||||
@Override
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
JsonParser parser = readJson.parser();
|
||||
JsonReader parser = readJson.parser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
break;
|
||||
}
|
||||
parser.beginArray();
|
||||
while (parser.hasNextElement()) {
|
||||
add.addElement(scalarType.jsonRead(parser));
|
||||
} while (true);
|
||||
}
|
||||
parser.endArray();
|
||||
return add.collection();
|
||||
}
|
||||
|
||||
|
||||
+11
-11
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
@@ -49,25 +49,25 @@ class BeanDescriptorElementScalarMap<T> extends BeanDescriptorElement<T> {
|
||||
|
||||
@Override
|
||||
public Object jsonReadCollection(SpiJsonReader readJson, EntityBean parentBean) throws IOException {
|
||||
JsonParser parser = readJson.parser();
|
||||
JsonReader parser = readJson.parser();
|
||||
ElementCollector add = elementHelp.createCollector();
|
||||
do {
|
||||
String fieldName = parser.nextFieldName();
|
||||
if (fieldName == null) {
|
||||
break;
|
||||
}
|
||||
parser.beginObject();
|
||||
while (parser.hasNextField()) {
|
||||
String fieldName = parser.nextField();
|
||||
if (stringKey) {
|
||||
parser.nextToken();
|
||||
Object val = scalarTypeVal.jsonRead(parser);
|
||||
add.addKeyValue(fieldName, val);
|
||||
} else {
|
||||
parser.nextFieldName();
|
||||
parser.beginObject();
|
||||
parser.nextField();
|
||||
Object key = scalarTypeKey.jsonRead(parser);
|
||||
parser.nextFieldName();
|
||||
parser.nextField();
|
||||
Object val = scalarTypeVal.jsonRead(parser);
|
||||
parser.endObject();
|
||||
add.addKeyValue(key, val);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
parser.endObject();
|
||||
return add.collection();
|
||||
}
|
||||
|
||||
|
||||
+37
-50
@@ -1,10 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.text.json.EJson;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
@@ -34,7 +31,8 @@ final class BeanDescriptorJsonHelp<T> {
|
||||
InheritInfo localInheritInfo = inheritInfo.readType(bean.getClass());
|
||||
String discValue = localInheritInfo.getDiscriminatorStringValue();
|
||||
String discColumn = localInheritInfo.getDiscriminatorColumn();
|
||||
writeJson.gen().writeStringField(discColumn, discValue);
|
||||
writeJson.gen().name(discColumn);
|
||||
writeJson.gen().value(discValue);
|
||||
localInheritInfo.desc().jsonWriteProperties(writeJson, bean);
|
||||
}
|
||||
writeJson.writeEndObject();
|
||||
@@ -66,44 +64,38 @@ final class BeanDescriptorJsonHelp<T> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T jsonRead(SpiJsonReader jsonRead, String path, boolean withInheritance, T target) throws IOException {
|
||||
JsonParser parser = jsonRead.parser();
|
||||
//noinspection StatementWithEmptyBody
|
||||
if (parser.getCurrentToken() == JsonToken.START_OBJECT) {
|
||||
// start object token read by Jackson already
|
||||
} else {
|
||||
// check for null or start object
|
||||
JsonToken token = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
return null;
|
||||
}
|
||||
if (JsonToken.START_OBJECT != token) {
|
||||
throw new JsonParseException(parser, "Unexpected token " + token + " - expecting start_object", parser.getCurrentLocation());
|
||||
}
|
||||
JsonReader parser = jsonRead.parser();
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
Token token = parser.currentToken();
|
||||
if (token != Token.BEGIN_OBJECT) {
|
||||
throw new IllegalStateException("Unexpected token " + token + " - expecting BEGIN_OBJECT at: " + parser.location());
|
||||
}
|
||||
|
||||
if (desc.inheritInfo == null || !withInheritance) {
|
||||
return jsonReadObject(jsonRead, path, target);
|
||||
}
|
||||
|
||||
ObjectNode node = jsonRead.mapper().readTree(parser);
|
||||
if (node.isNull()) {
|
||||
Map<String, Object> node = EJson.parseObject(parser);
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
JsonParser newParser = node.traverse();
|
||||
SpiJsonReader newReader = jsonRead.forJson(newParser);
|
||||
|
||||
// check for the discriminator value to determine the correct sub type
|
||||
String discColumn = inheritInfo.getRoot().getDiscriminatorColumn();
|
||||
JsonNode discNode = node.get(discColumn);
|
||||
if (discNode == null || discNode.isNull()) {
|
||||
Object discValue = node.get(discColumn);
|
||||
String rawObject = EJson.write(node);
|
||||
SpiJsonReader newReader = jsonRead.forJson(rawObject);
|
||||
if (discValue == null) {
|
||||
if (!desc.isAbstractType()) {
|
||||
return desc.jsonReadObject(newReader, path, target);
|
||||
}
|
||||
String msg = "Error reading inheritance discriminator - expected [" + discColumn + "] but no json key?";
|
||||
throw new JsonParseException(newParser, msg, parser.getCurrentLocation());
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
|
||||
BeanDescriptor<T> inheritDesc = (BeanDescriptor<T>) inheritInfo.readType(discNode.asText()).desc();
|
||||
BeanDescriptor<T> inheritDesc = (BeanDescriptor<T>) inheritInfo.readType(String.valueOf(discValue)).desc();
|
||||
return inheritDesc.jsonReadObject(newReader, path, target);
|
||||
}
|
||||
|
||||
@@ -124,35 +116,30 @@ final class BeanDescriptorJsonHelp<T> {
|
||||
if (path != null) {
|
||||
readJson.pushPath(path);
|
||||
}
|
||||
JsonReader parser = readJson.parser();
|
||||
parser.beginObject();
|
||||
|
||||
// unmapped properties, send to JsonReadBeanVisitor later
|
||||
Map<String, Object> unmappedProperties = null;
|
||||
do {
|
||||
JsonParser parser = readJson.parser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.FIELD_NAME == event) {
|
||||
String key = parser.getCurrentName();
|
||||
BeanProperty p = desc.beanProperty(key);
|
||||
if (p != null) {
|
||||
if (p.isVersion() && readJson.update() ) {
|
||||
// skip version prop during update
|
||||
p.jsonRead(readJson);
|
||||
} else {
|
||||
p.jsonRead(readJson, bean);
|
||||
}
|
||||
while (parser.hasNextField()) {
|
||||
String key = parser.nextField();
|
||||
BeanProperty p = desc.beanProperty(key);
|
||||
if (p != null) {
|
||||
if (p.isVersion() && readJson.update()) {
|
||||
// skip version prop during update
|
||||
p.jsonRead(readJson);
|
||||
} else {
|
||||
// read an unmapped property
|
||||
if (unmappedProperties == null) {
|
||||
unmappedProperties = new LinkedHashMap<>();
|
||||
}
|
||||
unmappedProperties.put(key, EJson.parse(parser));
|
||||
p.jsonRead(readJson, bean);
|
||||
}
|
||||
} else if (JsonToken.END_OBJECT == event) {
|
||||
break;
|
||||
} else {
|
||||
throw new RuntimeException("Unexpected token " + event + " - expecting key or end_object at: " + parser.getCurrentLocation());
|
||||
// read an unmapped property
|
||||
if (unmappedProperties == null) {
|
||||
unmappedProperties = new LinkedHashMap<>();
|
||||
}
|
||||
unmappedProperties.put(key, EJson.parse(parser));
|
||||
}
|
||||
|
||||
} while (true);
|
||||
}
|
||||
parser.endObject();
|
||||
|
||||
if (unmappedProperties != null) {
|
||||
desc.setUnmappedJson(bean, unmappedProperties);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.DataIntegrityException;
|
||||
import io.ebean.ModifyAwareType;
|
||||
import io.ebean.ValuePair;
|
||||
@@ -1437,14 +1438,14 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
|
||||
}
|
||||
|
||||
public Object jsonRead(SpiJsonReader ctx) throws IOException {
|
||||
JsonToken event = ctx.nextToken();
|
||||
if (JsonToken.VALUE_NULL == event) {
|
||||
JsonReader parser = ctx.parser();
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
} else {
|
||||
// expect to read non-null json value
|
||||
Object objValue;
|
||||
if (scalarType != null) {
|
||||
objValue = scalarType.jsonRead(ctx.parser());
|
||||
objValue = scalarType.jsonRead(parser);
|
||||
} else {
|
||||
try {
|
||||
objValue = ctx.readValueUsingObjectMapper(propertyType);
|
||||
|
||||
+87
-30
@@ -1,15 +1,14 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.bean.BeanCollection;
|
||||
import io.ebean.bean.BeanCollection.ModifyListenMode;
|
||||
import io.ebean.bean.BeanCollectionAdd;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.plugin.PropertyAssocMany;
|
||||
import io.ebean.text.PathProperties;
|
||||
@@ -1043,9 +1042,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
*/
|
||||
private Object jsonReadCollection(String json) throws IOException {
|
||||
SpiJsonReader ctx = descriptor.createJsonReader(json);
|
||||
JsonParser parser = ctx.parser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == event) {
|
||||
JsonReader parser = ctx.parser();
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
return jsonReadCollection(ctx, null, null);
|
||||
@@ -1068,20 +1066,18 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
BeanCollection<?> collection = createEmpty(parentBean);
|
||||
BeanCollectionAdd add = beanCollectionAdd(collection);
|
||||
Map<Object, T> existingBeans = extractBeans(targets);
|
||||
do {
|
||||
|
||||
JsonReader parser = readJson.parser();
|
||||
parser.beginArray();
|
||||
while (parser.hasNextElement()) {
|
||||
EntityBean detailBean = getDetailBean(readJson, existingBeans);
|
||||
if (detailBean == null) {
|
||||
// read the entire array
|
||||
break;
|
||||
if (detailBean != null) {
|
||||
add.addEntityBean(detailBean);
|
||||
if (parentBean != null && childMasterProperty != null) {
|
||||
// bind detail bean back to master via mappedBy property
|
||||
childMasterProperty.setValue(detailBean, parentBean);
|
||||
}
|
||||
}
|
||||
add.addEntityBean(detailBean);
|
||||
|
||||
if (parentBean != null && childMasterProperty != null) {
|
||||
// bind detail bean back to master via mappedBy property
|
||||
childMasterProperty.setValue(detailBean, parentBean);
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
|
||||
@@ -1092,18 +1088,79 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
|
||||
BeanProperty idProperty = targetDescriptor.idProperty();
|
||||
if (targets == null || idProperty == null) {
|
||||
return (EntityBean) targetDescriptor.jsonRead(readJson, name, null);
|
||||
} else {
|
||||
JsonToken token = readJson.parser().nextToken();
|
||||
if (JsonToken.VALUE_NULL == token || JsonToken.END_ARRAY == token) {
|
||||
return null;
|
||||
}
|
||||
// extract the id. We have to buffer the JSON;
|
||||
ObjectNode node = readJson.mapper().readTree(readJson.parser());
|
||||
SpiJsonReader jsonReader = readJson.forJson(node.traverse());
|
||||
JsonNode idNode = node.get(idProperty.name());
|
||||
Object id = idNode == null ? null : idProperty.jsonRead(readJson.forJson(idNode.traverse()));
|
||||
return (EntityBean) targetDescriptor.jsonRead(jsonReader, name, targets.get(id));
|
||||
}
|
||||
/// ROB Checkme
|
||||
JsonReader parser = readJson.parser();
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
EntityBean incomingBean = (EntityBean) targetDescriptor.jsonRead(readJson, name, null);
|
||||
Object id = idProperty.getValue(incomingBean);
|
||||
EntityBean existingBean = (EntityBean) targets.get(id);
|
||||
if (existingBean == null) {
|
||||
return incomingBean;
|
||||
}
|
||||
mergeLoadedBean(targetDescriptor, incomingBean, existingBean);
|
||||
return existingBean;
|
||||
}
|
||||
|
||||
private void mergeLoadedBean(BeanDescriptor<?> descriptor, EntityBean incomingBean, EntityBean existingBean) {
|
||||
EntityBeanIntercept incomingIntercept = incomingBean._ebean_getIntercept();
|
||||
for (BeanProperty property : descriptor.propertiesBaseScalar()) {
|
||||
if (incomingIntercept.isLoadedProperty(property.propertyIndex())) {
|
||||
property.setValueIntercept(existingBean, property.getValue(incomingBean));
|
||||
}
|
||||
}
|
||||
for (BeanPropertyAssocMany<?> property : descriptor.propertiesMany()) {
|
||||
if (incomingIntercept.isLoadedProperty(property.propertyIndex())) {
|
||||
Object mergedValue = mergeLoadedMany(property, property.getValue(incomingBean), property.getValue(existingBean));
|
||||
property.setValueIntercept(existingBean, mergedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object mergeLoadedMany(BeanPropertyAssocMany<?> property, Object incomingValue, Object existingValue) {
|
||||
if (!(incomingValue instanceof Collection<?>)) {
|
||||
return incomingValue;
|
||||
}
|
||||
if (!(existingValue instanceof Collection<?>)) {
|
||||
return incomingValue;
|
||||
}
|
||||
Collection<?> incomingCollection = (Collection<?>) incomingValue;
|
||||
Collection<?> existingCollection = (Collection<?>) existingValue;
|
||||
BeanProperty idProperty = property.targetDescriptor.idProperty();
|
||||
if (idProperty == null) {
|
||||
return incomingValue;
|
||||
}
|
||||
|
||||
Map<Object, EntityBean> existingById = new LinkedHashMap<>();
|
||||
for (Object existingElement : existingCollection) {
|
||||
if (existingElement instanceof EntityBean) {
|
||||
EntityBean existingBean = (EntityBean) existingElement;
|
||||
Object id = idProperty.getValue(existingBean);
|
||||
if (id != null) {
|
||||
existingById.put(id, existingBean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BeanCollection<?> mergedCollection = property.createEmpty(null);
|
||||
BeanCollectionAdd add = property.beanCollectionAdd(mergedCollection);
|
||||
for (Object incomingElement : incomingCollection) {
|
||||
if (!(incomingElement instanceof EntityBean)) {
|
||||
continue;
|
||||
}
|
||||
EntityBean incomingBean = (EntityBean) incomingElement;
|
||||
Object id = idProperty.getValue(incomingBean);
|
||||
EntityBean existingBean = id == null ? null : existingById.get(id);
|
||||
if (existingBean == null) {
|
||||
add.addEntityBean(incomingBean);
|
||||
} else {
|
||||
mergeLoadedBean(property.targetDescriptor, incomingBean, existingBean);
|
||||
add.addEntityBean(existingBean);
|
||||
}
|
||||
}
|
||||
return mergedCollection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+7
-8
@@ -1,8 +1,7 @@
|
||||
package io.ebeaninternal.server.deploy;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebeaninternal.api.json.SpiJsonReader;
|
||||
|
||||
@@ -38,17 +37,17 @@ class BeanPropertyAssocManyJsonHelp {
|
||||
if (!this.many.jsonDeserialize) {
|
||||
return;
|
||||
}
|
||||
JsonParser parser = readJson.parser();
|
||||
JsonToken event = parser.nextToken();
|
||||
if (JsonToken.VALUE_NULL == event) {
|
||||
JsonReader parser = readJson.parser();
|
||||
if (parser.isNullValue()) {
|
||||
return;
|
||||
}
|
||||
if (many.isTransient()) {
|
||||
jsonReadTransientUsingObjectMapper(readJson, parentBean);
|
||||
return;
|
||||
}
|
||||
if (JsonToken.START_ARRAY != event && JsonToken.START_OBJECT != event) {
|
||||
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start array or object");
|
||||
Token event = parser.currentToken();
|
||||
if (Token.BEGIN_ARRAY != event && Token.BEGIN_OBJECT != event) {
|
||||
throw new IllegalStateException("Unexpected token " + event + " - expecting start array or object");
|
||||
}
|
||||
if (readJson.update()) {
|
||||
many.setValueIntercept(parentBean, many.jsonReadCollection(readJson, parentBean, many.getValue(parentBean)));
|
||||
|
||||
+2
-2
@@ -29,11 +29,11 @@ class BeanPropertyAssocManyJsonTransient {
|
||||
TypeFactory typeFactory = mapper.getTypeFactory();
|
||||
JavaType target = typeFactory.constructType(many.targetType());
|
||||
MapType jacksonType = typeFactory.constructMapType(LinkedHashMap.class, TypeFactory.unknownType(), target);
|
||||
value = mapper.readValue(readJson.parser(), jacksonType);
|
||||
value = mapper.readValue(readJson.parser().readRaw(), jacksonType);
|
||||
} else {
|
||||
// read list or set using Jackson object mapper
|
||||
CollectionType jacksonType = mapper.getTypeFactory().constructCollectionType(manyType.getCollectionType(), many.targetType());
|
||||
value = mapper.readValue(readJson.parser(), jacksonType);
|
||||
value = mapper.readValue(readJson.parser().readRaw(), jacksonType);
|
||||
}
|
||||
many.setValue(parentBean, value);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanInit;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -43,6 +46,18 @@ public final class DtoBeanDescriptor<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arm bind capture for matching (native SQL) DTO query plans.
|
||||
*/
|
||||
public void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list) {
|
||||
for (DtoQueryPlan plan : plans.values()) {
|
||||
if (plan.supportsPlanCapture() && request.includeHash(plan.hash())) {
|
||||
plan.queryPlanInit(request.thresholdMicros(plan.hash()));
|
||||
list.add(plan.createMeta(null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the named RawSql query.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.meta.MetaQueryPlan;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.meta.QueryPlanInit;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@@ -51,4 +54,14 @@ public final class DtoBeanManager {
|
||||
value.visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Arm bind capture for matching (native SQL) DTO query plans.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void queryPlanInit(QueryPlanInit request, List<MetaQueryPlan> list) {
|
||||
for (DtoBeanDescriptor value : descriptorMap.values()) {
|
||||
value.queryPlanInit(request, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,26 +4,56 @@ import io.ebean.ProfileLocation;
|
||||
import io.ebean.metric.MetricFactory;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebeaninternal.api.SpiDtoQuery;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.query.CQueryPlan;
|
||||
import io.ebeaninternal.server.util.Md5;
|
||||
|
||||
/**
|
||||
* Request to map a resultSet columns for a query into a DTO bean.
|
||||
*/
|
||||
public final class DtoMappingRequest {
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
private final Class type;
|
||||
private final String label;
|
||||
private final ProfileLocation profileLocation;
|
||||
private final String sql;
|
||||
private final boolean relaxedMode;
|
||||
private final DtoColumn[] columnMeta;
|
||||
private final String name;
|
||||
private final String hash;
|
||||
private final boolean nativeSql;
|
||||
|
||||
public DtoMappingRequest(SpiDtoQuery query, String sql, DtoColumn[] columnMeta) {
|
||||
public DtoMappingRequest(SpiEbeanServer server, SpiDtoQuery<?> query, String sql, DtoColumn[] columnMeta) {
|
||||
this.server = server;
|
||||
this.type = query.type();
|
||||
this.label = query.planLabel();
|
||||
this.profileLocation = query.profileLocation();
|
||||
this.sql = sql;
|
||||
this.relaxedMode = query.isRelaxedMode();
|
||||
this.columnMeta = columnMeta;
|
||||
this.nativeSql = query.ormQuery() == null;
|
||||
this.name = deriveName(type.getSimpleName(), query.explicitLabel(), profileLocation);
|
||||
String loc = profileLocation == null ? null : profileLocation.location();
|
||||
this.hash = Md5.hash(sql, name, loc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the DTO query plan / metric name, mirroring the ORM convention:
|
||||
* an explicit label is prefixed with the bean type for disambiguation, a
|
||||
* profile location is used as-is (already a unique, type-independent
|
||||
* identifier) and an unlabelled query uses just the bean type.
|
||||
*/
|
||||
private static String deriveName(String simpleName, String explicitLabel, ProfileLocation profileLocation) {
|
||||
if (explicitLabel != null) {
|
||||
return "dto." + CQueryPlan.planLabelWithType(explicitLabel, simpleName);
|
||||
}
|
||||
if (profileLocation != null) {
|
||||
return "dto." + profileLocation.label();
|
||||
}
|
||||
return "dto." + simpleName;
|
||||
}
|
||||
|
||||
public DtoColumn[] columnMeta() {
|
||||
@@ -42,8 +72,41 @@ public final class DtoMappingRequest {
|
||||
return sql;
|
||||
}
|
||||
|
||||
public Class<?> type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public ProfileLocation profileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if this is a native SQL DtoQuery (capturable). ORM-backed DTO
|
||||
* queries capture their query plan via the underlying ORM query plan instead.
|
||||
*/
|
||||
public boolean nativeSql() {
|
||||
return nativeSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the bind capture for the given query plan. Returns the NOOP capture
|
||||
* for ORM-backed DTO queries (captured via the ORM plan) or when query plan
|
||||
* collection is disabled.
|
||||
*/
|
||||
public SpiQueryBindCapture createBindCapture(SpiQueryPlan queryPlan) {
|
||||
return nativeSql ? server.createQueryBindCapture(queryPlan) : SpiQueryBindCapture.NOOP;
|
||||
}
|
||||
|
||||
public QueryPlanMetric createMetric() {
|
||||
return MetricFactory.get().createQueryPlanMetric(type, label, profileLocation, sql);
|
||||
return MetricFactory.get().createQueryPlanMetric(type, name, label, profileLocation, sql, hash);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,13 +2,15 @@ package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.bind.capture.BindCapture;
|
||||
|
||||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* Knows how to read and map rows into a Bean.
|
||||
*/
|
||||
public interface DtoQueryPlan {
|
||||
public interface DtoQueryPlan extends SpiQueryPlan {
|
||||
|
||||
/**
|
||||
* Read the row data and return the DTO bean.
|
||||
@@ -20,6 +22,22 @@ public interface DtoQueryPlan {
|
||||
*/
|
||||
void collect(long exeMicros);
|
||||
|
||||
/**
|
||||
* Return true if the bind values for this (native SQL) query should be
|
||||
* captured in order to later collect the database query plan.
|
||||
*/
|
||||
boolean collectFor(long exeMicros);
|
||||
|
||||
/**
|
||||
* Return true if this plan supports query plan capture (native SQL only).
|
||||
*/
|
||||
boolean supportsPlanCapture();
|
||||
|
||||
/**
|
||||
* Set the captured bind values used to later collect the database query plan.
|
||||
*/
|
||||
void setBind(BindCapture bindCapture, long exeMicros, long startNanos);
|
||||
|
||||
/**
|
||||
* Visit the metric (if not empty).
|
||||
*/
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
package io.ebeaninternal.server.dto;
|
||||
|
||||
import io.ebean.ProfileLocation;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.QueryPlanMetric;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
import io.ebeaninternal.api.SpiDbQueryPlan;
|
||||
import io.ebeaninternal.api.SpiQueryBindCapture;
|
||||
import io.ebeaninternal.api.SpiQueryPlan;
|
||||
import io.ebeaninternal.server.bind.capture.BindCapture;
|
||||
import io.ebeaninternal.server.query.DQueryPlanOutput;
|
||||
|
||||
abstract class DtoQueryPlanBase implements DtoQueryPlan {
|
||||
abstract class DtoQueryPlanBase implements DtoQueryPlan, SpiQueryPlan {
|
||||
|
||||
private final QueryPlanMetric planMetric;
|
||||
|
||||
private final TimedMetric metric;
|
||||
private final Class<?> beanType;
|
||||
private final String name;
|
||||
private final String hash;
|
||||
private final String sql;
|
||||
private final ProfileLocation profileLocation;
|
||||
private final boolean nativeSql;
|
||||
private final SpiQueryBindCapture bindCapture;
|
||||
|
||||
DtoQueryPlanBase(DtoMappingRequest request) {
|
||||
this.planMetric = request.createMetric();
|
||||
this.metric = planMetric.metric();
|
||||
this.beanType = request.type();
|
||||
this.name = request.name();
|
||||
this.hash = request.hash();
|
||||
this.sql = request.sql();
|
||||
this.profileLocation = request.profileLocation();
|
||||
this.nativeSql = request.nativeSql();
|
||||
this.bindCapture = request.createBindCapture(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -20,8 +39,58 @@ abstract class DtoQueryPlanBase implements DtoQueryPlan {
|
||||
metric.add(exeTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean collectFor(long exeMicros) {
|
||||
return bindCapture.collectFor(exeMicros);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsPlanCapture() {
|
||||
return nativeSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBind(BindCapture capture, long exeMicros, long startNanos) {
|
||||
bindCapture.setBind(capture, exeMicros, startNanos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
planMetric.visit(visitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> beanType() {
|
||||
return beanType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sql() {
|
||||
return sql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProfileLocation profileLocation() {
|
||||
return profileLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryPlanInit(long thresholdMicros) {
|
||||
bindCapture.queryPlanInit(thresholdMicros);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiDbQueryPlan createMeta(String bind, String planString) {
|
||||
return new DQueryPlanOutput(beanType, name, hash, sql, profileLocation, bind, planString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.ebean.PersistenceIOException;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
import io.ebean.text.json.JsonBeanReader;
|
||||
@@ -45,7 +45,7 @@ public final class DJsonBeanReader<T> implements JsonBeanReader<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonBeanReader<T> forJson(JsonParser moreJson) {
|
||||
public JsonBeanReader<T> forJson(JsonReader moreJson) {
|
||||
return new DJsonBeanReader<>(desc, readJson.forJson(moreJson));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package io.ebeaninternal.server.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonFactory;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.core.PrettyPrinter;
|
||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.JsonConfig;
|
||||
@@ -28,7 +25,6 @@ import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.io.Writer;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -45,30 +41,22 @@ import java.util.Set;
|
||||
*/
|
||||
public final class DJsonContext implements SpiJsonContext {
|
||||
|
||||
private static final PrettyPrinter PRETTY_PRINTER = new Pretty();
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
private final JsonFactory jsonFactory;
|
||||
private final JsonStream jsonStream;
|
||||
private final Object defaultObjectMapper;
|
||||
private final JsonConfig.Include defaultInclude;
|
||||
private final DJsonScalar jsonScalar;
|
||||
|
||||
private static class Pretty extends DefaultPrettyPrinter {
|
||||
Pretty() {
|
||||
_objectFieldValueSeparatorWithSpaces = ": ";
|
||||
}
|
||||
}
|
||||
|
||||
public DJsonContext(SpiEbeanServer server, JsonFactory jsonFactory, TypeManager typeManager) {
|
||||
public DJsonContext(SpiEbeanServer server, JsonStream jsonStream, TypeManager typeManager) {
|
||||
this.server = server;
|
||||
this.jsonFactory = (jsonFactory != null) ? jsonFactory : new JsonFactory();
|
||||
this.jsonStream = jsonStream;
|
||||
this.defaultObjectMapper = this.server.config().getObjectMapper();
|
||||
this.defaultInclude = this.server.config().getJsonInclude();
|
||||
this.jsonScalar = new DJsonScalar(typeManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeScalar(JsonGenerator generator, Object scalarValue) throws IOException {
|
||||
public void writeScalar(JsonWriter generator, Object scalarValue) throws IOException {
|
||||
jsonScalar.write(generator, scalarValue);
|
||||
}
|
||||
|
||||
@@ -78,31 +66,34 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonGenerator createGenerator(Writer writer) throws JsonIOException {
|
||||
public JsonWriter createGenerator(Writer writer) throws JsonIOException {
|
||||
JsonWriter jsonWriter = stream().writer(writer);
|
||||
jsonWriter.serializeNulls(defaultInclude == JsonConfig.Include.ALL);
|
||||
jsonWriter.serializeEmpty(defaultInclude != JsonConfig.Include.NON_EMPTY);
|
||||
return jsonWriter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonReader createParser(Reader reader) throws JsonIOException {
|
||||
try {
|
||||
return jsonFactory.createGenerator(writer);
|
||||
return createParser(readAll(reader));
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonParser createParser(Reader reader) throws JsonIOException {
|
||||
try {
|
||||
return jsonFactory.createParser(reader);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
private JsonStream stream() {
|
||||
return (jsonStream != null) ? jsonStream : JsonStream.builder().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T toBean(Class<T> cls, String json) throws JsonIOException {
|
||||
return toBean(cls, new StringReader(json));
|
||||
return toBean(cls, createParser(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T toBean(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
|
||||
return toBean(cls, new StringReader(json), options);
|
||||
return toBean(cls, createParser(json), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,15 +107,15 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T toBean(Class<T> cls, JsonParser parser) throws JsonIOException {
|
||||
public <T> T toBean(Class<T> cls, JsonReader parser) throws JsonIOException {
|
||||
return toBean(cls, parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T toBean(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
public <T> T toBean(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException {
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
try {
|
||||
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false), null, null);
|
||||
return desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream()), null, null);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
@@ -132,12 +123,12 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
|
||||
@Override
|
||||
public <T> void toBean(T target, String json) throws JsonIOException {
|
||||
toBean(target, new StringReader(json));
|
||||
toBean(target, createParser(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void toBean(T target, String json, JsonReadOptions options) throws JsonIOException {
|
||||
toBean(target, new StringReader(json), options);
|
||||
toBean(target, createParser(json), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -151,42 +142,42 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void toBean(T target, JsonParser parser) throws JsonIOException {
|
||||
public <T> void toBean(T target, JsonReader parser) throws JsonIOException {
|
||||
toBean(target, parser, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> void toBean(T target, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
public <T> void toBean(T target, JsonReader parser, JsonReadOptions options) throws JsonIOException {
|
||||
BeanDescriptor<T> desc = (BeanDescriptor<T>) getDescriptor(target.getClass());
|
||||
try {
|
||||
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null), null, target);
|
||||
desc.jsonRead(new ReadJson(desc, parser, options, determineObjectMapper(options), target != null, stream()), null, target);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
public <T> DJsonBeanReader<T> createBeanReader(Class<T> cls, JsonReader parser, JsonReadOptions options) throws JsonIOException {
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false));
|
||||
return new DJsonBeanReader<>(desc, new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonParser parser, JsonReadOptions options) throws JsonIOException {
|
||||
public <T> DJsonBeanReader<T> createBeanReader(BeanType<T> beanType, JsonReader parser, JsonReadOptions options) throws JsonIOException {
|
||||
BeanDescriptor<T> desc = (BeanDescriptor<T>) beanType;
|
||||
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false);
|
||||
SpiJsonReader readJson = new ReadJson(desc, parser, options, determineObjectMapper(options), false, stream());
|
||||
return new DJsonBeanReader<>(desc, readJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> cls, String json) throws JsonIOException {
|
||||
return toList(cls, new StringReader(json));
|
||||
return toList(cls, createParser(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> cls, String json, JsonReadOptions options) throws JsonIOException {
|
||||
return toList(cls, new StringReader(json), options);
|
||||
return toList(cls, createParser(json), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,35 +191,30 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> cls, JsonParser src) throws JsonIOException {
|
||||
public <T> List<T> toList(Class<T> cls, JsonReader src) throws JsonIOException {
|
||||
return toList(cls, src, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<T> toList(Class<T> cls, JsonParser src, JsonReadOptions options) throws JsonIOException {
|
||||
public <T> List<T> toList(Class<T> cls, JsonReader src, JsonReadOptions options) throws JsonIOException {
|
||||
BeanDescriptor<T> desc = getDescriptor(cls);
|
||||
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false);
|
||||
SpiJsonReader readJson = new ReadJson(desc, src, options, determineObjectMapper(options), false, stream());
|
||||
try {
|
||||
|
||||
JsonToken currentToken = src.getCurrentToken();
|
||||
if (currentToken != JsonToken.START_ARRAY) {
|
||||
JsonToken event = src.nextToken();
|
||||
if (event != JsonToken.START_ARRAY) {
|
||||
throw new JsonParseException(src, "Expecting start_array event but got " + event);
|
||||
}
|
||||
if (src.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
Token currentToken = src.currentToken();
|
||||
if (currentToken != Token.BEGIN_ARRAY) {
|
||||
throw new JsonIOException("Expecting BEGIN_ARRAY but got " + currentToken);
|
||||
}
|
||||
|
||||
List<T> list = new ArrayList<>();
|
||||
do {
|
||||
// CHECKME: Should we update the list
|
||||
src.beginArray();
|
||||
while (src.hasNextElement()) {
|
||||
T bean = desc.jsonRead(readJson, null, null);
|
||||
if (bean == null) {
|
||||
break;
|
||||
} else {
|
||||
if (bean != null) {
|
||||
list.add(bean);
|
||||
}
|
||||
} while (true);
|
||||
|
||||
}
|
||||
return list;
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
@@ -237,7 +223,7 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
|
||||
@Override
|
||||
public Object toObject(Type genericType, String json) throws JsonIOException {
|
||||
return toObject(genericType, createParser(new StringReader(json)));
|
||||
return toObject(genericType, createParser(json));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -245,8 +231,22 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
return toObject(genericType, createParser(json));
|
||||
}
|
||||
|
||||
private JsonReader createParser(String json) {
|
||||
return stream().reader(json);
|
||||
}
|
||||
|
||||
private String readAll(Reader reader) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
char[] buffer = new char[2048];
|
||||
int len;
|
||||
while ((len = reader.read(buffer)) != -1) {
|
||||
builder.append(buffer, 0, len);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object toObject(Type genericType, JsonParser jsonParser) throws JsonIOException {
|
||||
public Object toObject(Type genericType, JsonReader jsonParser) throws JsonIOException {
|
||||
TypeInfo info = ParamTypeHelper.getTypeInfo(genericType);
|
||||
ManyType manyType = info.getManyType();
|
||||
switch (manyType) {
|
||||
@@ -262,19 +262,19 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(Object value, JsonGenerator generator) throws JsonIOException {
|
||||
public void toJson(Object value, JsonWriter generator) throws JsonIOException {
|
||||
// generator passed in so don't close it
|
||||
toJsonNoClose(value, generator, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(Object value, JsonGenerator generator, FetchPath fetchPath) throws JsonIOException {
|
||||
public void toJson(Object value, JsonWriter generator, FetchPath fetchPath) throws JsonIOException {
|
||||
// generator passed in so don't close it
|
||||
toJsonNoClose(value, generator, JsonWriteOptions.pathProperties(fetchPath));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toJson(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException {
|
||||
public void toJson(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
|
||||
// generator passed in so don't close it
|
||||
toJsonNoClose(o, generator, options);
|
||||
}
|
||||
@@ -303,9 +303,9 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the JsonGenerator and close when complete.
|
||||
* Write to the JsonWriter and close when complete.
|
||||
*/
|
||||
private void toJsonWithClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException {
|
||||
private void toJsonWithClose(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
|
||||
try {
|
||||
toJsonInternal(o, generator, options);
|
||||
generator.close();
|
||||
@@ -315,9 +315,9 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the JsonGenerator and without closing it (as it was created externally).
|
||||
* Write to the JsonWriter and without closing it (as it was created externally).
|
||||
*/
|
||||
private void toJsonNoClose(Object o, JsonGenerator generator, JsonWriteOptions options) throws JsonIOException {
|
||||
private void toJsonNoClose(Object o, JsonWriter generator, JsonWriteOptions options) throws JsonIOException {
|
||||
try {
|
||||
toJsonInternal(o, generator, options);
|
||||
} catch (IOException e) {
|
||||
@@ -342,9 +342,9 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
|
||||
private String toJsonString(Object value, JsonWriteOptions options, boolean pretty) throws JsonIOException {
|
||||
StringWriter writer = new StringWriter(500);
|
||||
try (JsonGenerator gen = createGenerator(writer)) {
|
||||
try (JsonWriter gen = createGenerator(writer)) {
|
||||
if (pretty) {
|
||||
gen.setPrettyPrinter(PRETTY_PRINTER);
|
||||
gen.pretty(true);
|
||||
}
|
||||
toJsonInternal(value, gen, options);
|
||||
} catch (IOException e) {
|
||||
@@ -354,29 +354,23 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void toJsonInternal(Object value, JsonGenerator gen, JsonWriteOptions options) throws IOException {
|
||||
private void toJsonInternal(Object value, JsonWriter gen, JsonWriteOptions options) throws IOException {
|
||||
if (value == null) {
|
||||
gen.writeNull();
|
||||
gen.nullValue();
|
||||
} else if (value instanceof Number) {
|
||||
gen.writeNumber(((Number) value).doubleValue());
|
||||
gen.jsonValue(value);
|
||||
} else if (value instanceof Boolean) {
|
||||
gen.writeBoolean((Boolean) value);
|
||||
gen.value((Boolean) value);
|
||||
} else if (value instanceof String) {
|
||||
gen.writeString((String) value);
|
||||
|
||||
// } else if (o instanceof JsonElement) {
|
||||
|
||||
gen.value((String) value);
|
||||
} else if (value instanceof Map<?, ?>) {
|
||||
toJsonFromMap((Map<Object, Object>) value, gen, options);
|
||||
|
||||
} else if (value instanceof Collection<?>) {
|
||||
toJsonFromCollection((Collection<?>) value, null, gen, options);
|
||||
|
||||
} else if (value instanceof EntityBean) {
|
||||
BeanDescriptor<?> d = getDescriptor(value.getClass());
|
||||
WriteJson writeJson = createWriteJson(gen, options);
|
||||
d.jsonWrite(writeJson, (EntityBean) value, null);
|
||||
|
||||
} else {
|
||||
jsonScalar.write(gen, value);
|
||||
}
|
||||
@@ -385,8 +379,8 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
@Override
|
||||
public SpiJsonReader createJsonRead(BeanType<?> beanType, String json) {
|
||||
BeanDescriptor<?> desc = (BeanDescriptor<?>) beanType;
|
||||
JsonParser parser = createParser(new StringReader(json));
|
||||
return new ReadJson(desc, parser, null, defaultObjectMapper, false);
|
||||
JsonReader parser = createParser(json);
|
||||
return new ReadJson(desc, parser, null, defaultObjectMapper, false, stream());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -395,11 +389,11 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiJsonWriter createJsonWriter(JsonGenerator gen, JsonWriteOptions options) {
|
||||
public SpiJsonWriter createJsonWriter(JsonWriter gen, JsonWriteOptions options) {
|
||||
return createWriteJson(gen, options);
|
||||
}
|
||||
|
||||
private WriteJson createWriteJson(JsonGenerator gen, JsonWriteOptions options) {
|
||||
private WriteJson createWriteJson(JsonWriter gen, JsonWriteOptions options) {
|
||||
FetchPath pathProps = (options == null) ? null : options.getPathProperties();
|
||||
Map<String, JsonWriteBeanVisitor<?>> visitors = (options == null) ? null : options.getVisitorMap();
|
||||
return new WriteJson(server,
|
||||
@@ -411,46 +405,50 @@ public final class DJsonContext implements SpiJsonContext {
|
||||
options == null || options.isIncludeLoadedImplicit());
|
||||
}
|
||||
|
||||
private <T> void toJsonFromCollection(Collection<T> collection, String key, JsonGenerator gen, JsonWriteOptions options) throws IOException {
|
||||
private <T> void toJsonFromCollection(Collection<T> collection, String key, JsonWriter gen, JsonWriteOptions options) throws IOException {
|
||||
if (key != null) {
|
||||
gen.writeFieldName(key);
|
||||
gen.name(key);
|
||||
}
|
||||
gen.writeStartArray();
|
||||
gen.beginArray();
|
||||
WriteJson writeJson = createWriteJson(gen, options);
|
||||
for (T bean : collection) {
|
||||
BeanDescriptor<?> d = getDescriptor(bean.getClass());
|
||||
d.jsonWrite(writeJson, (EntityBean) bean, null);
|
||||
if (bean == null) {
|
||||
gen.nullValue();
|
||||
} else if (bean instanceof EntityBean) {
|
||||
BeanDescriptor<?> d = getDescriptor(bean.getClass());
|
||||
d.jsonWrite(writeJson, (EntityBean) bean, null);
|
||||
} else {
|
||||
EJson.write(bean, gen);
|
||||
}
|
||||
}
|
||||
gen.writeEndArray();
|
||||
gen.endArray();
|
||||
}
|
||||
|
||||
private void toJsonFromMap(Map<Object, Object> map, JsonGenerator gen, JsonWriteOptions options) throws IOException {
|
||||
private void toJsonFromMap(Map<Object, Object> map, JsonWriter gen, JsonWriteOptions options) throws IOException {
|
||||
Set<Entry<Object, Object>> entrySet = map.entrySet();
|
||||
Iterator<Entry<Object, Object>> it = entrySet.iterator();
|
||||
|
||||
WriteJson writeJson = createWriteJson(gen, options);
|
||||
gen.writeStartObject();
|
||||
gen.beginObject();
|
||||
|
||||
while (it.hasNext()) {
|
||||
Entry<Object, Object> entry = it.next();
|
||||
String key = entry.getKey().toString();
|
||||
Object value = entry.getValue();
|
||||
if (value == null) {
|
||||
gen.writeNullField(key);
|
||||
gen.name(key);
|
||||
gen.nullValue();
|
||||
} else if (value instanceof Collection<?>) {
|
||||
toJsonFromCollection((Collection<?>) value, key, gen, options);
|
||||
} else if (value instanceof EntityBean) {
|
||||
BeanDescriptor<?> d = getDescriptor(value.getClass());
|
||||
d.jsonWrite(writeJson, (EntityBean) value, key);
|
||||
} else {
|
||||
if (value instanceof Collection<?>) {
|
||||
toJsonFromCollection((Collection<?>) value, key, gen, options);
|
||||
|
||||
} else if (value instanceof EntityBean) {
|
||||
BeanDescriptor<?> d = getDescriptor(value.getClass());
|
||||
d.jsonWrite(writeJson, (EntityBean) value, key);
|
||||
|
||||
} else {
|
||||
EJson.write(entry, gen);
|
||||
}
|
||||
gen.name(key);
|
||||
EJson.write(value, gen);
|
||||
}
|
||||
}
|
||||
gen.writeEndObject();
|
||||
gen.endObject();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebeaninternal.server.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebeaninternal.server.type.TypeManager;
|
||||
|
||||
@@ -19,21 +19,18 @@ public final class DJsonScalar {
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void write(JsonGenerator gen, Object value) throws IOException {
|
||||
public void write(JsonWriter gen, Object value) throws IOException {
|
||||
if (value instanceof String) {
|
||||
gen.writeString((String) value);
|
||||
gen.value((String) value);
|
||||
|
||||
} else if (value instanceof List) {
|
||||
// expected for @DbArray values
|
||||
List list = (List)value;
|
||||
gen.writeRaw('[');
|
||||
gen.beginArray();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i > 0) {
|
||||
gen.writeRaw(',');
|
||||
}
|
||||
write(gen, list.get(i));
|
||||
}
|
||||
gen.writeRaw(']');
|
||||
gen.endArray();
|
||||
|
||||
} else {
|
||||
ScalarType scalarType = typeManager.type(value.getClass());
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package io.ebeaninternal.server.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonToken;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonReader.Token;
|
||||
import io.avaje.json.stream.BufferRecycleStrategy;
|
||||
import io.avaje.json.stream.JsonStream;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.PersistenceContext;
|
||||
@@ -22,8 +24,9 @@ import java.util.Map;
|
||||
*/
|
||||
public final class ReadJson implements SpiJsonReader {
|
||||
|
||||
private final JsonStream jsonStream;
|
||||
private final BeanDescriptor<?> rootDesc;
|
||||
private final JsonParser parser;
|
||||
private final JsonReader parser;
|
||||
private final PathStack pathStack;
|
||||
/**
|
||||
* Map of the JsonReadBeanVisitor keyed by path.
|
||||
@@ -38,9 +41,10 @@ public final class ReadJson implements SpiJsonReader {
|
||||
/**
|
||||
* Construct with parser and readOptions.
|
||||
*/
|
||||
public ReadJson(BeanDescriptor<?> desc, JsonParser parser, JsonReadOptions readOptions, Object objectMapper, boolean update) {
|
||||
public ReadJson(BeanDescriptor<?> desc, JsonReader parser, JsonReadOptions readOptions, Object objectMapper, boolean update, JsonStream jsonStream) {
|
||||
this.rootDesc = desc;
|
||||
this.parser = parser;
|
||||
this.jsonStream = jsonStream;
|
||||
this.objectMapper = objectMapper;
|
||||
this.persistenceContext = initPersistenceContext(readOptions);
|
||||
this.loadContext = initLoadContext(desc, readOptions);
|
||||
@@ -54,7 +58,8 @@ public final class ReadJson implements SpiJsonReader {
|
||||
/**
|
||||
* Construct when transferring load context, persistence context, object mapper etc to a new ReadJson instance.
|
||||
*/
|
||||
private ReadJson(JsonParser moreJson, ReadJson source) {
|
||||
private ReadJson(JsonReader moreJson, ReadJson source) {
|
||||
this.jsonStream = source.jsonStream;
|
||||
this.parser = moreJson;
|
||||
this.rootDesc = source.rootDesc;
|
||||
this.pathStack = source.pathStack;
|
||||
@@ -91,13 +96,22 @@ public final class ReadJson implements SpiJsonReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance of ReadJson using the existing context but with a new JsonParser.
|
||||
* Return a new instance of ReadJson using the existing context but with a new JsonReader.
|
||||
*/
|
||||
@Override
|
||||
public SpiJsonReader forJson(JsonParser moreJson) {
|
||||
public SpiJsonReader forJson(JsonReader moreJson) {
|
||||
return new ReadJson(moreJson, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpiJsonReader forJson(String moreJson) {
|
||||
JsonReader nestedReader = JsonStream.builder()
|
||||
.bufferRecycling(BufferRecycleStrategy.NO_RECYCLING)
|
||||
.build()
|
||||
.reader(moreJson);
|
||||
return new ReadJson(nestedReader, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the bean to the persistence context.
|
||||
*/
|
||||
@@ -152,19 +166,19 @@ public final class ReadJson implements SpiJsonReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JsonParser.
|
||||
* Return the JsonReader.
|
||||
*/
|
||||
@Override
|
||||
public JsonParser parser() {
|
||||
public JsonReader parser() {
|
||||
return parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the next JsonToken from the underlying parser.
|
||||
* Return the current token from the underlying parser.
|
||||
*/
|
||||
@Override
|
||||
public JsonToken nextToken() throws IOException {
|
||||
return parser.nextToken();
|
||||
public Token nextToken() throws IOException {
|
||||
return parser.currentToken();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +223,7 @@ public final class ReadJson implements SpiJsonReader {
|
||||
*/
|
||||
@Override
|
||||
public Object readValueUsingObjectMapper(Class<?> propertyType) throws IOException {
|
||||
return mapper().readValue(parser, propertyType);
|
||||
return mapper().readValue(parser.readRaw(), propertyType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package io.ebeaninternal.server.json;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.ebean.FetchPath;
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.config.JsonConfig;
|
||||
@@ -24,7 +24,7 @@ import java.util.Set;
|
||||
public final class WriteJson implements SpiJsonWriter {
|
||||
|
||||
private final SpiEbeanServer server;
|
||||
private final JsonGenerator generator;
|
||||
private final JsonWriter generator;
|
||||
private final FetchPath fetchPath;
|
||||
private final Map<String, JsonWriteBeanVisitor<?>> visitors;
|
||||
private final PathStack pathStack;
|
||||
@@ -37,7 +37,7 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
/**
|
||||
* Construct for full bean use (normal).
|
||||
*/
|
||||
public WriteJson(SpiEbeanServer server, JsonGenerator generator, FetchPath fetchPath,
|
||||
public WriteJson(SpiEbeanServer server, JsonWriter generator, FetchPath fetchPath,
|
||||
Map<String, JsonWriteBeanVisitor<?>> visitors, Object objectMapper, JsonConfig.Include include,
|
||||
boolean includeLoadedImplicit) {
|
||||
|
||||
@@ -50,12 +50,14 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
this.includeLoadedImplicit = includeLoadedImplicit;
|
||||
this.parentBeans = new ArrayStack<>();
|
||||
this.pathStack = new PathStack();
|
||||
this.generator.serializeNulls(isIncludeNull());
|
||||
this.generator.serializeEmpty(isIncludeEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct for Json scalar use.
|
||||
*/
|
||||
public WriteJson(JsonGenerator generator, JsonConfig.Include include) {
|
||||
public WriteJson(JsonWriter generator, JsonConfig.Include include) {
|
||||
this.generator = generator;
|
||||
this.include = include;
|
||||
this.includeLoadedImplicit = true;
|
||||
@@ -65,6 +67,8 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
this.objectMapper = null;
|
||||
this.parentBeans = null;
|
||||
this.pathStack = null;
|
||||
this.generator.serializeNulls(isIncludeNull());
|
||||
this.generator.serializeEmpty(isIncludeEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +88,7 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonGenerator gen() {
|
||||
public JsonWriter gen() {
|
||||
return generator;
|
||||
}
|
||||
|
||||
@@ -95,171 +99,113 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
|
||||
@Override
|
||||
public void writeStartObject(String key) {
|
||||
try {
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
generator.writeStartObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
if (key != null) {
|
||||
generator.name(key);
|
||||
}
|
||||
generator.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartObject() {
|
||||
try {
|
||||
generator.writeStartObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.beginObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndObject() {
|
||||
try {
|
||||
generator.writeEndObject();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartArray(String key) {
|
||||
try {
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
if (key != null) {
|
||||
generator.name(key);
|
||||
}
|
||||
generator.beginArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStartArray() {
|
||||
try {
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.beginArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeEndArray() {
|
||||
try {
|
||||
generator.writeEndArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRaw(String text) {
|
||||
try {
|
||||
generator.writeRaw(text);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.rawChunkStart();
|
||||
generator.rawChunk(text);
|
||||
generator.rawChunkEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRawValue(String text) {
|
||||
try {
|
||||
generator.writeRawValue(text);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.rawValue(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeFieldName(String name) {
|
||||
try {
|
||||
generator.writeFieldName(name);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNullField(String name) {
|
||||
if (isIncludeNull()) {
|
||||
try {
|
||||
generator.writeNullField(name);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.nullValue();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, long value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, double value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, int value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, short value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, float value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value((double) value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void writeNumberField(String name, BigDecimal value) {
|
||||
try {
|
||||
generator.writeNumberField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeStringField(String name, String value) {
|
||||
try {
|
||||
generator.writeStringField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBinary(InputStream is, int length) {
|
||||
try {
|
||||
generator.writeBinary(is, length);
|
||||
generator.value(is.readNBytes(length));
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
@@ -267,83 +213,49 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
|
||||
@Override
|
||||
public void writeBinaryField(String name, byte[] value) {
|
||||
try {
|
||||
generator.writeBinaryField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBooleanField(String name, boolean value) {
|
||||
try {
|
||||
generator.writeBooleanField(name, value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.name(name);
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBoolean(boolean value) {
|
||||
try {
|
||||
generator.writeBoolean(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeString(String value) {
|
||||
try {
|
||||
generator.writeString(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(int value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(long value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(double value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNumber(BigDecimal value) {
|
||||
try {
|
||||
generator.writeNumber(value);
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeNull() {
|
||||
try {
|
||||
generator.writeNull();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
generator.nullValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -375,55 +287,39 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
|
||||
@Override
|
||||
public void beginAssocMany(String key) {
|
||||
try {
|
||||
pathStack.pushPathKey(key);
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
generator.writeStartArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
pathStack.pushPathKey(key);
|
||||
if (key != null) {
|
||||
generator.name(key);
|
||||
}
|
||||
generator.beginArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAssocMany() {
|
||||
try {
|
||||
pathStack.pop();
|
||||
generator.writeEndArray();
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
pathStack.pop();
|
||||
generator.endArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginAssocManyMap(String key, boolean elementCollection) {
|
||||
try {
|
||||
pathStack.pushPathKey(key);
|
||||
if (key != null) {
|
||||
generator.writeFieldName(key);
|
||||
}
|
||||
if (elementCollection) {
|
||||
generator.writeStartObject();
|
||||
} else {
|
||||
generator.writeStartArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
pathStack.pushPathKey(key);
|
||||
if (key != null) {
|
||||
generator.name(key);
|
||||
}
|
||||
if (elementCollection) {
|
||||
generator.beginObject();
|
||||
} else {
|
||||
generator.beginArray();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAssocManyMap(boolean elementCollection) {
|
||||
try {
|
||||
pathStack.pop();
|
||||
if (elementCollection) {
|
||||
generator.writeEndObject();
|
||||
} else {
|
||||
generator.writeEndArray();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
pathStack.pop();
|
||||
if (elementCollection) {
|
||||
generator.endObject();
|
||||
} else {
|
||||
generator.endArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,8 +361,8 @@ public final class WriteJson implements SpiJsonWriter {
|
||||
}
|
||||
}
|
||||
try {
|
||||
generator.writeFieldName(name);
|
||||
objectMapper().writeValue(generator, value);
|
||||
generator.name(name);
|
||||
generator.rawValue(objectMapper().writeValueAsString(value));
|
||||
} catch (IOException e) {
|
||||
throw new JsonIOException(e);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ abstract class DLoadBaseContext {
|
||||
|
||||
protected final String fullPath;
|
||||
|
||||
protected final String path;
|
||||
|
||||
protected final String serverName;
|
||||
|
||||
final OrmQueryProperties queryProps;
|
||||
@@ -38,6 +40,7 @@ abstract class DLoadBaseContext {
|
||||
this.serverName = parent.server().name();
|
||||
this.desc = desc;
|
||||
this.queryProps = queryProps;
|
||||
this.path = path;
|
||||
this.fullPath = parent.fullPath(path);
|
||||
this.hitCache = parent.isBeanCacheGet() && desc.isBeanCaching();
|
||||
this.objectGraphNode = parent.objectGraphNode(path);
|
||||
@@ -50,15 +53,21 @@ abstract class DLoadBaseContext {
|
||||
* set onto the secondary query.
|
||||
*/
|
||||
void setLabel(SpiQuery<?> query) {
|
||||
String label = parent.planLabel();
|
||||
if (label != null) {
|
||||
query.setProfilePath(label, pathMode(query), parent.profileLocation());
|
||||
String prefix = parent.namePrefix();
|
||||
if (prefix != null) {
|
||||
query.setProfilePath(prefix, pathMode(query), parent.profileLocation());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the parent query's full name with this load's immediate path segment
|
||||
* and load mode. The parent name already carries everything up to this point,
|
||||
* so only the immediate path (not the root relative fullPath) is appended to
|
||||
* avoid duplicating intermediate path segments on nested secondary loads.
|
||||
*/
|
||||
private String pathMode(SpiQuery<?> query) {
|
||||
final var loadMode = query.loadMode();
|
||||
return fullPath == null ? '_' + loadMode : fullPath + "__" + loadMode;
|
||||
return path == null ? loadMode : path + '.' + loadMode;
|
||||
}
|
||||
|
||||
PersistenceContext persistenceContext() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user