mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-09-20 03:16:42 +00:00
Compare commits
48
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4bdec0fda | ||
|
|
328b13af18 | ||
|
|
fd8ef48486 | ||
|
|
69f39f9c82 | ||
|
|
2f1625316b | ||
|
|
952d9940e7 | ||
|
|
c62b63119b | ||
|
|
fdcbb91981 | ||
|
|
0e2b1f95b2 | ||
|
|
80f5cab086 | ||
|
|
90aff3d9ef | ||
|
|
1c857e47b4 | ||
|
|
cc9e67c326 | ||
|
|
7a7dfb96d7 | ||
|
|
3c4e87f3f7 | ||
|
|
f5eee70ec4 | ||
|
|
46a73f6250 | ||
|
|
4a46cc0706 | ||
|
|
94630b7e1e | ||
|
|
aac85eec89 | ||
|
|
a8ec4ee437 | ||
|
|
12b9d0eaae | ||
|
|
eeb32514be | ||
|
|
02936735c1 | ||
|
|
57713a9686 | ||
|
|
4076b0fcbd | ||
|
|
49f6bef2fb | ||
|
|
d6ba4967b6 | ||
|
|
0d0ec79f1e | ||
|
|
1737ae13db | ||
|
|
17b641b163 | ||
|
|
4f6168dfed | ||
|
|
a03a098e1b | ||
|
|
78f17fcd46 | ||
|
|
6895a4c057 | ||
|
|
2362c0816a | ||
|
|
37567e8fe8 | ||
|
|
ce8dc36cf7 | ||
|
|
05f5291359 | ||
|
|
1173962398 | ||
|
|
56f86e0263 | ||
|
|
8751a1a04d | ||
|
|
14e6e3f824 | ||
|
|
a9d37b83d7 | ||
|
|
857e92583d | ||
|
|
14056bd7fb | ||
|
|
149ed8318d | ||
|
|
2fe4c6c456 |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-clickhouse</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-db2</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-hana</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mariadb</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-mysql</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-net-postgis-types</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-nuodb</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-oracle</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -47,19 +47,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlite</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -42,13 +42,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<relativePath>../..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -41,7 +41,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -60,13 +60,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-all</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>composites</artifactId>
|
||||
|
||||
@@ -139,6 +139,35 @@ while keeping DTOs as plain, framework-unattached classes.
|
||||
querybean-generator codegen support (static/instance dispatch, constructor wiring deduplicated by converter
|
||||
type). Test coverage: `tests/test-dto-mapping` `TestDtoConvert`.*
|
||||
|
||||
- **Type-pair (package-level) custom scalar conversion**
|
||||
Motivated by real hand-written mapper code (`EboxMapper`, central-access): the same conversion repeats
|
||||
across many unrelated properties on one target - `DateUtils.toCalendar(...)` on ~9 fields,
|
||||
`parseEnum(EnumType.class, value)` on ~3 - under today's `@DtoConvert` every one of those properties must
|
||||
carry its own repeated annotation. MapStruct solves this by letting a conversion method be defined once
|
||||
(in the mapper or a `uses = {...}` helper) and auto-applying it to *every* property whose source/target
|
||||
types match that method's signature - no per-field wiring. Proposed: a package-level, repeatable
|
||||
`@DtoConverters({ConverterType.class, ...})` (sibling to `@DtoMapping` in `package-info.java`) - the
|
||||
generator indexes every public static/instance method on the referenced type(s) by `(paramType ->
|
||||
returnType)`, then for any property whose source getter type doesn't already match the target field type
|
||||
and which carries no explicit per-property `@DtoConvert`, looks up that type pair and wires it in
|
||||
automatically (same static-vs-instance/`DtoConverterManager` dispatch rules as `@DtoConvert` today). An
|
||||
explicit per-property `@DtoConvert` always overrides the type-level default. Deliberately no built-in
|
||||
conversions shipped by Ebean itself (no implicit `Enum.valueOf`/`.name()`) - the app still owns
|
||||
exception/null-handling semantics (e.g. `parseEnum`'s catch-and-null-on-bad-value), just declares it once
|
||||
instead of per-field.
|
||||
**Status: implemented.** `@DtoConverters(ConverterType.class, ...)` (a single non-repeatable annotation
|
||||
taking a `Class<?>[]`, `@Target({PACKAGE, MODULE})`) is registered once per package/module alongside
|
||||
`@DtoMapping`. The generator indexes every public, single-arg, non-void method on each referenced type by
|
||||
exact `(paramType -> returnType)`; any SCALAR property (plain or `@DtoPath`-renamed) with no explicit
|
||||
`@DtoConvert` and a source/target type mismatch is auto-wired to the matching method (a duplicate/ambiguous
|
||||
type pair across the registered types is a compile-time processor error). List-element-wise conversion and
|
||||
`@DtoRef` (FK-id) properties are out of scope. Test coverage:
|
||||
`tests/test-dto-mapping/.../TestDtoConverters.java` (`UuidConverters`/`UuidShortCodeConverter`,
|
||||
`ContactTypeConverterDto`) - covers same-name auto-dispatch, `@DtoPath`-renamed auto-dispatch, and explicit
|
||||
`@DtoConvert` overriding the registered default.
|
||||
*Inspiration: `EboxMapper` (central-access) hand-written pattern; MapStruct type-signature-matched
|
||||
conversion methods.*
|
||||
|
||||
- **`@DtoMixin` for DTOs that cannot be annotated directly**
|
||||
Some DTOs are generated (e.g. from an OpenAPI spec) and not editable/annotatable, so `@DtoPath`/
|
||||
`@DtoConvert`/`@DtoRef` cannot always be placed directly on the DTO. Introduce a `@DtoMixin(Target.class)`
|
||||
@@ -229,6 +258,44 @@ instead (see "Recipe: adding extra caller-supplied fields after mapping" in
|
||||
*Inspiration: `UserService`/`User` (central-access).*
|
||||
*Status: implemented.*
|
||||
|
||||
- **Setter-based (mutable JavaBean) target construction**
|
||||
Motivated by `EboxMapper` (central-access): its target types (`Ebox`, `MachineSummaryInfo`, from
|
||||
`nz.co.eroad.schema.eroadtypes`, JAXB/XSD-generated legacy SOAP shapes) are plain mutable JavaBeans - a
|
||||
public no-arg constructor plus a `void setXxx(...)` setter per property - neither a positional constructor
|
||||
match nor a RecordBuilder-style fluent builder (see section G above). The generator currently only
|
||||
recognizes those two construction strategies, so this common third shape (typical of JAXB/XSD-generated
|
||||
and many hand-written mutable POJOs) can't be targeted by `@DtoMapping` at all today. Proposed: detect a
|
||||
no-arg constructor plus a `void setXxx(propertyType)` setter per mapped property as a third construction
|
||||
strategy, generating `Target target = new Target(); target.setX(...); ...; return target;` (mirroring the
|
||||
existing `build = AUTO | ALWAYS | NEVER` override precedent from section G for explicit control over which
|
||||
strategy applies). Would also unblock the `mapToBuilder()`-style "populate ignored/derived properties after
|
||||
the generated mapping, before finishing construction" pattern for these targets (currently only available
|
||||
for builder-shaped targets) - relevant to `EboxMapper`'s `machineSummaryInfo` (a genuinely composite,
|
||||
multi-association derived value, out of reach of `@DtoConvert`/`@DtoPath` regardless of this gap, but a
|
||||
natural fit for the same "map base fields via codegen, then set the derived one by hand" pattern already
|
||||
used for `Fleet.assignedMachines`/`assignedDrivers`).
|
||||
*Inspiration: `EboxMapper` (central-access); JAXB/XSD-generated SOAP DTO shapes generally.*
|
||||
**Status: implemented.** `@DtoMapping(setter = AUTO | ALWAYS | NEVER)` mirrors `builder()`'s override
|
||||
precedent. Detection requires a public no-arg constructor plus a public `setXxx(...)` setter for every
|
||||
mapped property - either `void` or fluent-style (returning the target type itself, e.g. `public Target
|
||||
setXxx(...) { ...; return this; }`); the generated code always calls the setter as a bare statement and
|
||||
discards any return value, so either shape works identically. A builder, when selected, always takes
|
||||
priority over setter-based construction. Under the default `AUTO`, setter-based construction is only
|
||||
attempted when the target has no positional constructor matching the mapped properties (arity-based) and
|
||||
no builder was selected - existing positional-constructor and builder-shaped targets are entirely
|
||||
unaffected. `ALWAYS` requires the shape (codegen-time error otherwise); `NEVER` always uses a positional
|
||||
constructor. Generated shape: `Target target = new Target(); target.setX(...); ...; return target;` (a
|
||||
`computeIfAbsent(...)`-wrapped block-lambda variant when the target is nested elsewhere in the graph).
|
||||
Deliberately **no** `mapToBuilder(...)`-style post-construction accessor is generated for this strategy -
|
||||
the returned target is already the final, fully mutable instance (setters are required to be `public`), so
|
||||
a caller can already call e.g. `dto.setExternalRef(...)` directly on the mapped result, exactly the pattern
|
||||
`EboxMapper` already uses by hand; this is unlike the builder strategy, where the intermediate builder is
|
||||
otherwise unreachable after its one-shot `build()` call. Test coverage:
|
||||
`tests/test-dto-mapping/.../TestDtoSetterConstruction.java` (`ContactSetterDto`) - covers auto-detected
|
||||
setter-chain construction plus post-construction population of two `@DtoIgnore` properties (a plain scalar
|
||||
and a `List`) via their public setters; plus `ContactSetterFluentDto` - covers the fluent-setter-return-shape
|
||||
variant.
|
||||
|
||||
### H. Record entity sources
|
||||
|
||||
- **Record-style (bare/fluent) accessors on the source (entity) side**
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
# Guide: `@DbJson` / `@DbJsonB` mapping support — built-in vs Jackson ObjectMapper
|
||||
# Guide: `@DbJson` / `@DbJsonB` mapping support
|
||||
|
||||
## Purpose
|
||||
|
||||
Ebean can map `@DbJson` and `@DbJsonB` properties in two ways:
|
||||
Ebean can map `@DbJson` and `@DbJsonB` properties in three 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.
|
||||
- **Jackson `ObjectMapper`**, provided by **`ebean-jackson-mapper`**.
|
||||
- **Avaje Jsonb**, provided by **`ebean-avajejsonb-mapper`** and its generated adapters.
|
||||
|
||||
This guide lists exactly which property types are handled built-in and which require
|
||||
`ebean-jackson-mapper`.
|
||||
The built-in mapper handles the common natural JSON types. Add one mapper module for
|
||||
typed collections, POJOs, records, and other custom types.
|
||||
|
||||
> If a property type is **not** handled built-in and `ebean-jackson-mapper` is not on the
|
||||
> classpath, Ebean fails fast at startup:
|
||||
> If a property type is **not** handled built-in and no mapper module is on the classpath,
|
||||
> Ebean fails fast at startup:
|
||||
>
|
||||
> ```text
|
||||
> Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper?
|
||||
> Jackson ObjectMapper not present for <property>
|
||||
> Unsupported @DbJson mapping - missing JSON mapper dependency for <property>
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Quick reference
|
||||
|
||||
| Property type | Built-in (avaje-json-core) | Needs `ebean-jackson-mapper` |
|
||||
|---|:---:|:---:|
|
||||
| Property type | Built-in (avaje-json-core) | Mapper module |
|
||||
|---|:---:|---|
|
||||
| `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 | | ✅ |
|
||||
| `List`/`Set` of any other element type (`Integer`, `Double`, `UUID`, `LocalDate`, an enum, a POJO, …) | | Jackson or Avaje Jsonb |
|
||||
| `Map` with a typed value other than `String`/`Object` (`Map<String,Integer>`, `Map<String,UUID>`, …) | | Jackson or Avaje Jsonb |
|
||||
| `Map` with a key other than `String` or an enum (`Map<Integer, …>`, `Map<UUID, …>`) | | Jackson or Avaje Jsonb |
|
||||
| POJOs, records, or any other type | | Jackson or Avaje Jsonb |
|
||||
|
||||
---
|
||||
|
||||
@@ -59,10 +58,10 @@ Postgres `json` / `jsonb` — without `ebean-jackson-mapper`.
|
||||
|
||||
---
|
||||
|
||||
## Everything else → Jackson `ObjectMapper`
|
||||
## Jackson `ObjectMapper`
|
||||
|
||||
Any other `@DbJson` / `@DbJsonB` property routes to the Jackson `ObjectMapper` path, which
|
||||
requires `ebean-jackson-mapper`:
|
||||
`ebean-jackson-mapper` uses a Jackson `ObjectMapper` for the property types not handled
|
||||
built-in:
|
||||
|
||||
- **Typed collections** — `List`/`Set` whose element type is not `String` or `Long`
|
||||
(for example `List<Integer>`, `List<UUID>`, `List<LocalDate>`, `List<MyEnum>`, `List<MyPojo>`).
|
||||
@@ -77,7 +76,7 @@ requires `ebean-jackson-mapper`:
|
||||
|
||||
---
|
||||
|
||||
## Adding `ebean-jackson-mapper`
|
||||
### Adding `ebean-jackson-mapper`
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
@@ -92,6 +91,79 @@ registers the mapper-based JSON support automatically.
|
||||
|
||||
---
|
||||
|
||||
## Avaje Jsonb
|
||||
|
||||
`ebean-avajejsonb-mapper` uses Avaje Jsonb adapters. Annotate each JSON payload type with
|
||||
`@Json`, or use `@Json.Import`, and configure `avaje-jsonb-generator` as an annotation
|
||||
processor.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-avajejsonb-mapper</artifactId>
|
||||
<version>${ebean.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
```xml
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-jsonb-generator</artifactId>
|
||||
<version>${avaje-jsonb.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```java
|
||||
import io.avaje.jsonb.Json;
|
||||
|
||||
@Json
|
||||
public class Address {
|
||||
public String line1;
|
||||
public String city;
|
||||
}
|
||||
```
|
||||
|
||||
Avaje Jsonb preserves a property's declared generic type, so `List<Address>` and other
|
||||
parameterised JSON values use the generated `Address` adapter.
|
||||
|
||||
### Avaje JsonNode
|
||||
|
||||
`@DbJson` and `@DbJsonB` properties declared as `io.avaje.json.node.JsonNode` are supported
|
||||
when the application includes `avaje-json-node`. Its Jsonb component supplies the JSON tree
|
||||
adapters; no application-generated adapter is needed for the node hierarchy.
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-node</artifactId>
|
||||
<version>${avaje-jsonb.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Mapper module selection
|
||||
|
||||
Use exactly one mapper module in an application: `ebean-jackson-mapper` or
|
||||
`ebean-avajejsonb-mapper`. Ebean selects a single `ScalarJsonMapper` service provider, so
|
||||
having both modules on the runtime classpath is not supported.
|
||||
|
||||
The `ebean` composite dependency includes `ebean-jackson-mapper`. Applications using Avaje
|
||||
Jsonb should depend on the individual Ebean modules they need instead of that composite.
|
||||
|
||||
To migrate from Jackson to Avaje Jsonb, remove `ebean-jackson-mapper`, add
|
||||
`ebean-avajejsonb-mapper`, and generate adapters for each JSON payload type.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Enum map keys** are serialised using the enum `name()` (for example `ACTIVE`), not any
|
||||
@@ -102,15 +174,13 @@ registers the mapper-based JSON support automatically.
|
||||
(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.
|
||||
only produces natural JSON types — for example a JSON number always parses to `Long`.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
object/string maps) to avoid an additional mapper module.
|
||||
- Add **`ebean-jackson-mapper`** or **`ebean-avajejsonb-mapper`** when you need rich POJO JSON
|
||||
columns or typed collections / typed-value maps.
|
||||
|
||||
@@ -376,7 +376,12 @@ public class Customer {
|
||||
**Important:**
|
||||
- Use `List<>` not `Set<>` for collections (Set calls equals/hashCode before beans have IDs)
|
||||
- `mappedBy` means Order.customer is the owner
|
||||
- Relationships are lazy-loaded by default
|
||||
- Relationships are lazy-loaded by default — **except** `@OneToOne(mappedBy=...)`,
|
||||
which defaults to `FetchType.EAGER` and adds a `left join` to every default
|
||||
select of the owning entity. Explicitly set `fetch = FetchType.LAZY` on
|
||||
`@OneToOne(mappedBy=...)` fields unless the association is needed on
|
||||
(almost) every load. See "`@OneToOne(mappedBy=...)` is EAGER by default" in
|
||||
the query-beans guide for details.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -431,6 +431,48 @@ List<Customer> customers = new QCustomer()
|
||||
If the caller needs multiple to-many paths or a paged query, be suspicious of a
|
||||
plain `fetch(...)` on those paths. `fetchQuery()` is often the safer default.
|
||||
|
||||
### `@OneToOne(mappedBy=...)` is EAGER by default — mark it LAZY
|
||||
|
||||
The non-owning side of a `@OneToOne` (the side with `mappedBy`) defaults to
|
||||
`FetchType.EAGER` per JPA, same as `@ManyToOne`. Unlike a `@ManyToOne`
|
||||
reference (which is FK-only until `.fetch()`'d), Ebean's default select for an
|
||||
EAGER `@OneToOne(mappedBy=...)` still adds a `left join` to the target table
|
||||
on **every** query for the owning entity — even a plain `findById()` — because
|
||||
there is no local FK column to use as a lazy reference; the only way to know
|
||||
the associated row exists is to join to it.
|
||||
|
||||
If that association is rarely needed (e.g. a rarely-read child/detail table),
|
||||
this join executes on every load of the parent, including in hot-path list
|
||||
queries, and can dominate query cost as more such associations accumulate.
|
||||
|
||||
**Always set `fetch = FetchType.LAZY` on `@OneToOne(mappedBy=...)`
|
||||
associations unless the association is genuinely needed on (almost) every
|
||||
load:**
|
||||
|
||||
```java
|
||||
@OneToOne(mappedBy = "device", fetch = FetchType.LAZY)
|
||||
private SensorBoard sensorBoard;
|
||||
```
|
||||
|
||||
This correctly excludes the join from Ebean's default select clause (verified
|
||||
for FK-based, non-shared-primary-key `@OneToOne` relationships — the common
|
||||
case). Callers that do need the association can still `.fetch("sensorBoard")`
|
||||
explicitly on the query bean.
|
||||
|
||||
**Caveat:** the exclusion is driven by Ebean's default-select-clause
|
||||
mechanism. It is bypassed if the query has already been switched into an
|
||||
"all properties" mode by something other than the deploy-time
|
||||
`FetchType.LAZY`/`EAGER` metadata (for example, an active AutoTune profile
|
||||
that supplies its own tuned property set). Confirm the join is actually gone
|
||||
by checking generated SQL (`LoggedSql` in tests, or query logging) after
|
||||
making this change — don't assume it's excluded from the annotation alone.
|
||||
|
||||
### Agent rule
|
||||
|
||||
Default new `@OneToOne(mappedBy=...)` fields to `fetch = FetchType.LAZY`
|
||||
unless there's a clear reason the association is needed on every load. This
|
||||
is a one-line, low-risk change that avoids an always-on join.
|
||||
|
||||
---
|
||||
|
||||
## Step 8 - Use DTO projection when the caller does not need entity beans
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean api</name>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Query for performing native SQL queries that return DTO Bean's.
|
||||
@@ -43,12 +36,7 @@ import java.util.stream.Stream;
|
||||
* }</pre>
|
||||
*/
|
||||
@NullMarked
|
||||
public interface DtoQuery<T> extends CancelableQuery {
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
List<T> findList();
|
||||
public interface DtoQuery<T> extends StreamableQuery<DtoQuery<T>, T> {
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time.
|
||||
@@ -59,56 +47,6 @@ public interface DtoQuery<T> extends CancelableQuery {
|
||||
*/
|
||||
QueryIterator<T> findIterate();
|
||||
|
||||
/**
|
||||
* Execute the query returning a Stream.
|
||||
* <p>
|
||||
* Note that the Stream holds resources related to the underlying
|
||||
* resultSet and potentially connection and MUST be closed. We should use
|
||||
* the Stream in a <em>try with resource block</em>.
|
||||
*/
|
||||
Stream<T> findStream();
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time.
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating the results and batching them for the consumer.
|
||||
* <p>
|
||||
* This runs like findEach streaming results from the database but just collects the results
|
||||
* into batches to pass to the consumer.
|
||||
*
|
||||
* @param batch The number of dto beans to collect before given them to the consumer
|
||||
* @param consumer The consumer to process the batch of DTO beans
|
||||
*/
|
||||
void findEach(int batch, Consumer<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
|
||||
* <p>
|
||||
* Returning false after processing a row stops the iteration through the query results.
|
||||
* </p>
|
||||
* <p>
|
||||
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
|
||||
* </p>
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Bind all the parameters using index positions.
|
||||
* <p>
|
||||
@@ -214,38 +152,6 @@ public interface DtoQuery<T> extends CancelableQuery {
|
||||
*/
|
||||
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
|
||||
|
||||
/**
|
||||
* Use the explicit transaction to execute the query.
|
||||
*/
|
||||
DtoQuery<T> usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
DtoQuery<T> usingConnection(Connection connection);
|
||||
|
||||
/**
|
||||
* Ensure that the master DataSource is used if there is a read only data source
|
||||
* being used (that is using a read replica database potentially with replication lag).
|
||||
* <p>
|
||||
* When the database is configured with a read-only DataSource via
|
||||
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)} then
|
||||
* by default when a query is run without an active transaction, it uses the read-only data
|
||||
* source. We use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
default DtoQuery<T> usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
DtoQuery<T> usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
@@ -281,6 +187,7 @@ public interface DtoQuery<T> extends CancelableQuery {
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
@Override
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* List of Expressions that make up a where or having clause.
|
||||
@@ -421,6 +422,26 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean or throwing a {@link jakarta.persistence.EntityNotFoundException}
|
||||
* if there is no matching bean.
|
||||
*
|
||||
* @see Query#findOneOrThrow()
|
||||
*/
|
||||
default T findOneOrThrow() {
|
||||
return query().findOneOrThrow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query returning a single bean or throwing the exception produced by the
|
||||
* given supplier if there is no matching bean.
|
||||
*
|
||||
* @see Query#findOneOrThrow(Supplier)
|
||||
*/
|
||||
default T findOneOrThrow(Supplier<? extends RuntimeException> exceptionSupplier) {
|
||||
return query().findOneOrThrow(exceptionSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute find row count query in a background thread.
|
||||
* <p>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Common find operations shared by the query types that can execute and return
|
||||
* results - {@link SqlQuery}, {@link DtoQuery}, {@link MappedQuery} and {@link QueryBuilder}.
|
||||
*
|
||||
* @param <SELF> The query type (used for method chaining)
|
||||
* @param <T> The type of the result
|
||||
*/
|
||||
@NullMarked
|
||||
public interface FindableQuery<SELF extends FindableQuery<SELF, T>, T> extends CancelableQuery {
|
||||
|
||||
/**
|
||||
* Execute the query returning the list of results.
|
||||
*/
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result, or {@code null} if there is no matching row.
|
||||
* <p>
|
||||
* If more than 1 row is found for this query then a PersistenceException is thrown.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional result.
|
||||
*/
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result or throwing a
|
||||
* {@link jakarta.persistence.EntityNotFoundException} if there is no matching row.
|
||||
*/
|
||||
default T findOneOrThrow() {
|
||||
return findOneOrEmpty().orElseThrow(() -> new EntityNotFoundException("Not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query returning a single result or throwing the exception produced
|
||||
* by the given supplier if there is no matching row.
|
||||
*/
|
||||
default T findOneOrThrow(Supplier<? extends RuntimeException> exceptionSupplier) {
|
||||
return findOneOrEmpty().orElseThrow(exceptionSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query using the given transaction.
|
||||
*/
|
||||
SELF usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
SELF usingConnection(Connection connection);
|
||||
|
||||
/**
|
||||
* Ensure that the master DataSource is used if there is a read only data source
|
||||
* being used (that is using a read replica database potentially with replication lag).
|
||||
* <p>
|
||||
* When the database is configured with a read-only DataSource via
|
||||
* say {@link DatabaseBuilder#readOnlyDataSource(DataSource)} then
|
||||
* by default when a query is run without an active transaction, it uses the read-only data
|
||||
* source. We use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
default SELF usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
SELF usingMaster(boolean useMaster);
|
||||
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -22,11 +21,12 @@ import java.util.stream.Stream;
|
||||
* @param <D> the target DTO type
|
||||
*/
|
||||
@NullMarked
|
||||
public interface MappedQuery<D> {
|
||||
public interface MappedQuery<D> extends StreamableQuery<MappedQuery<D>, D> {
|
||||
|
||||
/**
|
||||
* Execute the query returning the mapped DTO list.
|
||||
*/
|
||||
@Override
|
||||
List<D> findList();
|
||||
|
||||
/**
|
||||
@@ -38,6 +38,7 @@ public interface MappedQuery<D> {
|
||||
* ({@link PagedList#getTotalCount()}, {@link PagedList#hasNext()}, etc.) reflects the
|
||||
* underlying entity query and is unaffected by the DTO mapping.
|
||||
*/
|
||||
@Override
|
||||
PagedList<D> findPagedList();
|
||||
|
||||
/**
|
||||
@@ -60,33 +61,51 @@ public interface MappedQuery<D> {
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Override
|
||||
Stream<D> findStream();
|
||||
|
||||
/**
|
||||
* Execute the query returning a single mapped DTO, or {@code null} if there is no matching row.
|
||||
* Execute the query processing the mapped DTOs one at a time.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEach(Consumer)} - the underlying entity graph query is
|
||||
* streamed one entity at a time and each entity is mapped to its target DTO lazily as it is
|
||||
* consumed, sharing one {@link DtoMapContext} across the whole callback so that repeated
|
||||
* references to the same source entity still de-duplicate to the same DTO instance.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the mapped DTOs are
|
||||
* consumed one at a time and do not need to be held in memory (unlike {@link #findList()}).
|
||||
*
|
||||
* @param consumer the consumer used to process the mapped DTOs.
|
||||
*/
|
||||
@Nullable
|
||||
D findOne();
|
||||
@Override
|
||||
void findEach(Consumer<D> consumer);
|
||||
/**
|
||||
* Execute findEach streaming query batching the mapped DTOs for consuming.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEach(int, Consumer)} - typically used when we want to do
|
||||
* further processing on the mapped DTOs in batch form, for example 100 at a time. Each batch
|
||||
* shares one {@link DtoMapContext} with the rest of the query so that repeated references to
|
||||
* the same source entity still de-duplicate to the same DTO instance.
|
||||
*
|
||||
* @param batch The number of mapped DTOs processed in the batch
|
||||
* @param consumer Process the batch of mapped DTOs
|
||||
*/
|
||||
@Override
|
||||
void findEach(int batch, Consumer<List<D>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional mapped DTO.
|
||||
* Execute the query using callbacks to process the resulting mapped DTOs one at a time,
|
||||
* with the ability to stop processing part way through.
|
||||
* <p>
|
||||
* Mirrors {@link QueryBuilder#findEachWhile(Predicate)} - returning {@code false} after
|
||||
* processing a DTO stops the iteration through the query results. Sharing one
|
||||
* {@link DtoMapContext} across the whole callback so that repeated references to the same
|
||||
* source entity still de-duplicate to the same DTO instance.
|
||||
*
|
||||
* @param consumer the consumer used to process the mapped DTOs, returning {@code false} to
|
||||
* stop processing.
|
||||
*/
|
||||
Optional<D> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*/
|
||||
MappedQuery<D> usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Use the explicit transaction to execute the query.
|
||||
*/
|
||||
MappedQuery<D> usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
MappedQuery<D> usingConnection(Connection connection);
|
||||
@Override
|
||||
void findEachWhile(Predicate<D> consumer);
|
||||
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ import org.jspecify.annotations.Nullable;
|
||||
* @param <T> the type of Entity bean this query will fetch.
|
||||
*/
|
||||
@NullMarked
|
||||
public interface Query<T> extends CancelableQuery, QueryBuilder<Query<T>, T> {
|
||||
public interface Query<T> extends QueryBuilder<Query<T>, T> {
|
||||
|
||||
/**
|
||||
* The lock type (strength) to use with query FOR UPDATE row locking.
|
||||
|
||||
@@ -2,8 +2,7 @@ package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -11,8 +10,6 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Build and execute an ORM query.
|
||||
@@ -20,7 +17,7 @@ import java.util.stream.Stream;
|
||||
* @param <SELF> The type of the builder
|
||||
* @param <T> The entity bean type
|
||||
*/
|
||||
public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends QueryBuilderProjection<SELF, T> {
|
||||
public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends QueryBuilderProjection<SELF, T>, StreamableQuery<SELF, T> {
|
||||
|
||||
/**
|
||||
* Set root table alias.
|
||||
@@ -146,48 +143,16 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
*/
|
||||
SELF copy();
|
||||
|
||||
/**
|
||||
* Execute the query using the given transaction.
|
||||
*/
|
||||
SELF usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute this query using immutable bean cache values for matching bean types.
|
||||
*/
|
||||
SELF using(ImmutableBeanCache<?> beanCache);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
SELF usingConnection(Connection connection);
|
||||
|
||||
/**
|
||||
* Execute the query using the given database.
|
||||
*/
|
||||
SELF usingDatabase(Database database);
|
||||
|
||||
/**
|
||||
* Ensure that the master DataSource is used if there is a read only data source
|
||||
* being used (that is using a read replica database potentially with replication lag).
|
||||
* <p>
|
||||
* When the database is configured with a read-only DataSource via
|
||||
* say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then
|
||||
* by default when a query is run without an active transaction, it uses the read-only data
|
||||
* source. We we use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
default SELF usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
SELF usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Set the base table to use for this query.
|
||||
* <p>
|
||||
@@ -679,88 +644,24 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
boolean exists();
|
||||
|
||||
/**
|
||||
* Execute the query returning either a single bean or null (if no matching
|
||||
* bean is found).
|
||||
* <p>
|
||||
* If more than 1 row is found for this query then a PersistenceException is
|
||||
* thrown.
|
||||
* <p>
|
||||
* This is useful when your predicates dictate that your query should only
|
||||
* return 0 or 1 results.
|
||||
* Execute the query returning a single bean or throwing a {@link jakarta.persistence.EntityNotFoundException}
|
||||
* if there is no matching bean.
|
||||
* <p>
|
||||
* This is a convenience alternative to:
|
||||
* <pre>{@code
|
||||
*
|
||||
* // assuming the sku of products is unique...
|
||||
* Product product =
|
||||
* new QProduct()
|
||||
* .sku.equalTo("aa113")
|
||||
* .findOne();
|
||||
* ...
|
||||
* query.findOneOrEmpty()
|
||||
* .orElseThrow(() -> new EntityNotFoundException(...));
|
||||
* }</pre>
|
||||
* <p>
|
||||
* It is also useful with finding objects by their id when you want to specify
|
||||
* further join information to optimise the query.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Fetch order 42 and additionally fetch join its order details...
|
||||
* Order order =
|
||||
* new QOrder()
|
||||
* .fetch("details") // eagerly load the order details
|
||||
* .id.equalTo(42)
|
||||
* .findOne();
|
||||
*
|
||||
* // the order details were eagerly loaded
|
||||
* List<OrderDetail> details = order.getDetails();
|
||||
* ...
|
||||
* }</pre>
|
||||
* The exception message is a best effort - it uses the id when this is effectively a
|
||||
* find-by-id query, or the single equality predicate when the query is filtered by what
|
||||
* looks like a natural/unique key, otherwise a generic "not found" message.
|
||||
*/
|
||||
@Nullable
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional bean.
|
||||
*/
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Execute the query returning the list of objects.
|
||||
* <p>
|
||||
* This query will execute against the EbeanServer that was used to create it.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* List<Customer> customers =
|
||||
* new QCustomer()
|
||||
* .name.ilike("rob%")
|
||||
* .findList();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @see Query#findList()
|
||||
*/
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
* Execute the query returning the result as a Stream.
|
||||
* <p>
|
||||
* Note that this can support very large queries iterating
|
||||
* any number of results. To do so internally it can use
|
||||
* multiple persistence contexts.
|
||||
* </p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // use try with resources to ensure Stream is closed
|
||||
*
|
||||
* try (Stream<Customer> stream = query.findStream()) {
|
||||
* stream
|
||||
* .map(...)
|
||||
* .collect(...);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Stream<T> findStream();
|
||||
@Override
|
||||
default T findOneOrThrow() {
|
||||
return findOneOrEmpty().orElseThrow(() ->
|
||||
new EntityNotFoundException(getBeanType().getSimpleName() + " not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the query returning the set of objects.
|
||||
@@ -905,84 +806,6 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
*/
|
||||
<A> Set<A> findSingleAttributeSet();
|
||||
|
||||
/**
|
||||
* Execute the query processing the beans one at a time.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the
|
||||
* beans are consumed one at a time and do not need to be held in memory
|
||||
* (unlike #findList #findSet etc)
|
||||
* <p>
|
||||
* Note that internally Ebean can inform the JDBC driver that it is expecting larger
|
||||
* resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
|
||||
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
|
||||
* generally preferable.
|
||||
* <p>
|
||||
* Compared with #findEachWhile this will always process all the beans where as
|
||||
* #findEachWhile provides a way to stop processing the query result early before
|
||||
* all the beans have been read.
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the Consumer interface which is better suited to use with closures.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* new QCustomer()
|
||||
* .status.equalTo(Status.NEW)
|
||||
* .orderBy().id.asc()
|
||||
* .findEach((Customer customer) -> {
|
||||
*
|
||||
* // do something with customer
|
||||
* System.out.println("-- visit " + customer);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer the consumer used to process the queried beans.
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute findEach streaming query batching the results for consuming.
|
||||
* <p>
|
||||
* This query execution will stream the results and is suited to consuming
|
||||
* large numbers of results from the database.
|
||||
* <p>
|
||||
* Typically, we use this batch consumer when we want to do further processing on
|
||||
* the beans and want to do that processing in batch form, for example - 100 at
|
||||
* a time.
|
||||
*
|
||||
* @param batch The number of beans processed in the batch
|
||||
* @param consumer Process the batch of beans
|
||||
*/
|
||||
void findEach(int batch, Consumer<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query using callbacks to a visitor to process the resulting
|
||||
* beans one at a time.
|
||||
* <p>
|
||||
* This method is functionally equivalent to findIterate() but instead of using an
|
||||
* iterator uses the Predicate interface which is better suited to use with closures.
|
||||
*
|
||||
* <pre>{@code
|
||||
*
|
||||
* new QCustomer()
|
||||
* .status.equalTo(Status.NEW)
|
||||
* .orderBy().id.asc()
|
||||
* .findEachWhile((Customer customer) -> {
|
||||
*
|
||||
* // do something with customer
|
||||
* System.out.println("-- visit " + customer);
|
||||
*
|
||||
* // return true to continue processing or false to stop
|
||||
* return (customer.getId() < 40);
|
||||
* });
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @param consumer the consumer used to process the queried beans.
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
/**
|
||||
* Return versions of a @History entity bean.
|
||||
* <p>
|
||||
@@ -1048,33 +871,4 @@ public interface QueryBuilder<SELF extends QueryBuilder<SELF, T>, T> extends Que
|
||||
*/
|
||||
<K> FutureMap<K,T> findFutureMap();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
* <p>
|
||||
* <pre>{@code
|
||||
*
|
||||
* PagedList<Order> pagedList =
|
||||
* new QOrder()
|
||||
* .setFirstRow(50)
|
||||
* .setMaxRows(20)
|
||||
* .findPagedList();
|
||||
*
|
||||
* // fetch the total row count in the background
|
||||
* pagedList.loadRowCount();
|
||||
*
|
||||
* List<Order> orders = pagedList.getList();
|
||||
* int totalRowCount = pagedList.getTotalRowCount();
|
||||
*
|
||||
* }</pre>
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package io.ebean;
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.io.Serializable;
|
||||
import java.sql.Connection;
|
||||
import java.util.Collection;
|
||||
@@ -41,44 +40,7 @@ import java.util.function.Predicate;
|
||||
* }</pre>
|
||||
*/
|
||||
@NullMarked
|
||||
public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
|
||||
/**
|
||||
* Execute the query using the given transaction.
|
||||
*/
|
||||
SqlQuery usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
SqlQuery usingConnection(Connection connection);
|
||||
|
||||
/**
|
||||
* Ensure that the master DataSource is used if there is a read only data source
|
||||
* being used (that is using a read replica database potentially with replication lag).
|
||||
* <p>
|
||||
* When the database is configured with a read-only DataSource via
|
||||
* say {@link io.ebean.DatabaseBuilder#readOnlyDataSource(DataSource)}then
|
||||
* by default when a query is run without an active transaction, it uses the read-only data
|
||||
* source. We use {@code usingMaster()} to instead ensure that the query is executed
|
||||
* against the master data source.
|
||||
*/
|
||||
default SqlQuery usingMaster() {
|
||||
return usingMaster(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
* data source can be used if defined.
|
||||
*
|
||||
* @see #usingMaster()
|
||||
*/
|
||||
SqlQuery usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Execute the query returning a list.
|
||||
*/
|
||||
List<SqlRow> findList();
|
||||
public interface SqlQuery extends Serializable, FindableQuery<SqlQuery, SqlRow> {
|
||||
|
||||
/**
|
||||
* Execute the SqlQuery iterating a row at a time.
|
||||
@@ -99,16 +61,6 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
*/
|
||||
void findEachWhile(Predicate<SqlRow> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning a single row or null.
|
||||
* <p>
|
||||
* If this query finds 2 or more rows then it will throw a
|
||||
* PersistenceException.
|
||||
* </p>
|
||||
*/
|
||||
@Nullable
|
||||
SqlRow findOne();
|
||||
|
||||
/**
|
||||
* Execute the query reading each row from ResultSet using the RowConsumer.
|
||||
* <p>
|
||||
@@ -139,11 +91,6 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
*/
|
||||
void findEachRow(RowConsumer consumer);
|
||||
|
||||
/**
|
||||
* Execute the query returning an optional row.
|
||||
*/
|
||||
Optional<SqlRow> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Set one of more positioned parameters.
|
||||
* <p>
|
||||
@@ -365,7 +312,7 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
*
|
||||
* @param <T> The type of the scalar values
|
||||
*/
|
||||
interface TypeQuery<T> {
|
||||
interface TypeQuery<T> extends FindableQuery<TypeQuery<T>, T> {
|
||||
|
||||
/**
|
||||
* Ensure the master DataSource is used when useMaster is true. Otherwise, the read only
|
||||
@@ -373,27 +320,38 @@ public interface SqlQuery extends Serializable, CancelableQuery {
|
||||
*
|
||||
* @see SqlQuery#usingMaster(boolean)
|
||||
*/
|
||||
@Override
|
||||
TypeQuery<T> usingMaster(boolean useMaster);
|
||||
|
||||
/**
|
||||
* Execute the query using the given transaction.
|
||||
*/
|
||||
@Override
|
||||
TypeQuery<T> usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute the query using the given connection.
|
||||
*/
|
||||
@Override
|
||||
TypeQuery<T> usingConnection(Connection connection);
|
||||
|
||||
/**
|
||||
* Return the single value.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
T findOne();
|
||||
|
||||
/**
|
||||
* Return the single value that is optional.
|
||||
*/
|
||||
@Override
|
||||
Optional<T> findOneOrEmpty();
|
||||
|
||||
/**
|
||||
* Return the list of values.
|
||||
*/
|
||||
@Override
|
||||
List<T> findList();
|
||||
|
||||
/**
|
||||
|
||||
@@ -158,6 +158,15 @@ public interface SqlUpdate {
|
||||
*/
|
||||
int executeNow();
|
||||
|
||||
/**
|
||||
* Set an explicit transaction to use to execute this statement.
|
||||
* <p>
|
||||
* When not set, {@link #execute()} and {@link #executeNow()} use whatever transaction
|
||||
* is currently active on the thread (or auto-commit if none is active) - consistent
|
||||
* with {@link Database#execute(SqlUpdate, Transaction)}.
|
||||
*/
|
||||
SqlUpdate usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Execute when addBatch() has been used to batch multiple bind executions.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
package io.ebean;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A {@link FindableQuery} that additionally supports streaming results and paging.
|
||||
*
|
||||
* @param <SELF> The query type (used for method chaining)
|
||||
* @param <T> The type of the result
|
||||
*/
|
||||
@NullMarked
|
||||
public interface StreamableQuery<SELF extends StreamableQuery<SELF, T>, T> extends FindableQuery<SELF, T> {
|
||||
|
||||
/**
|
||||
* Execute the query returning the result as a Stream.
|
||||
* <p>
|
||||
* Note that this can support very large queries iterating any number of results.
|
||||
* To do so internally it can use multiple persistence contexts.
|
||||
* <p>
|
||||
* Note that the Stream holds resources related to the underlying resultSet and
|
||||
* potentially connection and MUST be closed. We should use the Stream in a
|
||||
* <em>try with resource block</em>.
|
||||
* <pre>{@code
|
||||
*
|
||||
* // use try with resources to ensure Stream is closed
|
||||
*
|
||||
* try (Stream<T> stream = query.findStream()) {
|
||||
* stream
|
||||
* .map(...)
|
||||
* .collect(...);
|
||||
* }
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
Stream<T> findStream();
|
||||
|
||||
/**
|
||||
* Return a PagedList for this query using firstRow and maxRows.
|
||||
* <p>
|
||||
* The benefit of using this over findList() is that it provides functionality to get the
|
||||
* total row count etc.
|
||||
* <p>
|
||||
* If maxRows is not set on the query prior to calling findPagedList() then a
|
||||
* PersistenceException is thrown.
|
||||
*
|
||||
* @return The PagedList
|
||||
*/
|
||||
PagedList<T> findPagedList();
|
||||
|
||||
/**
|
||||
* Execute the query processing the results one at a time.
|
||||
* <p>
|
||||
* This method is appropriate to process very large query results as the results are
|
||||
* consumed one at a time and do not need to be held in memory (unlike {@link #findList()}).
|
||||
* <p>
|
||||
* Note that internally Ebean can inform the JDBC driver that it is expecting a larger
|
||||
* resultSet and specifically for MySQL this hint is required to stop its JDBC driver
|
||||
* from buffering the entire resultSet. As such, for smaller resultSets findList() is
|
||||
* generally preferable.
|
||||
* <p>
|
||||
* Compared with {@link #findEachWhile(Predicate)} this will always process all the results
|
||||
* whereas findEachWhile() provides a way to stop processing the query result early before
|
||||
* all the results have been read.
|
||||
*
|
||||
* @param consumer the consumer used to process the queried results.
|
||||
*/
|
||||
void findEach(Consumer<T> consumer);
|
||||
|
||||
/**
|
||||
* Execute findEach streaming query batching the results for consuming.
|
||||
* <p>
|
||||
* This query execution will stream the results and is suited to consuming
|
||||
* large numbers of results from the database.
|
||||
* <p>
|
||||
* Typically, we use this batch consumer when we want to do further processing on
|
||||
* the results and want to do that processing in batch form, for example - 100 at
|
||||
* a time.
|
||||
*
|
||||
* @param batch The number of results processed in the batch
|
||||
* @param consumer Process the batch of results
|
||||
*/
|
||||
void findEach(int batch, Consumer<List<T>> consumer);
|
||||
|
||||
/**
|
||||
* Execute the query using callbacks to process the resulting results one at a time,
|
||||
* with the ability to stop processing part way through.
|
||||
* <p>
|
||||
* Returning {@code false} after processing a result stops the iteration through the
|
||||
* query results.
|
||||
*
|
||||
* @param consumer the consumer used to process the queried results, returning
|
||||
* {@code false} to stop processing.
|
||||
*/
|
||||
void findEachWhile(Predicate<T> consumer);
|
||||
|
||||
}
|
||||
@@ -84,6 +84,15 @@ public interface Update<T> {
|
||||
*/
|
||||
int execute();
|
||||
|
||||
/**
|
||||
* Set an explicit transaction to use to execute this statement.
|
||||
* <p>
|
||||
* When not set, {@link #execute()} uses whatever transaction is currently active on
|
||||
* the thread (or auto-commit if none is active) - consistent with
|
||||
* {@link Database#execute(Update, Transaction)}.
|
||||
*/
|
||||
Update<T> usingTransaction(Transaction transaction);
|
||||
|
||||
/**
|
||||
* Set an ordered bind parameter.
|
||||
* <p>
|
||||
|
||||
@@ -5,13 +5,18 @@ package io.ebean.meta;
|
||||
*/
|
||||
public abstract class AbstractMetricVisitor implements MetricVisitor {
|
||||
|
||||
private final boolean reset;
|
||||
private final Mode mode;
|
||||
private final boolean collectTransactionMetrics;
|
||||
private final boolean collectQueryMetrics;
|
||||
private final boolean collectL2Metrics;
|
||||
|
||||
public AbstractMetricVisitor(boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
|
||||
this.reset = reset;
|
||||
this(reset ? Mode.RESET : Mode.CUMULATIVE,
|
||||
collectTransactionMetrics, collectQueryMetrics, collectL2Metrics);
|
||||
}
|
||||
|
||||
public AbstractMetricVisitor(Mode mode, boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
|
||||
this.mode = mode;
|
||||
this.collectTransactionMetrics = collectTransactionMetrics;
|
||||
this.collectQueryMetrics = collectQueryMetrics;
|
||||
this.collectL2Metrics = collectL2Metrics;
|
||||
@@ -19,7 +24,12 @@ public abstract class AbstractMetricVisitor implements MetricVisitor {
|
||||
|
||||
@Override
|
||||
public boolean reset() {
|
||||
return reset;
|
||||
return mode == Mode.RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mode mode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,4 +57,3 @@ public abstract class AbstractMetricVisitor implements MetricVisitor {
|
||||
// do nothing by default
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,16 @@ public class BasicMetricVisitor extends AbstractMetricVisitor implements ServerM
|
||||
* Construct specifying reset and what to collect.
|
||||
*/
|
||||
public BasicMetricVisitor(String name, Function<String,String> naming, boolean reset, boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
|
||||
super(reset, collectTransactionMetrics, collectQueryMetrics, collectL2Metrics);
|
||||
this(name, naming, reset ? Mode.RESET : Mode.CUMULATIVE,
|
||||
collectTransactionMetrics, collectQueryMetrics, collectL2Metrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct specifying the collection mode and what to collect.
|
||||
*/
|
||||
public BasicMetricVisitor(String name, Function<String,String> naming, Mode mode,
|
||||
boolean collectTransactionMetrics, boolean collectQueryMetrics, boolean collectL2Metrics) {
|
||||
super(mode, collectTransactionMetrics, collectQueryMetrics, collectL2Metrics);
|
||||
this.name = name;
|
||||
this.naming = naming;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ import java.time.Instant;
|
||||
*/
|
||||
public interface MetaQueryPlan {
|
||||
|
||||
/**
|
||||
* Return the name of the database for the query.
|
||||
*/
|
||||
String dbName();
|
||||
|
||||
/**
|
||||
* Return the bean type for the query.
|
||||
*/
|
||||
|
||||
@@ -7,6 +7,12 @@ import java.util.function.Function;
|
||||
*/
|
||||
public interface MetricVisitor {
|
||||
|
||||
enum Mode {
|
||||
RESET,
|
||||
CUMULATIVE,
|
||||
DELTA
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the naming convention that should be applied to the reported metric names.
|
||||
*/
|
||||
@@ -17,6 +23,13 @@ public interface MetricVisitor {
|
||||
*/
|
||||
boolean reset();
|
||||
|
||||
/**
|
||||
* Return the metric collection mode.
|
||||
*/
|
||||
default Mode mode() {
|
||||
return reset() ? Mode.RESET : Mode.CUMULATIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if we should visit the transaction metrics.
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,16 @@ public interface TimedMetric {
|
||||
*/
|
||||
TimedMetricStats collect(boolean reset);
|
||||
|
||||
/**
|
||||
* Collect a snapshot using the given collection mode.
|
||||
*
|
||||
* <p>Implementations that do not support delta collection use cumulative
|
||||
* collection for {@link MetricVisitor.Mode#DELTA}.</p>
|
||||
*/
|
||||
default TimedMetricStats collect(MetricVisitor.Mode mode) {
|
||||
return collect(mode == MetricVisitor.Mode.RESET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit non empty metrics.
|
||||
*/
|
||||
|
||||
@@ -43,4 +43,18 @@ class MetaInfoManagerTest {
|
||||
|
||||
assertThat(manager.collectMetrics(false)).isSameAs(metrics);
|
||||
}
|
||||
|
||||
@Test
|
||||
void basicMetricVisitorSupportsExplicitCollectionModes() {
|
||||
var reset = new BasicMetricVisitor("db", MetricNamingMatch.INSTANCE, MetricVisitor.Mode.RESET, true, true, true);
|
||||
var cumulative = new BasicMetricVisitor("db", MetricNamingMatch.INSTANCE, MetricVisitor.Mode.CUMULATIVE, true, true, true);
|
||||
var delta = new BasicMetricVisitor("db", MetricNamingMatch.INSTANCE, MetricVisitor.Mode.DELTA, true, true, true);
|
||||
|
||||
assertThat(reset.reset()).isTrue();
|
||||
assertThat(reset.mode()).isEqualTo(MetricVisitor.Mode.RESET);
|
||||
assertThat(cumulative.reset()).isFalse();
|
||||
assertThat(cumulative.mode()).isEqualTo(MetricVisitor.Mode.CUMULATIVE);
|
||||
assertThat(delta.reset()).isFalse();
|
||||
assertThat(delta.mode()).isEqualTo(MetricVisitor.Mode.DELTA);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ebean-avajejsonb-mapper</artifactId>
|
||||
<name>ebean-avajejsonb-mapper</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-core</artifactId>
|
||||
<version>${avaje-json-core.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-jsonb</artifactId>
|
||||
<version>${avaje-jsonb.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-json-node</artifactId>
|
||||
<version>${avaje-jsonb.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-datasource</artifactId>
|
||||
<version>${ebean-datasource.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<version>${h2database.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>io.avaje</groupId>
|
||||
<artifactId>avaje-jsonb-generator</artifactId>
|
||||
<version>${avaje-jsonb.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-maven-plugin</artifactId>
|
||||
<version>${ebean-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>test</id>
|
||||
<phase>process-test-classes</phase>
|
||||
<configuration>
|
||||
<packages>org/example/avajejsonb/**</packages>
|
||||
<transformArgs>debug=0</transformArgs>
|
||||
</configuration>
|
||||
<goals>
|
||||
<goal>testEnhance</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package io.ebean.avajejsonb.mapper;
|
||||
|
||||
import io.avaje.json.JsonReader;
|
||||
import io.avaje.json.JsonWriter;
|
||||
import io.avaje.jsonb.JsonType;
|
||||
import io.avaje.jsonb.Jsonb;
|
||||
import io.ebean.annotation.MutationDetection;
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.core.type.JsonTrim;
|
||||
import io.ebean.core.type.PostgresHelper;
|
||||
import io.ebean.core.type.ScalarJsonManager;
|
||||
import io.ebean.core.type.ScalarJsonMapper;
|
||||
import io.ebean.core.type.ScalarJsonRequest;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import io.ebean.core.type.ScalarTypeBase;
|
||||
import io.ebean.text.TextException;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Type;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Supports {@code @DbJson} properties using Avaje Jsonb.
|
||||
*/
|
||||
public final class ScalarJsonAvajeJsonbMapper implements ScalarJsonMapper {
|
||||
|
||||
private final ConcurrentHashMap<Type, JsonType<Object>> jsonTypes = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public <A extends Annotation> Class<A> markerAnnotation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScalarType<?> createType(ScalarJsonRequest request) {
|
||||
Type genericType = genericType(request);
|
||||
JsonType<Object> jsonType = jsonTypes.computeIfAbsent(genericType, type -> jsonb(request.manager()).type(type));
|
||||
if (request.mode() == MutationDetection.NONE) {
|
||||
return new NoMutationDetection(request.manager(), jsonType, request.dbType(), request.docType());
|
||||
}
|
||||
return new GenericObject(request.manager(), jsonType, request.dbType(), request.docType());
|
||||
}
|
||||
|
||||
private Jsonb jsonb(ScalarJsonManager manager) {
|
||||
Object mapper = manager.mapper();
|
||||
return mapper instanceof Jsonb ? (Jsonb) mapper : Jsonb.instance();
|
||||
}
|
||||
|
||||
private Type genericType(ScalarJsonRequest request) {
|
||||
Class<?> type = request.beanType();
|
||||
while (type != null) {
|
||||
try {
|
||||
Field field = type.getDeclaredField(request.name());
|
||||
return field.getGenericType();
|
||||
} catch (NoSuchFieldException e) {
|
||||
type = type.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Field not found to match " + request.name());
|
||||
}
|
||||
|
||||
private static final class NoMutationDetection extends Base<Object> {
|
||||
|
||||
NoMutationDetection(ScalarJsonManager jsonManager, JsonType<Object> jsonType, int dbType, DocPropertyType docType) {
|
||||
super(Object.class, jsonManager, jsonType, dbType, docType);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GenericObject extends Base<Object> {
|
||||
|
||||
private final boolean jsonb;
|
||||
|
||||
GenericObject(ScalarJsonManager jsonManager, JsonType<Object> jsonType, int dbType, DocPropertyType docType) {
|
||||
super(Object.class, jsonManager, jsonType, dbType, docType);
|
||||
this.jsonb = "jsonb".equals(pgType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mutable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean jsonMapper() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(DataReader reader) throws SQLException {
|
||||
String json = reader.getString();
|
||||
if (jsonb) {
|
||||
json = JsonTrim.trim(json);
|
||||
}
|
||||
reader.pushJson(json);
|
||||
return parseJson(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBinder binder, Object value) throws SQLException {
|
||||
String rawJson = binder.popJson();
|
||||
if (rawJson == null && value != null) {
|
||||
rawJson = formatValue(value);
|
||||
}
|
||||
bindJson(binder, value, rawJson);
|
||||
}
|
||||
}
|
||||
|
||||
private static abstract class Base<T> extends ScalarTypeBase<T> {
|
||||
|
||||
private final JsonType<T> jsonType;
|
||||
protected final String pgType;
|
||||
private final DocPropertyType docType;
|
||||
|
||||
Base(Class<T> cls, ScalarJsonManager jsonManager, JsonType<T> jsonType, int dbType, DocPropertyType docType) {
|
||||
super(cls, false, dbType);
|
||||
this.jsonType = jsonType;
|
||||
this.pgType = jsonManager.postgresType(dbType);
|
||||
this.docType = docType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T read(DataReader reader) throws SQLException {
|
||||
return parseJson(reader.getString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(DataBinder binder, T value) throws SQLException {
|
||||
bindJson(binder, value, value == null ? null : formatValue(value));
|
||||
}
|
||||
|
||||
final T parseJson(String json) {
|
||||
if (json == null || json.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return jsonType.fromJson(json);
|
||||
} catch (RuntimeException e) {
|
||||
throw new TextException("Failed to parse JSON [{}] as " + jsonType, json, e);
|
||||
}
|
||||
}
|
||||
|
||||
final void bindJson(DataBinder binder, Object value, String rawJson) throws SQLException {
|
||||
if (pgType != null) {
|
||||
binder.setObject(PostgresHelper.asObject(pgType, rawJson));
|
||||
} else if (value == null) {
|
||||
binder.setNull(Types.VARCHAR);
|
||||
} else {
|
||||
binder.setString(rawJson);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Object toJdbcType(Object value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public final T toBeanType(Object value) {
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String formatValue(T value) {
|
||||
try {
|
||||
return jsonType.toJson(value);
|
||||
} catch (RuntimeException e) {
|
||||
throw new PersistenceException("Unable to create JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T parse(String value) {
|
||||
return parseJson(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final DocPropertyType docType() {
|
||||
return docType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T jsonRead(JsonReader parser) {
|
||||
if (parser.isNullValue()) {
|
||||
return null;
|
||||
}
|
||||
return parseJson(parser.readRaw());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void jsonWrite(JsonWriter writer, T value) throws IOException {
|
||||
if (value == null) {
|
||||
writer.nullValue();
|
||||
} else {
|
||||
writer.rawValue(formatValue(value));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T readData(DataInput dataInput) throws IOException {
|
||||
return dataInput.readBoolean() ? parse(dataInput.readUTF()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void writeData(DataOutput dataOutput, T value) throws IOException {
|
||||
if (value == null) {
|
||||
dataOutput.writeBoolean(false);
|
||||
} else {
|
||||
dataOutput.writeBoolean(true);
|
||||
dataOutput.writeUTF(format(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import io.ebean.avajejsonb.mapper.ScalarJsonAvajeJsonbMapper;
|
||||
|
||||
module io.ebean.avajejsonb.mapper {
|
||||
|
||||
requires io.avaje.jsonb;
|
||||
requires io.ebean.core.type;
|
||||
|
||||
provides io.ebean.core.type.ScalarJsonMapper with ScalarJsonAvajeJsonbMapper;
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
io.ebean.avajejsonb.mapper.ScalarJsonAvajeJsonbMapper
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package io.ebean.avajejsonb.mapper;
|
||||
|
||||
import io.avaje.json.node.JsonNode;
|
||||
import io.avaje.json.node.JsonObject;
|
||||
import io.ebean.Database;
|
||||
import io.ebean.DatabaseBuilder;
|
||||
import org.example.avajejsonb.JsonbEntity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class JsonbDatabaseTest {
|
||||
|
||||
@Test
|
||||
void dbJson_roundTripsJsonbPayloadsAndJsonNode() {
|
||||
Database database = buildDatabase();
|
||||
try {
|
||||
JsonbEntity entity = new JsonbEntity();
|
||||
entity.setPayload(new JsonbPayload("main", 1));
|
||||
entity.setPayloads(List.of(new JsonbPayload("first", 2), new JsonbPayload("second", 3)));
|
||||
entity.setNode(JsonObject.create().add("name", "node").add("count", 4));
|
||||
database.save(entity);
|
||||
|
||||
JsonbEntity found = database.find(JsonbEntity.class, entity.getId());
|
||||
|
||||
assertThat(found.getPayload()).isEqualTo(new JsonbPayload("main", 1));
|
||||
assertThat(found.getPayloads()).containsExactly(new JsonbPayload("first", 2), new JsonbPayload("second", 3));
|
||||
JsonNode node = found.getNode();
|
||||
assertThat(node.extract("name")).isEqualTo("node");
|
||||
assertThat(node.extract("count", 0)).isEqualTo(4);
|
||||
} finally {
|
||||
database.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static Database buildDatabase() {
|
||||
DatabaseBuilder config = Database.builder();
|
||||
config.setName("avajeJsonbMapper");
|
||||
config.setDefaultServer(false);
|
||||
config.setDdlGenerate(true);
|
||||
config.setDdlRun(true);
|
||||
config.setDdlExtra(false);
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.setProperty("datasource.avajeJsonbMapper.username", "sa");
|
||||
properties.setProperty("datasource.avajeJsonbMapper.password", "");
|
||||
properties.setProperty("datasource.avajeJsonbMapper.databaseUrl", "jdbc:h2:mem:avajeJsonbMapper");
|
||||
properties.setProperty("datasource.avajeJsonbMapper.databaseDriver", "org.h2.Driver");
|
||||
config.loadFromProperties(properties);
|
||||
config.addClass(JsonbEntity.class);
|
||||
return config.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package io.ebean.avajejsonb.mapper;
|
||||
|
||||
import io.avaje.jsonb.Json;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Json
|
||||
public class JsonbPayload {
|
||||
|
||||
public String name;
|
||||
public int count;
|
||||
|
||||
public JsonbPayload() {
|
||||
}
|
||||
|
||||
JsonbPayload(String name, int count) {
|
||||
this.name = name;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof JsonbPayload)) {
|
||||
return false;
|
||||
}
|
||||
JsonbPayload other = (JsonbPayload) object;
|
||||
return count == other.count && Objects.equals(name, other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, count);
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
package io.ebean.avajejsonb.mapper;
|
||||
|
||||
import io.avaje.json.node.JsonNode;
|
||||
import io.avaje.json.node.JsonObject;
|
||||
import io.avaje.jsonb.Json;
|
||||
import io.ebean.annotation.MutationDetection;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
import io.ebean.core.type.ScalarJsonManager;
|
||||
import io.ebean.core.type.ScalarJsonRequest;
|
||||
import io.ebean.core.type.ScalarType;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class ScalarJsonAvajeJsonbMapperTest {
|
||||
|
||||
private static final ScalarJsonManager JSON_MANAGER = new ScalarJsonManager() {
|
||||
|
||||
@Override
|
||||
public MutationDetection mutationDetection() {
|
||||
return MutationDetection.HASH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object mapper() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postgresType(int dbType) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private final ScalarJsonAvajeJsonbMapper mapper = new ScalarJsonAvajeJsonbMapper();
|
||||
|
||||
@Test
|
||||
void pojo_roundTripsThroughGeneratedJsonbAdapter() {
|
||||
ScalarType<Object> scalarType = scalarType("document", MutationDetection.HASH);
|
||||
Payload payload = new Payload("hello", 42);
|
||||
|
||||
String json = scalarType.formatValue(payload);
|
||||
|
||||
assertThat(json).isEqualTo("{\"name\":\"hello\",\"count\":42}");
|
||||
assertThat(scalarType.parse(json)).isEqualTo(payload);
|
||||
assertThat(scalarType.mutable()).isTrue();
|
||||
assertThat(scalarType.jsonMapper()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void genericList_roundTripsUsingPropertyGenericType() {
|
||||
ScalarType<Object> scalarType = scalarType("payloads", MutationDetection.HASH);
|
||||
List<Payload> payloads = List.of(new Payload("one", 1), new Payload("two", 2));
|
||||
|
||||
String json = scalarType.formatValue(payloads);
|
||||
|
||||
assertThat(json).isEqualTo("[{\"name\":\"one\",\"count\":1},{\"name\":\"two\",\"count\":2}]");
|
||||
assertThat(scalarType.parse(json)).isEqualTo(payloads);
|
||||
}
|
||||
|
||||
@Test
|
||||
void jsonNode_roundTripsThroughAvajeJsonNodeComponent() {
|
||||
ScalarType<Object> scalarType = scalarType("node", MutationDetection.HASH);
|
||||
JsonNode node = JsonObject.create().add("name", "node").add("count", 3);
|
||||
|
||||
String json = scalarType.formatValue(node);
|
||||
|
||||
assertThat(json).isEqualTo("{\"name\":\"node\",\"count\":3}");
|
||||
JsonNode parsed = (JsonNode) scalarType.parse(json);
|
||||
assertThat(parsed.extract("name")).isEqualTo("node");
|
||||
assertThat(parsed.extract("count", 0)).isEqualTo(3);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mutationDetectionNone_isNotMutable() {
|
||||
ScalarType<Object> scalarType = scalarType("document", MutationDetection.NONE);
|
||||
|
||||
assertThat(scalarType.mutable()).isFalse();
|
||||
assertThat(scalarType.jsonMapper()).isFalse();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private ScalarType<Object> scalarType(String property, MutationDetection mutationDetection) {
|
||||
var request = new ScalarJsonRequest(JSON_MANAGER, Types.VARCHAR, DocPropertyType.OBJECT, Entity.class, mutationDetection, property);
|
||||
return (ScalarType<Object>) mapper.createType(request);
|
||||
}
|
||||
|
||||
private static final class Entity {
|
||||
|
||||
Payload document;
|
||||
List<Payload> payloads;
|
||||
JsonNode node;
|
||||
}
|
||||
|
||||
@Json
|
||||
static class Payload {
|
||||
|
||||
public String name;
|
||||
public int count;
|
||||
|
||||
Payload() {
|
||||
}
|
||||
|
||||
Payload(String name, int count) {
|
||||
this.name = name;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof Payload)) {
|
||||
return false;
|
||||
}
|
||||
Payload other = (Payload) object;
|
||||
return count == other.count && Objects.equals(name, other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, count);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package org.example.avajejsonb;
|
||||
|
||||
import io.avaje.json.node.JsonNode;
|
||||
import io.ebean.avajejsonb.mapper.JsonbPayload;
|
||||
import io.ebean.annotation.DbJson;
|
||||
import io.ebean.annotation.DbJsonB;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Entity
|
||||
public class JsonbEntity {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
|
||||
@DbJson
|
||||
JsonbPayload payload;
|
||||
|
||||
@DbJson
|
||||
List<JsonbPayload> payloads;
|
||||
|
||||
@DbJsonB
|
||||
JsonNode node;
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public JsonbPayload getPayload() {
|
||||
return payload;
|
||||
}
|
||||
|
||||
public void setPayload(JsonbPayload payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
public List<JsonbPayload> getPayloads() {
|
||||
return payloads;
|
||||
}
|
||||
|
||||
public void setPayloads(List<JsonbPayload> payloads) {
|
||||
this.payloads = payloads;
|
||||
}
|
||||
|
||||
public JsonNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
public void setNode(JsonNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
entity-packages: org.example.avajejsonb
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-bench</artifactId>
|
||||
|
||||
+34
-28
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean bom</name>
|
||||
@@ -89,25 +89,25 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -125,13 +125,19 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-jackson-mapper</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-avajejsonb-mapper</artifactId>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -155,37 +161,37 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>kotlin-querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-spring-txn</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- platforms -->
|
||||
@@ -193,91 +199,91 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-clickhouse</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-db2</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-h2</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-hana</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mariadb</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-mysql</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-nuodb</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-oracle</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-postgis-types</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-pgvector-types</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlite</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-sqlserver</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<name>ebean-core-json</name>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
@@ -16,7 +16,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -29,7 +29,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.11</version>
|
||||
<version>42.7.12</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
|
||||
+10
-5
@@ -1,14 +1,19 @@
|
||||
package io.ebean.jackson.mapper;
|
||||
package io.ebean.core.type;
|
||||
|
||||
/**
|
||||
* Helper that removes whitespace from JSON. Used to normalise Postgres JSONB content.
|
||||
* Helper that removes whitespace from JSON content.
|
||||
* <p>
|
||||
* Used to normalise PostgreSQL JSONB content before it is retained for mutation detection.
|
||||
*/
|
||||
final class JsonTrim {
|
||||
public final class JsonTrim {
|
||||
|
||||
private JsonTrim() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return JSON with whitespace trimmed.
|
||||
*/
|
||||
static String trim(String json) {
|
||||
public static String trim(String json) {
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -18,7 +23,7 @@ final class JsonTrim {
|
||||
boolean quoted = false;
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '\"') {
|
||||
if (c == '"') {
|
||||
if (!escaped) {
|
||||
quoted = !quoted;
|
||||
} else {
|
||||
+7
-7
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-core</artifactId>
|
||||
@@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-json</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -52,7 +52,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
@@ -157,21 +157,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-h2</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-sqlserver</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -334,6 +334,12 @@ public interface SpiEbeanServer extends SpiServer, BeanCollectionLoader {
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate);
|
||||
|
||||
/**
|
||||
* Execute the sql update regardless of transaction batch mode using the given
|
||||
* explicit transaction (or the current ambient transaction when null).
|
||||
*/
|
||||
int executeNow(SpiSqlUpdate sqlUpdate, @Nullable Transaction transaction);
|
||||
|
||||
/**
|
||||
* Create a query bind capture for the given query plan.
|
||||
*/
|
||||
|
||||
@@ -1938,7 +1938,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
|
||||
|
||||
@Override
|
||||
public int executeNow(SpiSqlUpdate sqlUpdate) {
|
||||
return persister.executeSqlUpdateNow(sqlUpdate, null);
|
||||
return executeNow(sqlUpdate, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int executeNow(SpiSqlUpdate sqlUpdate, @Nullable Transaction transaction) {
|
||||
return persister.executeSqlUpdateNow(sqlUpdate, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.core;
|
||||
|
||||
import io.ebean.DB;
|
||||
import io.ebean.SqlUpdate;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.Update;
|
||||
import io.ebeaninternal.api.BindParams;
|
||||
import io.ebeaninternal.api.SpiEbeanServer;
|
||||
@@ -140,7 +141,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
server.executeBatch(this, transaction);
|
||||
return -1;
|
||||
}
|
||||
return server.execute(this);
|
||||
return server.execute(this, transaction);
|
||||
} else {
|
||||
// Hopefully this doesn't catch anyone out...
|
||||
return DB.getDefault().execute(this);
|
||||
@@ -150,12 +151,18 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
|
||||
@Override
|
||||
public int executeNow() {
|
||||
if (server != null) {
|
||||
return server.executeNow(this);
|
||||
return server.executeNow(this, transaction);
|
||||
} else {
|
||||
throw new IllegalStateException("server is null?");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlUpdate usingTransaction(Transaction transaction) {
|
||||
this.transaction = (SpiTransaction) transaction;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] executeBatch() {
|
||||
if (server == null) {
|
||||
|
||||
@@ -80,6 +80,10 @@ public final class DtoMappingRequest {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String dbName() {
|
||||
return server.name();
|
||||
}
|
||||
|
||||
public String hash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ abstract class DtoQueryPlanBase implements DtoQueryPlan, SpiQueryPlan {
|
||||
private final QueryPlanMetric planMetric;
|
||||
private final TimedMetric metric;
|
||||
private final Class<?> beanType;
|
||||
private final String dbName;
|
||||
private final String name;
|
||||
private final String hash;
|
||||
private final String sql;
|
||||
@@ -26,6 +27,7 @@ abstract class DtoQueryPlanBase implements DtoQueryPlan, SpiQueryPlan {
|
||||
this.planMetric = request.createMetric();
|
||||
this.metric = planMetric.metric();
|
||||
this.beanType = request.type();
|
||||
this.dbName = request.dbName();
|
||||
this.name = request.name();
|
||||
this.hash = request.hash();
|
||||
this.sql = request.sql();
|
||||
@@ -91,6 +93,6 @@ abstract class DtoQueryPlanBase implements DtoQueryPlan, SpiQueryPlan {
|
||||
|
||||
@Override
|
||||
public SpiDbQueryPlan createMeta(String bind, String planString) {
|
||||
return new DQueryPlanOutput(beanType, name, hash, sql, profileLocation, bind, planString);
|
||||
return new DQueryPlanOutput(beanType, dbName, name, hash, sql, profileLocation, bind, planString);
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -1335,6 +1335,24 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a "propertyName: value" description when this expression list is a single
|
||||
* simple equality predicate (a candidate natural/unique key), otherwise null.
|
||||
* <p>
|
||||
* Used to build a decent default message for {@code findOneOrThrow()} when the query
|
||||
* isn't a simple find-by-id.
|
||||
*/
|
||||
@Nullable
|
||||
public String singleEqDescription() {
|
||||
if (list.size() == 1 && list.get(0) instanceof SimpleExpression) {
|
||||
SimpleExpression simple = (SimpleExpression) list.get(0);
|
||||
if (simple.isOpEquals()) {
|
||||
return simple.getPropName() + ": " + simple.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> clear() {
|
||||
list.clear();
|
||||
|
||||
@@ -81,6 +81,13 @@ public final class BatchControl {
|
||||
*/
|
||||
private int bufferMax;
|
||||
|
||||
/**
|
||||
* True while the batched requests are being executed. A persist performed from a
|
||||
* BeanPersistController callback must not flush the batch that is executing it, the same way
|
||||
* executeNow() stops a query from doing so.
|
||||
*/
|
||||
private boolean executing;
|
||||
|
||||
private final Queue[] queues = new Queue[2];
|
||||
|
||||
static final int DELETE_QUEUE = 0;
|
||||
@@ -139,8 +146,9 @@ public final class BatchControl {
|
||||
* to the depth.
|
||||
*/
|
||||
public int executeStatementOrBatch(PersistRequest request, boolean batch, boolean addBatch) throws BatchedSqlException {
|
||||
if (!batch || (batchFlushOnMixed && !isBeansEmpty())) {
|
||||
// flush when mixing beans and updateSql
|
||||
if (!executing && (!batch || (batchFlushOnMixed && !isBeansEmpty()))) {
|
||||
// flush when mixing beans and updateSql, unless we are inside the execution of the batch
|
||||
// itself : flushing then would issue the statements queued behind the current one early
|
||||
flush();
|
||||
}
|
||||
if (!batch) {
|
||||
@@ -163,8 +171,9 @@ public final class BatchControl {
|
||||
* according to the depth (object graph depth).
|
||||
*/
|
||||
public int executeOrQueue(PersistRequestBean<?> request, boolean batch) throws BatchedSqlException {
|
||||
if (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty())) {
|
||||
// flush when mixing beans and updateSql
|
||||
if (!executing && (!batch || (batchFlushOnMixed && !pstmtHolder.isEmpty()))) {
|
||||
// flush when mixing beans and updateSql, unless we are inside the execution of the batch
|
||||
// itself : flushing then would issue the statements queued behind the current one early
|
||||
flush();
|
||||
}
|
||||
if (!batch) {
|
||||
@@ -226,7 +235,9 @@ public final class BatchControl {
|
||||
void executeNow(ArrayList<PersistRequest> list) throws BatchedSqlException {
|
||||
boolean old = transaction.isFlushOnQuery();
|
||||
transaction.setFlushOnQuery(false);
|
||||
// disable flush on query due transaction callbacks
|
||||
boolean oldExecuting = executing;
|
||||
executing = true;
|
||||
// disable flush on query and on persist due transaction callbacks
|
||||
try {
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i % batchSize == 0) {
|
||||
@@ -237,6 +248,7 @@ public final class BatchControl {
|
||||
}
|
||||
flushPstmtHolder();
|
||||
} finally {
|
||||
executing = oldExecuting;
|
||||
transaction.setFlushOnQuery(old);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -4,11 +4,9 @@ import io.ebean.InsertOptions;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Postgres specific generation of insert on conflict.
|
||||
@@ -51,10 +49,7 @@ final class InsertMetaOptionsPostgres implements InsertMetaOptions {
|
||||
meta.sql(request, !withId, baseTable, false);
|
||||
request.append(" on conflict ");
|
||||
|
||||
List<String> uniqueColumns = desc.uniqueProps().stream()
|
||||
.flatMap(Arrays::stream)
|
||||
.map(BeanProperty::dbColumn)
|
||||
.collect(Collectors.toList());
|
||||
List<String> uniqueColumns = InsertMetaOptionsSupport.uniqueColumns(desc, withId);
|
||||
|
||||
String constraintName = options.constraint();
|
||||
if (constraintName != null) {
|
||||
|
||||
+1
-6
@@ -4,11 +4,9 @@ import io.ebean.InsertOptions;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* SQLite specific generation of insert on conflict.
|
||||
@@ -57,10 +55,7 @@ final class InsertMetaOptionsSqlite implements InsertMetaOptions {
|
||||
meta.sql(request, !withId, baseTable, false);
|
||||
request.append(" on conflict (");
|
||||
|
||||
List<String> uniqueColumns = desc.uniqueProps().stream()
|
||||
.flatMap(Arrays::stream)
|
||||
.map(BeanProperty::dbColumn)
|
||||
.collect(Collectors.toList());
|
||||
List<String> uniqueColumns = InsertMetaOptionsSupport.uniqueColumns(desc, withId);
|
||||
|
||||
String cols = options.uniqueColumns();
|
||||
if (cols != null) {
|
||||
|
||||
+38
@@ -2,10 +2,13 @@ package io.ebeaninternal.server.persist.dml;
|
||||
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanProperty;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.deploy.generatedproperty.GeneratedProperty;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Shared support for the platform specific insert on conflict do update generation.
|
||||
@@ -15,6 +18,41 @@ final class InsertMetaOptionsSupport {
|
||||
private InsertMetaOptionsSupport() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns to use as the on conflict target.
|
||||
* <p>
|
||||
* Uses columns explicitly mapped as unique via {@code @Column(unique=true)} or
|
||||
* {@code @Index(unique=true)}. When there are none of those, falls back to the primary
|
||||
* key column(s) given the primary key constraint is inherently unique. This fallback only
|
||||
* applies when the id value is included in the insert (withId) as an insert relying on a
|
||||
* database generated id (identity/sequence) is never going to naturally conflict on that id.
|
||||
*/
|
||||
static List<String> uniqueColumns(BeanDescriptor<?> desc, boolean withId) {
|
||||
List<String> uniqueColumns = desc.uniqueProps().stream()
|
||||
.flatMap(Arrays::stream)
|
||||
.map(BeanProperty::dbColumn)
|
||||
.collect(Collectors.toList());
|
||||
if (uniqueColumns.isEmpty() && withId) {
|
||||
uniqueColumns = idColumns(desc);
|
||||
}
|
||||
return uniqueColumns;
|
||||
}
|
||||
|
||||
private static List<String> idColumns(BeanDescriptor<?> desc) {
|
||||
BeanProperty idProperty = desc.idProperty();
|
||||
if (idProperty == null) {
|
||||
return List.of();
|
||||
}
|
||||
if (idProperty.isEmbedded() && idProperty instanceof BeanPropertyAssocOne) {
|
||||
List<String> columns = new ArrayList<>();
|
||||
for (BeanProperty embedded : ((BeanPropertyAssocOne<?>) idProperty).properties()) {
|
||||
columns.add(embedded.dbColumn());
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
return List.of(idProperty.dbColumn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the columns that should be excluded from the generated "do update set" clause
|
||||
* as they are not updatable, e.g. {@code @Column(updatable=false)} or a generated property
|
||||
|
||||
@@ -4,24 +4,19 @@ import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.CountMetric;
|
||||
import io.ebean.metric.CountMetricStats;
|
||||
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* Used to collect counter metrics.
|
||||
*/
|
||||
final class DCountMetric implements CountMetric {
|
||||
|
||||
private final String name;
|
||||
private final LongAdder count = new LongAdder();
|
||||
private final ValueAdder count = new ValueAdder();
|
||||
private String reportName;
|
||||
|
||||
DCountMetric(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a value. Usually the value is Time or Bytes etc.
|
||||
*/
|
||||
@Override
|
||||
public void add(long value) {
|
||||
count.add(value);
|
||||
@@ -29,12 +24,12 @@ final class DCountMetric implements CountMetric {
|
||||
|
||||
@Override
|
||||
public void increment() {
|
||||
count.increment();
|
||||
count.add(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return count.sum() == 0;
|
||||
return count.currentValue() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,12 +39,25 @@ final class DCountMetric implements CountMetric {
|
||||
|
||||
@Override
|
||||
public long get(boolean reset) {
|
||||
return reset ? count.sumThenReset() : count.sum();
|
||||
return reset ? count.getAndReset() : count.cumulative();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
long val = visitor.reset() ? count.sumThenReset() : count.sum();
|
||||
long val;
|
||||
switch (visitor.mode()) {
|
||||
case RESET:
|
||||
val = count.getAndReset();
|
||||
break;
|
||||
case CUMULATIVE:
|
||||
val = count.cumulative();
|
||||
break;
|
||||
case DELTA:
|
||||
val = count.delta();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown metric collection mode");
|
||||
}
|
||||
if (val > 0) {
|
||||
final String name = reportName != null ? reportName : reportName(visitor);
|
||||
visitor.visitCount(new DCountMetricStats(name, val));
|
||||
|
||||
@@ -20,7 +20,7 @@ final class DQueryPlanMetric implements QueryPlanMetric {
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
TimedMetricStats stats = metric.collect(visitor.reset());
|
||||
TimedMetricStats stats = metric.collect(visitor.mode());
|
||||
if (stats != null) {
|
||||
String name = reportName != null ? reportName : reportName(visitor);
|
||||
visitor.visitQuery(new Stats(name, meta, stats, collected));
|
||||
|
||||
@@ -3,9 +3,6 @@ package io.ebeaninternal.server.profile;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import io.ebean.metric.TimedMetric;
|
||||
|
||||
import java.util.concurrent.atomic.LongAccumulator;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* Used to collect timed execution statistics.
|
||||
* <p>
|
||||
@@ -15,14 +12,19 @@ import java.util.concurrent.atomic.LongAdder;
|
||||
final class DTimedMetric implements TimedMetric {
|
||||
|
||||
private final String name;
|
||||
private final LongAdder count = new LongAdder();
|
||||
private final LongAdder total = new LongAdder();
|
||||
private final LongAccumulator max = new LongAccumulator(Math::max, 0);
|
||||
private final ValueAdder count = new ValueAdder();
|
||||
private final ValueAdder total = new ValueAdder();
|
||||
private final ValueMax max;
|
||||
private boolean collected;
|
||||
private String reportName;
|
||||
|
||||
DTimedMetric(String name) {
|
||||
this(name, new ValueMax());
|
||||
}
|
||||
|
||||
DTimedMetric(String name, ValueMax max) {
|
||||
this.name = name;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -32,7 +34,7 @@ final class DTimedMetric implements TimedMetric {
|
||||
final long mean = totalMicros / batch;
|
||||
count.add(batch);
|
||||
total.add(totalMicros);
|
||||
max.accumulate(mean);
|
||||
max.add(mean);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +45,14 @@ final class DTimedMetric implements TimedMetric {
|
||||
|
||||
@Override
|
||||
public void add(long value) {
|
||||
count.increment();
|
||||
count.add(1);
|
||||
total.add(value);
|
||||
max.accumulate(value);
|
||||
max.add(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return count.sum() == 0;
|
||||
return count.currentValue() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,30 +64,63 @@ final class DTimedMetric implements TimedMetric {
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
final long countSum = visitor.reset() ? count.sumThenReset() : count.sum();
|
||||
if (countSum > 0) {
|
||||
final DTimeMetricStats stats = collect(visitor.mode());
|
||||
if (stats != null) {
|
||||
final String name = reportName != null ? reportName : reportName(visitor);
|
||||
visitor.visitTimed(stats(visitor.reset(), name, countSum));
|
||||
stats.setName(name);
|
||||
visitor.visitTimed(stats);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DTimeMetricStats collect(boolean reset) {
|
||||
final long countSum = reset ? count.sumThenReset() : count.sum();
|
||||
return collect(reset ? MetricVisitor.Mode.RESET : MetricVisitor.Mode.CUMULATIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DTimeMetricStats collect(MetricVisitor.Mode mode) {
|
||||
final long maxValue = max.collect();
|
||||
final long countSum;
|
||||
switch (mode) {
|
||||
case RESET:
|
||||
countSum = count.getAndReset();
|
||||
break;
|
||||
case CUMULATIVE:
|
||||
countSum = count.cumulative();
|
||||
break;
|
||||
case DELTA:
|
||||
countSum = count.delta();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown metric collection mode");
|
||||
}
|
||||
if (countSum == 0) {
|
||||
return null;
|
||||
} else {
|
||||
return stats(reset, name, countSum);
|
||||
return stats(mode, name, countSum, maxValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current statistics resetting the internal values if reset is true.
|
||||
*/
|
||||
private DTimeMetricStats stats(boolean reset, String name, long countSum) {
|
||||
private DTimeMetricStats stats(MetricVisitor.Mode mode, String name, long countSum, long maxValue) {
|
||||
try {
|
||||
final long totalSum = reset ? total.sumThenReset() : total.sum();
|
||||
return new DTimeMetricStats(name, collected, countSum, totalSum, max.getThenReset());
|
||||
final long totalSum;
|
||||
switch (mode) {
|
||||
case RESET:
|
||||
totalSum = total.getAndReset();
|
||||
break;
|
||||
case CUMULATIVE:
|
||||
totalSum = total.cumulative();
|
||||
break;
|
||||
case DELTA:
|
||||
totalSum = total.delta();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown metric collection mode");
|
||||
}
|
||||
return new DTimeMetricStats(name, collected, countSum, totalSum, maxValue);
|
||||
} finally {
|
||||
collected = true;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ final class DTimedProfileLocation extends DProfileLocation implements TimedProfi
|
||||
|
||||
@Override
|
||||
public void visit(MetricVisitor visitor) {
|
||||
TimedMetricStats collect = timedMetric.collect(visitor.reset());
|
||||
TimedMetricStats collect = timedMetric.collect(visitor.mode());
|
||||
if (collect != null) {
|
||||
final String name = reportName != null ? reportName : reportName(visitor, collect.name());
|
||||
collect.setName(name);
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
|
||||
/**
|
||||
* Accumulates a value while supporting cumulative and reset-based delta reads.
|
||||
*/
|
||||
final class ValueAdder {
|
||||
|
||||
private final LongAdder value = new LongAdder();
|
||||
private final AtomicLong previousValue = new AtomicLong();
|
||||
|
||||
void add(long amount) {
|
||||
value.add(amount);
|
||||
}
|
||||
|
||||
long cumulative() {
|
||||
return value.sum();
|
||||
}
|
||||
|
||||
long delta() {
|
||||
long currentValue = value.sum();
|
||||
long previous = previousValue.getAndSet(currentValue);
|
||||
return currentValue >= previous ? currentValue - previous : currentValue;
|
||||
}
|
||||
|
||||
long getAndReset() {
|
||||
long currentValue = value.sumThenReset();
|
||||
previousValue.set(0);
|
||||
return currentValue;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
value.reset();
|
||||
previousValue.set(0);
|
||||
}
|
||||
|
||||
long currentValue() {
|
||||
return value.sum();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package io.ebeaninternal.server.profile;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.LongAccumulator;
|
||||
import java.util.function.LongSupplier;
|
||||
|
||||
/**
|
||||
* Accumulates a maximum value and publishes it in rolling 59-second windows.
|
||||
*/
|
||||
final class ValueMax {
|
||||
|
||||
private static final long WINDOW_NANOS = TimeUnit.SECONDS.toNanos(59);
|
||||
|
||||
private final LongSupplier nanoTime;
|
||||
private final LongAccumulator value;
|
||||
private volatile long published;
|
||||
private long lastResetNanos;
|
||||
|
||||
ValueMax() {
|
||||
this(System::nanoTime);
|
||||
}
|
||||
|
||||
ValueMax(LongSupplier nanoTime) {
|
||||
this.nanoTime = nanoTime;
|
||||
this.value = new LongAccumulator(Math::max, 0);
|
||||
this.lastResetNanos = nanoTime.getAsLong() - 2 * WINDOW_NANOS;
|
||||
}
|
||||
|
||||
void add(long amount) {
|
||||
value.accumulate(amount);
|
||||
}
|
||||
|
||||
synchronized long collect() {
|
||||
long now = nanoTime.getAsLong();
|
||||
if (now - lastResetNanos >= WINDOW_NANOS) {
|
||||
published = value.getThenReset();
|
||||
lastResetNanos = now;
|
||||
}
|
||||
return published;
|
||||
}
|
||||
|
||||
synchronized void reset() {
|
||||
value.reset();
|
||||
published = 0;
|
||||
lastResetNanos = nanoTime.getAsLong() - 2 * WINDOW_NANOS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public class CQueryPlan implements SpiQueryPlan {
|
||||
|
||||
@Override
|
||||
public final DQueryPlanOutput createMeta(String bind, String planString) {
|
||||
return new DQueryPlanOutput(beanType(), name, hash, sql, profileLocation, bind, planString);
|
||||
return new DQueryPlanOutput(beanType(), server.name(), name, hash, sql, profileLocation, bind, planString);
|
||||
}
|
||||
|
||||
public DataReader createDataReader(boolean unmodifiable, ResultSet rset) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.time.Instant;
|
||||
public final class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
|
||||
|
||||
private final Class<?> beanType;
|
||||
private final String dbName;
|
||||
private final String label;
|
||||
private final ProfileLocation profileLocation;
|
||||
|
||||
@@ -25,8 +26,9 @@ public final class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
|
||||
private Instant whenCaptured;
|
||||
private Object tenantId;
|
||||
|
||||
public DQueryPlanOutput(Class<?> beanType, String label, String hash, String sql, ProfileLocation profileLocation, String bind, String plan) {
|
||||
public DQueryPlanOutput(Class<?> beanType, String dbName, String label, String hash, String sql, ProfileLocation profileLocation, String bind, String plan) {
|
||||
this.beanType = beanType;
|
||||
this.dbName = dbName;
|
||||
this.label = label;
|
||||
this.hash = hash;
|
||||
this.sql = sql;
|
||||
@@ -35,6 +37,11 @@ public final class DQueryPlanOutput implements MetaQueryPlan, SpiDbQueryPlan {
|
||||
this.plan = plan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dbName() {
|
||||
return dbName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String hash() {
|
||||
return hash;
|
||||
|
||||
@@ -21,11 +21,13 @@ public final class SqlQueryPlan implements SpiQueryPlan {
|
||||
private final String name;
|
||||
private final String hash;
|
||||
private final String sql;
|
||||
private final String dbName;
|
||||
private final SpiQueryBindCapture bindCapture;
|
||||
|
||||
SqlQueryPlan(SpiEbeanServer server, String name, String sql) {
|
||||
this.name = name;
|
||||
this.sql = sql;
|
||||
this.dbName = server.name();
|
||||
this.hash = Md5.hash(sql, name);
|
||||
this.bindCapture = server.createQueryBindCapture(this);
|
||||
}
|
||||
@@ -77,6 +79,6 @@ public final class SqlQueryPlan implements SpiQueryPlan {
|
||||
|
||||
@Override
|
||||
public SpiDbQueryPlan createMeta(String bind, String planString) {
|
||||
return new DQueryPlanOutput(null, name, hash, sql, null, bind, planString);
|
||||
return new DQueryPlanOutput(null, dbName, name, hash, sql, null, bind, planString);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import io.ebeaninternal.api.SpiQuery;
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
@@ -90,6 +92,11 @@ public final class DefaultMappedQuery<T, D> implements MappedQuery<D> {
|
||||
return Optional.ofNullable(findOne());
|
||||
}
|
||||
|
||||
@Override
|
||||
public D findOneOrThrow() {
|
||||
return mapper().map(query.findOneOrThrow());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedList<D> findPagedList() {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
@@ -103,6 +110,27 @@ public final class DefaultMappedQuery<T, D> implements MappedQuery<D> {
|
||||
return query.findStream().map(source -> m.map(source, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEach(Consumer<D> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEach(source -> consumer.accept(m.map(source, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEach(int batch, Consumer<List<D>> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEach(batch, sourceBatch -> consumer.accept(m.mapList(sourceBatch, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findEachWhile(Predicate<D> consumer) {
|
||||
DtoMapper<T, D> m = mapper();
|
||||
DtoMapContext context = new DtoMapContext();
|
||||
query.findEachWhile(source -> consumer.test(m.map(source, context)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedQuery<D> usingMaster(boolean useMaster) {
|
||||
query.usingMaster(useMaster);
|
||||
@@ -120,4 +148,9 @@ public final class DefaultMappedQuery<T, D> implements MappedQuery<D> {
|
||||
query.usingConnection(connection);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
query.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import io.ebeaninternal.server.query.NativeSqlQueryPlanKey;
|
||||
import io.ebeaninternal.server.rawsql.SpiRawSql;
|
||||
import io.ebeaninternal.server.transaction.ExternalJdbcTransaction;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.persistence.PersistenceException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.Timestamp;
|
||||
@@ -1656,6 +1657,30 @@ public class DefaultOrmQuery<T> extends AbstractQuery implements SpiQuery<T> {
|
||||
return server.findOneOrEmpty(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final T findOneOrThrow() {
|
||||
return findOneOrEmpty().orElseThrow(() -> new EntityNotFoundException(notFoundMessage()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a decent default "not found" message using the id (if this is effectively a
|
||||
* find-by-id query) or, failing that, a single simple equality predicate (a likely
|
||||
* natural/unique key). Falls back to a generic message for anything more complex.
|
||||
*/
|
||||
private String notFoundMessage() {
|
||||
String type = beanDescriptor.type().getSimpleName();
|
||||
if (isFindById()) {
|
||||
return type + " not found for id: " + id;
|
||||
}
|
||||
if (whereExpressions != null) {
|
||||
String desc = whereExpressions.singleEqDescription();
|
||||
if (desc != null) {
|
||||
return type + " not found for " + desc;
|
||||
}
|
||||
}
|
||||
return type + " not found";
|
||||
}
|
||||
|
||||
@Override
|
||||
public final FutureIds<T> findFutureIds() {
|
||||
return server.findFutureIds(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package io.ebeaninternal.server.querydefn;
|
||||
|
||||
import io.ebean.Database;
|
||||
import io.ebean.Transaction;
|
||||
import io.ebean.Update;
|
||||
import io.ebeaninternal.api.BindParams;
|
||||
import io.ebeaninternal.api.SpiUpdate;
|
||||
@@ -30,6 +31,7 @@ public final class DefaultOrmUpdate<T> implements SpiUpdate<T>, Serializable {
|
||||
private String generatedSql;
|
||||
private final String baseTable;
|
||||
private final OrmUpdateType type;
|
||||
private transient Transaction transaction;
|
||||
|
||||
/**
|
||||
* Create with a specific server. This means you can use the
|
||||
@@ -88,7 +90,13 @@ public final class DefaultOrmUpdate<T> implements SpiUpdate<T>, Serializable {
|
||||
|
||||
@Override
|
||||
public int execute() {
|
||||
return server.execute(this);
|
||||
return server.execute(this, transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultOrmUpdate<T> usingTransaction(Transaction transaction) {
|
||||
this.transaction = transaction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
@@ -281,6 +281,17 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeQuery<T> usingConnection(Connection connection) {
|
||||
DefaultRelationalQuery.this.usingConnection(connection);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
DefaultRelationalQuery.this.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T findOne() {
|
||||
return findSingleAttribute(type);
|
||||
@@ -326,6 +337,17 @@ public final class DefaultRelationalQuery extends AbstractQuery implements SpiSq
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeQuery<T> usingConnection(Connection connection) {
|
||||
DefaultRelationalQuery.this.usingConnection(connection);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
DefaultRelationalQuery.this.cancel();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public T findOne() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import io.ebeaninternal.api.SpiExpressionList;
|
||||
import io.ebeaninternal.api.SpiQueryManyJoin;
|
||||
import io.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
|
||||
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
|
||||
import io.ebeaninternal.server.el.ElPropertyDeploy;
|
||||
import io.ebeaninternal.server.el.ElPropertyValue;
|
||||
|
||||
@@ -358,6 +359,54 @@ public final class OrmQueryDetail implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* After sorting, fold any fetch path that only fetches a *ToOne association's id
|
||||
* into a plain select of the parent path instead - the foreign key column is already
|
||||
* present on the owning table so the join can be avoided entirely.
|
||||
* <p>
|
||||
* Iterates the fetch paths backwards (deepest first) so that a path already folded
|
||||
* away does not stop a shallower ancestor also being folded, while a path that must
|
||||
* remain a real join protects its own parent (added to {@code nonRemovable}) from
|
||||
* being folded, as the parent's join is required to reach it.
|
||||
*/
|
||||
private void convertIdFetches(BeanDescriptor<?> desc) {
|
||||
Set<String> nonRemovable = new HashSet<>();
|
||||
String[] paths = fetchPaths.keySet().toArray(new String[0]);
|
||||
int i = paths.length;
|
||||
while (i-- > 0) {
|
||||
String path = paths[i];
|
||||
ElPropertyDeploy el = desc.elPropertyDeploy(path);
|
||||
OrmQueryProperties prop = fetchPaths.get(path);
|
||||
if (nonRemovable.contains(path)) {
|
||||
// a still-existing child fetch depends on this join, don't remove
|
||||
} else if (el == null) {
|
||||
throw new PersistenceException("Invalid fetch path " + path + " from " + desc.fullName());
|
||||
} else if (el.beanProperty() instanceof BeanPropertyAssocOne) {
|
||||
BeanPropertyAssocOne<?> assoc = (BeanPropertyAssocOne<?>) el.beanProperty();
|
||||
// exported (mappedBy) one-to-one has no local foreign key column - it always needs the join
|
||||
if (assoc.hasForeignKeyConstraint() && !assoc.isOneToOneExported()) {
|
||||
if (prop.includesExactly(assoc.targetDescriptor().idName())) {
|
||||
String parentPath = prop.getParentPath();
|
||||
OrmQueryProperties parentProp = parentPath == null ? baseProps : fetchPaths.get(parentPath);
|
||||
if (parentProp != null && parentProp.hasProperties()) {
|
||||
OrmQueryProperties newParentProp = parentProp.withAddedInclude(assoc.name());
|
||||
if (parentPath == null) {
|
||||
baseProps = newParentProp;
|
||||
} else {
|
||||
fetchPaths.put(parentPath, newParentProp);
|
||||
}
|
||||
fetchPaths.remove(path);
|
||||
prop = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (prop != null && prop.getParentPath() != null) {
|
||||
nonRemovable.add(prop.getParentPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark 'fetch joins' to 'many' properties over to 'query joins' where needed.
|
||||
*
|
||||
@@ -375,6 +424,7 @@ public final class OrmQueryDetail implements Serializable {
|
||||
boolean fetchJoinFirstMany = allowOne;
|
||||
|
||||
sortFetchPaths(beanDescriptor, addIds);
|
||||
convertIdFetches(beanDescriptor);
|
||||
List<FetchEntry> pairs = sortByFetchPreference(beanDescriptor);
|
||||
|
||||
for (FetchEntry pair : pairs) {
|
||||
|
||||
@@ -151,6 +151,22 @@ public final class OrmQueryProperties implements Serializable {
|
||||
: buildImmutableQueryPlanHashSuffix(sourceFetchConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy constructor with a replacement included set (used by {@link #withAddedInclude(String)}).
|
||||
*/
|
||||
private OrmQueryProperties(OrmQueryProperties source, Set<String> replacementIncluded) {
|
||||
this.fetchConfig = source.fetchConfig;
|
||||
this.parentPath = source.parentPath;
|
||||
this.path = source.path;
|
||||
this.allProperties = source.allProperties;
|
||||
this.cache = source.cache;
|
||||
this.filterMany = source.filterMany;
|
||||
this.markForQueryJoin = source.markForQueryJoin;
|
||||
this.included = immutableIncluded(replacementIncluded);
|
||||
this.immutableHashPrefix = buildImmutableQueryPlanHashPrefix(path, this.included);
|
||||
this.immutableHashSuffix = source.immutableHashSuffix;
|
||||
}
|
||||
|
||||
private static Set<String> immutableIncluded(Set<String> included) {
|
||||
if (included == null) {
|
||||
return null;
|
||||
@@ -412,6 +428,37 @@ public final class OrmQueryProperties implements Serializable {
|
||||
return included == null || included.contains(propName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the included properties are exactly the single given property.
|
||||
* <p>
|
||||
* Used to detect a fetch/select of a *ToOne association that only includes the
|
||||
* target's id property - a candidate for folding into the parent select as a plain
|
||||
* foreign key property (avoiding an unnecessary join).
|
||||
*/
|
||||
boolean includesExactly(String property) {
|
||||
return included != null && included.size() == 1 && included.contains(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new instance with the given property added to the included set.
|
||||
* <p>
|
||||
* Used to fold an id-only *ToOne fetch into this select as a plain foreign key
|
||||
* property. A new instance is returned (rather than mutating {@link #included} in
|
||||
* place) as this instance's included set is immutable and may be shared/cached
|
||||
* (e.g. via FetchGroup reuse).
|
||||
*/
|
||||
OrmQueryProperties withAddedInclude(String property) {
|
||||
if (allProperties) {
|
||||
return this;
|
||||
}
|
||||
Set<String> newIncluded = new LinkedHashSet<>();
|
||||
if (included != null) {
|
||||
newIncluded.addAll(included);
|
||||
}
|
||||
newIncluded.add(property);
|
||||
return new OrmQueryProperties(this, newIncluded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this path as needing to be a query join.
|
||||
*/
|
||||
|
||||
+3
-1
@@ -285,7 +285,9 @@ public class TransactionManager implements SpiTransactionManager {
|
||||
|
||||
private SpiTransaction createTransaction(TxScope txScope) {
|
||||
if (txScope.isReadonly()) {
|
||||
return createReadOnlyTransaction(null, false);
|
||||
// Honor isolation on read-only scopes (e.g. @Transactional(readOnly=true, isolation=...))
|
||||
SpiTransaction transaction = createReadOnlyTransaction(null, false);
|
||||
return transactionFactory.setIsolationLevel(transaction, txScope.getIsolationLevel());
|
||||
} else {
|
||||
return createTransaction(true, txScope.getIsolationLevel());
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
this.databasePlatform = config.getDatabasePlatform();
|
||||
this.postgres = isPostgresCompatible(config.getDatabasePlatform());
|
||||
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
|
||||
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : null;
|
||||
this.jsonManager = (objectMapperPresent) ? new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection()) : null;
|
||||
this.objectMapper = (objectMapperPresent) ? initObjectMapper(config) : config.getObjectMapper();
|
||||
this.jsonManager = new TypeJsonManager(postgres, objectMapper, config.getJsonMutationDetection());
|
||||
this.extraTypeFactory = new DefaultTypeFactory(config);
|
||||
this.arrayTypeListFactory = arrayTypeListFactory(config.getDatabasePlatform());
|
||||
this.arrayTypeSetFactory = arrayTypeSetFactory(config.getDatabasePlatform());
|
||||
@@ -413,7 +413,7 @@ public final class DefaultTypeManager implements TypeManager {
|
||||
|
||||
private ScalarType<?> createJsonObjectMapperType(DeployProperty prop, int dbType, DocPropertyType docType) {
|
||||
if (jsonMapper == null) {
|
||||
throw new IllegalArgumentException("Unsupported @DbJson mapping - Missing dependency ebean-jackson-mapper? Jackson ObjectMapper not present for " + prop);
|
||||
throw new IllegalArgumentException("Unsupported @DbJson mapping - missing JSON mapper dependency for " + prop);
|
||||
}
|
||||
if (MutationDetection.DEFAULT == prop.mutationDetection()) {
|
||||
prop.setMutationDetection(jsonManager.mutationDetection());
|
||||
|
||||
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.profile;
|
||||
|
||||
import io.ebean.meta.BasicMetricVisitor;
|
||||
import io.ebean.meta.MetaCountMetric;
|
||||
import io.ebean.meta.MetricVisitor;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
@@ -40,4 +41,61 @@ class DCountMetricTest {
|
||||
assertThat(result2.get(0).count()).isEqualTo(12);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void cumulativeAndDeltaAreIndependent() {
|
||||
DCountMetric counter = new DCountMetric("org.hello");
|
||||
counter.add(7);
|
||||
|
||||
assertThat(counter.get(false)).isEqualTo(7);
|
||||
assertThat(counter.get(false)).isEqualTo(7);
|
||||
assertThat(counter.get(true)).isEqualTo(7);
|
||||
|
||||
counter.add(5);
|
||||
assertThat(counter.get(false)).isEqualTo(5);
|
||||
assertThat(counter.get(true)).isEqualTo(5);
|
||||
assertThat(counter.get(true)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueAdderSupportsExplicitCollectionOperations() {
|
||||
var values = new ValueAdder();
|
||||
values.add(7);
|
||||
|
||||
assertThat(values.cumulative()).isEqualTo(7);
|
||||
assertThat(values.delta()).isEqualTo(7);
|
||||
|
||||
values.add(5);
|
||||
assertThat(values.cumulative()).isEqualTo(12);
|
||||
assertThat(values.delta()).isEqualTo(5);
|
||||
assertThat(values.getAndReset()).isEqualTo(12);
|
||||
assertThat(values.cumulative()).isEqualTo(0);
|
||||
assertThat(values.delta()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitorCanCollectDeltaWithoutResettingCumulativeValue() {
|
||||
var counter = new DCountMetric("org.hello");
|
||||
counter.add(7);
|
||||
|
||||
var cumulative = new BasicMetricVisitor("db", naming, MetricVisitor.Mode.CUMULATIVE, true, true, true);
|
||||
counter.visit(cumulative);
|
||||
assertThat(cumulative.countMetrics()).hasSize(1);
|
||||
assertThat(cumulative.countMetrics().get(0).count()).isEqualTo(7);
|
||||
|
||||
var delta = new BasicMetricVisitor("db", naming, MetricVisitor.Mode.DELTA, true, true, true);
|
||||
counter.visit(delta);
|
||||
assertThat(delta.countMetrics()).hasSize(1);
|
||||
assertThat(delta.countMetrics().get(0).count()).isEqualTo(7);
|
||||
|
||||
counter.add(5);
|
||||
delta = new BasicMetricVisitor("db", naming, MetricVisitor.Mode.DELTA, true, true, true);
|
||||
counter.visit(delta);
|
||||
assertThat(delta.countMetrics()).hasSize(1);
|
||||
assertThat(delta.countMetrics().get(0).count()).isEqualTo(5);
|
||||
|
||||
cumulative = new BasicMetricVisitor("db", naming, MetricVisitor.Mode.CUMULATIVE, true, true, true);
|
||||
counter.visit(cumulative);
|
||||
assertThat(cumulative.countMetrics().get(0).count()).isEqualTo(12);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,19 +5,22 @@ import io.ebean.meta.MetaQueryMetric;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class DQueryPlanMetricTest {
|
||||
|
||||
private final AtomicLong nanoTime = new AtomicLong();
|
||||
Function<String, String> naming = (String name) -> "prefix[" + name.replace('.', '-') + "]";
|
||||
|
||||
@Test
|
||||
void visit() {
|
||||
|
||||
DQueryPlanMeta meta = new DQueryPlanMeta(Object.class, "dto.Object.lab", "lab", null, "sql", "hash");
|
||||
DTimedMetric metric = new DTimedMetric("org.timed.plan");
|
||||
DTimedMetric metric = new DTimedMetric("org.timed.plan", new ValueMax(nanoTime::get));
|
||||
DQueryPlanMetric planMetric = new DQueryPlanMetric(meta, metric);
|
||||
|
||||
metric.add(560);
|
||||
@@ -46,10 +49,10 @@ class DQueryPlanMetricTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void visitCumulativeResetsMax() {
|
||||
void visitCumulativePublishesSharedMax() {
|
||||
|
||||
DQueryPlanMeta meta = new DQueryPlanMeta(Object.class, "dto.Object.lab", "lab", null, "sql", "hash");
|
||||
DTimedMetric metric = new DTimedMetric("org.timed.plan");
|
||||
DTimedMetric metric = new DTimedMetric("org.timed.plan", new ValueMax(nanoTime::get));
|
||||
DQueryPlanMetric planMetric = new DQueryPlanMetric(meta, metric);
|
||||
|
||||
metric.add(560);
|
||||
@@ -74,7 +77,7 @@ class DQueryPlanMetricTest {
|
||||
assertThat(result.get(0).name()).isEqualTo("prefix[dto-Object-lab]");
|
||||
assertThat(result.get(0).count()).isEqualTo(2);
|
||||
assertThat(result.get(0).total()).isEqualTo(820);
|
||||
assertThat(result.get(0).max()).isEqualTo(0);
|
||||
assertThat(result.get(0).max()).isEqualTo(560);
|
||||
}
|
||||
|
||||
metric.add(410);
|
||||
@@ -87,7 +90,14 @@ class DQueryPlanMetricTest {
|
||||
assertThat(result.get(0).name()).isEqualTo("prefix[dto-Object-lab]");
|
||||
assertThat(result.get(0).count()).isEqualTo(3);
|
||||
assertThat(result.get(0).total()).isEqualTo(1230);
|
||||
assertThat(result.get(0).max()).isEqualTo(410);
|
||||
assertThat(result.get(0).max()).isEqualTo(560);
|
||||
}
|
||||
|
||||
nanoTime.addAndGet(TimeUnit.SECONDS.toNanos(59));
|
||||
BasicMetricVisitor visitor = new BasicMetricVisitor("v", naming, false, true, true, true);
|
||||
planMetric.visit(visitor);
|
||||
List<MetaQueryMetric> result = visitor.queryMetrics();
|
||||
assertThat(result).hasSize(1);
|
||||
assertThat(result.get(0).max()).isEqualTo(410);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,20 @@ import io.ebean.meta.MetaTimedMetric;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class DTimedMetricTest {
|
||||
|
||||
private final AtomicLong nanoTime = new AtomicLong();
|
||||
|
||||
@Test
|
||||
public void addSinceNanos() throws InterruptedException {
|
||||
|
||||
DTimedMetric metric = new DTimedMetric("addSinceNanos");
|
||||
DTimedMetric metric = new DTimedMetric("addSinceNanos", new ValueMax(nanoTime::get));
|
||||
|
||||
long start = System.nanoTime();
|
||||
Thread.sleep(11);
|
||||
@@ -28,6 +32,7 @@ public class DTimedMetricTest {
|
||||
|
||||
metric.addSinceNanos(start);
|
||||
|
||||
nanoTime.addAndGet(TimeUnit.SECONDS.toNanos(59));
|
||||
stats = metric.collect(true);
|
||||
assertThat(stats.count()).isEqualTo(1);
|
||||
assertThat(stats.total()).isGreaterThan(10);
|
||||
@@ -37,7 +42,7 @@ public class DTimedMetricTest {
|
||||
@Test
|
||||
public void addBatchSince() throws InterruptedException {
|
||||
|
||||
DTimedMetric metric = new DTimedMetric("addSinceNanos");
|
||||
DTimedMetric metric = new DTimedMetric("addSinceNanos", new ValueMax(nanoTime::get));
|
||||
|
||||
long start = System.nanoTime();
|
||||
Thread.sleep(11);
|
||||
@@ -52,6 +57,7 @@ public class DTimedMetricTest {
|
||||
|
||||
metric.addBatchSince(start, 2);
|
||||
|
||||
nanoTime.addAndGet(TimeUnit.SECONDS.toNanos(59));
|
||||
stats = metric.collect(true);
|
||||
assertThat(stats.count()).isEqualTo(2);
|
||||
assertThat(stats.total()).isGreaterThan(10000);
|
||||
@@ -92,8 +98,8 @@ public class DTimedMetricTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void collectCumulativeResetsMax() {
|
||||
DTimedMetric metric = new DTimedMetric("org.timed");
|
||||
void collectCumulativePublishesSharedMax() {
|
||||
DTimedMetric metric = new DTimedMetric("org.timed", new ValueMax(nanoTime::get));
|
||||
metric.add(560);
|
||||
metric.add(500);
|
||||
|
||||
@@ -105,7 +111,7 @@ public class DTimedMetricTest {
|
||||
stats = metric.collect(false);
|
||||
assertThat(stats.count()).isEqualTo(2);
|
||||
assertThat(stats.total()).isEqualTo(1060);
|
||||
assertThat(stats.max()).isEqualTo(0);
|
||||
assertThat(stats.max()).isEqualTo(560);
|
||||
|
||||
metric.add(160);
|
||||
metric.add(100);
|
||||
@@ -114,6 +120,31 @@ public class DTimedMetricTest {
|
||||
stats = metric.collect(false);
|
||||
assertThat(stats.count()).isEqualTo(5);
|
||||
assertThat(stats.total()).isEqualTo(1470);
|
||||
assertThat(stats.max()).isEqualTo(560);
|
||||
|
||||
nanoTime.addAndGet(TimeUnit.SECONDS.toNanos(59));
|
||||
stats = metric.collect(false);
|
||||
assertThat(stats.max()).isEqualTo(160);
|
||||
}
|
||||
|
||||
@Test
|
||||
void cumulativeAndDeltaAreIndependent() {
|
||||
DTimedMetric metric = new DTimedMetric("org.timed", new ValueMax(nanoTime::get));
|
||||
metric.add(560);
|
||||
metric.add(500);
|
||||
|
||||
DTimeMetricStats cumulative = metric.collect(false);
|
||||
assertThat(cumulative.count()).isEqualTo(2);
|
||||
assertThat(cumulative.total()).isEqualTo(1060);
|
||||
|
||||
metric.add(160);
|
||||
|
||||
DTimeMetricStats delta = metric.collect(true);
|
||||
assertThat(delta.count()).isEqualTo(3);
|
||||
assertThat(delta.total()).isEqualTo(1220);
|
||||
assertThat(delta.max()).isEqualTo(560);
|
||||
|
||||
cumulative = metric.collect(false);
|
||||
assertThat(cumulative).isNull();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean ddl generation</name>
|
||||
@@ -28,14 +28,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-all</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core-type</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package io.ebean.jackson.mapper;
|
||||
|
||||
|
||||
import io.ebean.core.type.JsonTrim;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean net postgis types</name>
|
||||
@@ -19,14 +19,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-opentelemetry</artifactId>
|
||||
@@ -28,7 +28,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -71,21 +71,21 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean pgvector types</name>
|
||||
@@ -19,14 +19,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean postgis types</name>
|
||||
@@ -19,14 +19,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-platform-postgres</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- provided scope -->
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>42.7.11</version>
|
||||
<version>42.7.12</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<name>ebean querybean</name>
|
||||
@@ -17,7 +17,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -59,14 +59,14 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-ddl-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -738,6 +738,11 @@ public abstract class QueryBean<T, R extends QueryBean<T, R>> implements IQueryB
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
query.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final R usingTransaction(Transaction transaction) {
|
||||
query.usingTransaction(transaction);
|
||||
|
||||
+6
-6
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-redis</artifactId>
|
||||
@@ -29,35 +29,35 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ ebean:
|
||||
dumpMetricsOptions: sql,hash,loc
|
||||
test:
|
||||
registerTestTenantProvider: false
|
||||
redis: latest
|
||||
redis: 8.6.2
|
||||
# shutdown: stop # stop | remove
|
||||
platform: h2 # h2, postgres, mysql, oracle, sqlserver, sqlite
|
||||
ddlMode: dropCreate # none | dropCreate | create | migration | createOnly | migrationDropCreate
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<artifactId>ebean-parent</artifactId>
|
||||
<groupId>io.ebean</groupId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ebean-redisson</artifactId>
|
||||
@@ -29,35 +29,35 @@
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-api</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-core</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-querybean</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>ebean-test</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.ebean</groupId>
|
||||
<artifactId>querybean-generator</artifactId>
|
||||
<version>18.3.0</version>
|
||||
<version>18.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@ import jakarta.persistence.Entity;
|
||||
@SuppressWarnings("unused")
|
||||
@Cache(naturalKey = {"one", "two"})
|
||||
@Entity
|
||||
public class OtherOne extends EBase {
|
||||
public class FOtherOne extends EBase {
|
||||
|
||||
private final String one;
|
||||
private final String two;
|
||||
private String notes;
|
||||
|
||||
public OtherOne(String one, String two, String notes) {
|
||||
public FOtherOne(String one, String two, String notes) {
|
||||
this.one = one;
|
||||
this.two = two;
|
||||
this.notes = notes;
|
||||
+2
-2
@@ -11,7 +11,7 @@ import java.time.LocalDate;
|
||||
@Cache(enableQueryCache = true, nearCache = true, naturalKey = "name")
|
||||
@CacheBeanTuning(maxSecsToLive = 1)
|
||||
@Entity
|
||||
public class Person extends EBase {
|
||||
public class FPerson extends EBase {
|
||||
|
||||
public enum Status {
|
||||
NEW,
|
||||
@@ -33,7 +33,7 @@ public class Person extends EBase {
|
||||
*/
|
||||
String key;
|
||||
|
||||
public Person(String name) {
|
||||
public FPerson(String name) {
|
||||
this.name = name;
|
||||
this.status = Status.NEW;
|
||||
}
|
||||
+2
-2
@@ -7,12 +7,12 @@ import jakarta.persistence.Entity;
|
||||
|
||||
@Cache(naturalKey = "name")
|
||||
@Entity
|
||||
public class RCust extends EBase {
|
||||
public class FRCust extends EBase {
|
||||
|
||||
@Index(unique = true)
|
||||
String name;
|
||||
|
||||
public RCust(String name) {
|
||||
public FRCust(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import jakarta.persistence.ManyToOne;
|
||||
|
||||
@Cache
|
||||
@Entity
|
||||
public class UChild extends Model {
|
||||
public class FUChild extends Model {
|
||||
|
||||
@Id
|
||||
long id;
|
||||
@@ -16,9 +16,9 @@ public class UChild extends Model {
|
||||
String name;
|
||||
|
||||
@ManyToOne
|
||||
final UParent parent;
|
||||
final FUParent parent;
|
||||
|
||||
public UChild(UParent parent, String name) {
|
||||
public FUChild(FUParent parent, String name) {
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user