Compare commits

..
16 Commits
Author SHA1 Message Date
e4bdec0fda Postgis: Add GraalVM native image reflect-config.json for org.postgis.DriverWrapperLW etc (#3887)
This is handy when compiling to native image with GraalVM and using Postgis and DriverWrapperLW
Adds the PGbox2d, PGbox3d, PGgeography, PGgeographyLW, PGgeometry, PGgeometryLW

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-09-19 21:27:59 +12:00
328b13af18 Dto mapper generation support for dbarray (#3886)
* Dto mapper generation support for @DbArray

Currently it is not detected DbArray and instead thinking its a ToMany which is invalid.

* Dto mapper generation support for @DbJson collections

Currently it is not detected and instead thinking its a ToMany which is invalid.

---------

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-09-18 18:57:46 +12:00
fd8ef48486 Build(deps-dev): Bump org.mariadb.jdbc:mariadb-java-client (#3881)
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.0.7 to 3.3.5.
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.0.7...3.3.5)

---
updated-dependencies:
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-version: 3.3.5
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-09-17 16:11:11 +12:00
69f39f9c82 Publish timed metric max values over rolling 59-second windows (#3880)
* Publish timed metric max values over rolling 59-second windows

* Publish timed metric max values over rolling 59-second windows

Use "now - 2 * WINDOW_NANOS" on initialise and reset to ensure
the next collection publishes.

---------

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-09-17 16:10:40 +12:00
2f1625316b Postgis: Add GraalVM native image reflect-config.json for org.postgis.DriverWrapperLW etc (#3885)
This is handy when compiling to native image with GraalVM and using Postgis and DriverWrapperLW

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-09-17 16:09:09 +12:00
952d9940e7 #3882 - Fix querybean-generator:19.3.0+ warning: Failed to write EntityClassRegister (#3884)
Fix a regression introduced in 19.3.0 via early creation of EntityClassRegister source file

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-09-17 15:57:38 +12:00
robin.bygrave c62b63119b Fix test pom versions to 18.5.0 after release 2026-08-18 15:48:26 +12:00
Rob Bygrave fdcbb91981 Version 18.5.0 2026-08-17 21:57:31 +12:00
Rob Bygrave 0e2b1f95b2 Bump ebean-datasource to 10.12 with cumulative + delta metrics support 2026-08-17 21:54:36 +12:00
robin.bygrave 80f5cab086 Version 18.5.0-RC1 2026-08-17 13:13:49 +12:00
AntoineDuComptoirDesPharmaciesandGitHub 90aff3d9ef Bugfix/3876 (#3877)
* Add test for #1852 : cascaded delete order broken by a write from a persist callback

A join entity owns the foreign key to the bean its delete cascades to, so the join row
has to be deleted first. When a BeanPersistController writes to the database from
preDelete, that write flushes the batch from inside the flush that is already running :
the outer flush has taken the join rows out of their bean holder, so the inner flush
finds only the assets and executes them first, which fails on the foreign key.

    BatchControl flush [DcoLink:0 d:2, DcoAsset:1 d:2]                  <- outer flush
    BatchControl flush [DcoLink:0 d:0, DcoAsset:1 d:2, DcoAudit:2 i:1]  <- from preDelete

The scenario is fixed as a side effect of a2f954a60 (#3830, released in 18.3.0) which
moved controllerPreDelete() ahead of the cascade. The test pins that down : it fails
with a DataIntegrityException on a2f954a60~1 and passes on master. The graph is fetched
up front on purpose, a lazy load would flush the batch on its own and hide the ordering.

* Add failing test reproducing #1852 : out of order cascaded delete on a self referencing tree

The shape reported on the issue in 2019 : a container cascades the delete down a tree of
TreeBean, and the deletes are not issued deepest first. On 18.4.0 :

    delete from dco_tree where id in (?)      -- the root, whose children are still there
    delete from dco_tree where id in (?,?,?)
    delete from dco_tree where id in (?,?)

Referential integrity constraint violation: FK_DCO_TREE_PARENT_ID.

This is a different defect from the batch reordering fixed by a2f954a60 : nothing is batched
here, the recursion itself walks the tree in the wrong order. Disabled so it does not break
the build, remove the annotation to see the failure.

* FIX: a persist done from a BeanPersistController callback flushes the batch mid-execution

#3148 stopped a query performed from a callback from flushing the batch that is already
executing : BatchControl.executeNow disables flushOnQuery for the duration. A persist done
from the same callback is not covered. It reaches BatchControl.executeOrQueue, which flushes,
and the statements queued behind the one currently executing are issued early.

Saving a parent/child graph in batch while an audit row is written from preInsert issues the
children before their parent has an id :

    insert into dco_link (parent_id, asset_id) values (?,?)
      NULL not allowed for column "PARENT_ID"

Same defect on the delete side, where the join rows are issued after the beans they reference
(#1852, #3185) — that path no longer reproduces since a2f954a60 moved controllerPreDelete()
ahead of the cascade, but only preDelete was moved, so preInsert and preUpdate still run
inside the flush.

Guard executeOrQueue with the same reasoning as the existing flushOnQuery guard : while the
batch is executing, queue rather than flush. The statements added meanwhile are picked up by
the do/while loop in executeAll().

* FIX: same guard on executeStatementOrBatch, a SqlUpdate from a callback also flushes mid-execution

executeStatementOrBatch() flushes on (batchFlushOnMixed && !isBeansEmpty()), and persistedBeans
is only cleared once executeAll() returns, so during the batch execution that condition holds and
a SqlUpdate run from a BeanPersistController callback re-enters the flush the same way a save does.

Reproduced with the same test, the callback running a SqlUpdate instead of a save :

    insert into dco_link (parent_id, asset_id) values (?,?)
      NULL not allowed for column "PARENT_ID"

The second flush of that method, on pstmtHolder.maxSize() >= batchSize, is left untouched : it can
only trigger when the pstmt holder fills up mid-execution and there is no test covering it.
2026-08-17 13:02:25 +12:00
1c857e47b4 #3407 Honor isolation level on read-only transactions (#3874)
Read-only TxScope previously ignored isolation when creating
ImplicitReadOnlyTransaction. Apply setIsolationLevel after
createReadOnlyTransaction so @Transactional(readOnly=true, isolation=...)
and TxScope setReadOnly+setIsolation take effect.

Co-authored-by: arimu1 <19286898+arimu1@users.noreply.github.com>
2026-08-14 18:39:12 +12:00
cc9e67c326 Add dbName() to MetaQueryPlan - easier to support multi-db query plan capture handling (#3879)
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-08-14 18:32:53 +12:00
7a7dfb96d7 Metrics - support both CUMULATIVE and DELTA metrics collection concur… (#3878)
* Metrics - support both CUMULATIVE and DELTA metrics collection concurrently

ebean insight [and StatsD] work off DELTA metrics, where as OTEL
and Prometheus want CUMULATIVE. With this change we can have a metrics
collection for ebean insight using DELTA mode and have a second collection
use CUMULATIVE for reporting to OTEL - for the case of fan-out metrics
going to 2 places.

This isn't strictly needed when only one collection mode is used.

* Metrics - Add Mode with RESET, DELTA and CUMULATIVE

Previously we were overloading RESET and DELTA but we really need
these to be 2 separate modes for the existing tests and the
get(reset) api

* Fix test TestNatKeyCacheWithForeignKey with cache stats reset

---------

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-08-14 18:32:23 +12:00
robin.bygrave 3c4e87f3f7 Docs: Update docs on OneToOne mapping with mappedBy 2026-08-06 21:53:14 +12:00
Rob Bygrave f5eee70ec4 Bump test modules to 18.4.0 2026-07-28 22:35:46 +12:00
106 changed files with 1551 additions and 329 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<artifactId>composites</artifactId>
+6 -1
View File
@@ -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.
---
+42
View File
@@ -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
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<name>ebean api</name>
@@ -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);
}
}
+5 -5
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -14,7 +14,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
@@ -41,14 +41,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
@@ -69,7 +69,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<artifactId>ebean-bench</artifactId>
+29 -29
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -125,19 +125,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-avajejsonb-mapper</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -161,37 +161,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<!-- platforms -->
@@ -199,91 +199,91 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
+7 -7
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-json</artifactId>
<version>18.4.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.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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());
}
@@ -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 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.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.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
+6 -6
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.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.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+7 -7
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<name>ebean test</name>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
@@ -240,7 +240,7 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.0.7</version>
<version>3.3.5</version>
<scope>test</scope>
</dependency>
@@ -125,6 +125,7 @@ class DtoQueryPlanCaptureTest extends BaseTestCase {
.orElse(null);
assertThat(dtoPlan).as("captured a native DTO query plan").isNotNull();
assertThat(dtoPlan.dbName()).isEqualTo(DB.getDefault().name());
assertThat(dtoPlan.sql()).contains("from o_customer where id > ?");
assertThat(dtoPlan.plan()).isNotEmpty();
}
@@ -71,6 +71,7 @@ class SqlQueryPlanCaptureTest extends BaseTestCase {
.orElse(null);
assertThat(sqlPlan).as("captured a SqlQuery query plan").isNotNull();
assertThat(sqlPlan.dbName()).isEqualTo(DB.getDefault().name());
assertThat(sqlPlan.sql()).contains("from o_customer where id > ?");
assertThat(sqlPlan.plan()).isNotEmpty();
}
@@ -0,0 +1,95 @@
package org.tests.delete;
import io.ebean.DB;
import io.ebean.Transaction;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.tests.model.deleteorder.DcoAsset;
import org.tests.model.deleteorder.DcoLinkAdapter;
import org.tests.model.deleteorder.DcoParent;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* A join entity owns the foreign key to the bean its delete cascades to, so the join row has to be
* deleted first. When a persist callback writes to the database it flushes the batch from inside the
* flush that is already running : the outer flush has taken the join rows out of their bean holder,
* so the inner flush only finds the assets and executes them first.
* <p>
* See <a href="https://github.com/ebean-orm/ebean/issues/1852">#1852</a>.
*/
class TestDeleteCascadeOrder extends BaseTestCase {
@AfterEach
void after() {
DcoLinkAdapter.reset();
}
@Test
void deleteLinkBeforeAsset() {
assertLinkDeletedBeforeAsset(deleteAllLinks(newParent(2)));
}
@Test
void deleteLinkBeforeAsset_whenCallbackWritesOnPreDelete() {
DcoLinkAdapter.writeOnPreDelete(true);
assertLinkDeletedBeforeAsset(deleteAllLinks(newParent(2)));
}
@Test
void deleteLinkBeforeAsset_whenCallbackWritesOnPostDelete() {
DcoLinkAdapter.writeOnPostDelete(true);
assertLinkDeletedBeforeAsset(deleteAllLinks(newParent(2)));
}
private Long newParent(int assetCount) {
DcoParent parent = new DcoParent("parent-" + assetCount);
for (int i = 0; i < assetCount; i++) {
parent.addAsset(new DcoAsset("asset-" + i));
}
DB.save(parent);
return parent.getId();
}
/**
* Remove every link of the parent, which cascades the delete to the assets behind them. The graph is
* fetched up front : a lazy load would flush the batch on its own and hide the ordering.
*/
private List<String> deleteAllLinks(Long parentId) {
try (Transaction txn = DB.beginTransaction()) {
txn.setBatchMode(true);
DcoParent parent = DB.find(DcoParent.class)
.fetch("links")
.fetch("links.asset")
.where().idEq(parentId)
.findOne();
parent.getLinks().clear();
LoggedSql.start();
DB.save(parent);
txn.commit();
return LoggedSql.stop();
}
}
private void assertLinkDeletedBeforeAsset(List<String> sql) {
assertThat(firstIndexOf(sql, "delete from dco_link"))
.as("the join row must be deleted before the asset it references, statements were :%n%s", String.join("\n", sql))
.isLessThan(firstIndexOf(sql, "delete from dco_asset"));
}
private int firstIndexOf(List<String> sql, String fragment) {
for (int i = 0; i < sql.size(); i++) {
if (sql.get(i).contains(fragment)) {
return i;
}
}
throw new AssertionError("no statement containing '" + fragment + "', statements were :\n" + String.join("\n", sql));
}
}
@@ -0,0 +1,50 @@
package org.tests.delete;
import io.ebean.DB;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.tests.model.deleteorder.DcoTree;
import org.tests.model.deleteorder.DcoTreeContainer;
import static org.assertj.core.api.Assertions.assertThat;
/**
* The tree shape reported on <a href="https://github.com/ebean-orm/ebean/issues/1852">#1852</a> :
* deleting the container cascades down a self referencing tree, and the deletes have to reach the
* leaves before their parents.
*/
class TestDeleteTreeCascadeOrder extends BaseTestCase {
/**
* Still reproduces on 18.4.0. The tree is deleted level by level but not deepest first :
* <pre>
* delete from dco_tree where id in (?) -- the root, whose children are still there
* delete from dco_tree where id in (?,?,?)
* delete from dco_tree where id in (?,?)
* </pre>
* which fails with "Referential integrity constraint violation: FK_DCO_TREE_PARENT_ID". Disabled so
* that it does not break the build, remove the annotation to see the failure.
*/
@Disabled("reproduces #1852, not fixed yet")
@Test
void deleteContainerOfNestedTree() {
DcoTreeContainer container = new DcoTreeContainer();
DcoTree root = new DcoTree("root");
DcoTree child1 = root.addChild("child 1");
child1.addChild("child 1a").addChild("child 1a1");
DcoTree child2 = root.addChild("child 2");
child2.addChild("child 2a");
child2.addChild("child 2b");
container.getTrees().add(root);
DB.save(container);
DB.delete(container);
assertThat(DB.find(DcoTree.class).findCount()).isZero();
assertThat(DB.find(DcoTreeContainer.class).where().idEq(container.getId()).findCount()).isZero();
}
}
@@ -0,0 +1,91 @@
package org.tests.delete;
import io.ebean.DB;
import io.ebean.Transaction;
import io.ebean.test.LoggedSql;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.tests.model.deleteorder.DcoAsset;
import org.tests.model.deleteorder.DcoParent;
import org.tests.model.deleteorder.DcoParentAdapter;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Same defect as the cascaded delete one, on the insert side : a persist done from a BeanPersistController
* flushes the batch from inside the flush that is already running, and the statements queued behind the
* one being executed are issued out of order.
* <p>
* <a href="https://github.com/ebean-orm/ebean/pull/3148">#3148</a> fixed this for a flush triggered by a
* query (BatchControl.executeNow disables flushOnQuery), but a flush triggered by a write goes through
* BatchControl.executeOrQueue, which that guard does not cover.
*/
class TestInsertCascadeOrder extends BaseTestCase {
@AfterEach
void after() {
DcoParentAdapter.writeOnPreInsert(false);
DcoParentAdapter.sqlUpdateOnPreInsert(false);
}
@Test
void insertParentBeforeItsLinks_whenCallbackWritesDuringFlush() {
DcoParentAdapter.writeOnPreInsert(true);
List<String> sql = insertParents(3);
// every parent has to be inserted before the link that points at it
assertThat(lastIndexOf(sql, "insert into dco_parent"))
.as("a parent must be inserted before the links referencing it, statements were :%n%s", String.join("\n", sql))
.isLessThan(lastIndexOf(sql, "insert into dco_link"));
}
/**
* Same as above but the callback runs a SqlUpdate, which reaches BatchControl by
* executeStatementOrBatch rather than executeOrQueue.
*/
@Test
void insertParentBeforeItsLinks_whenCallbackRunsSqlUpdateDuringFlush() {
DcoParentAdapter.sqlUpdateOnPreInsert(true);
List<String> sql = insertParents(3);
assertThat(lastIndexOf(sql, "insert into dco_parent"))
.as("a parent must be inserted before the links referencing it, statements were :%n%s", String.join("\n", sql))
.isLessThan(lastIndexOf(sql, "insert into dco_link"));
}
private List<String> insertParents(int count) {
List<DcoParent> parents = new ArrayList<>();
for (int i = 0; i < count; i++) {
DcoParent parent = new DcoParent("batch-parent-" + i);
parent.addAsset(new DcoAsset("batch-asset-" + i));
parents.add(parent);
}
try (Transaction txn = DB.beginTransaction()) {
txn.setBatchMode(true);
txn.setBatchSize(50);
LoggedSql.start();
DB.saveAll(parents);
txn.commit();
List<String> sql = LoggedSql.stop();
System.out.println("---- insert order ----");
sql.stream().filter(s -> !s.contains("-- bind")).forEach(s -> System.out.println(" " + s));
return sql;
}
}
private int lastIndexOf(List<String> sql, String fragment) {
for (int i = sql.size() - 1; i >= 0; i--) {
if (sql.get(i).contains(fragment)) {
return i;
}
}
throw new AssertionError("no statement containing '" + fragment + "', statements were :\n" + String.join("\n", sql));
}
}
@@ -30,6 +30,7 @@ public class TestNatKeyCacheWithForeignKey extends BaseTestCase {
setupData();
clearAllL2Cache();
getStats();
final OCachedAppDetail found0 = findDetail(app0, "detail0");
assertThat(found0).isNotNull();
@@ -0,0 +1,34 @@
package org.tests.model.deleteorder;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Version;
/**
* Owned by a {@link DcoLink} through a cascading OneToOne, so deleting the link deletes the asset.
*/
@Entity
public class DcoAsset {
@Id
@GeneratedValue
Long id;
@Version
Long version;
String name;
public DcoAsset(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
}
@@ -0,0 +1,30 @@
package org.tests.model.deleteorder;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
/**
* Written from the delete callback of {@link DcoLink}, the way an audit or an outbox row is.
*/
@Entity
public class DcoAudit {
@Id
@GeneratedValue
Long id;
String message;
public DcoAudit(String message) {
this.message = message;
}
public Long getId() {
return id;
}
public String getMessage() {
return message;
}
}
@@ -0,0 +1,46 @@
package org.tests.model.deleteorder;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
/**
* Join entity between {@link DcoParent} and {@link DcoAsset}. It owns the foreign key to the asset,
* so the link row has to be deleted before the asset it points at.
* <p>
* A real entity (rather than a plain join table) so that its delete fires a persistence callback,
* see {@link DcoLinkAdapter}.
*/
@Entity
public class DcoLink {
@Id
@GeneratedValue
Long id;
@ManyToOne(optional = false)
DcoParent parent;
@OneToOne(optional = false, cascade = CascadeType.ALL, orphanRemoval = true)
DcoAsset asset;
public DcoLink(DcoParent parent, DcoAsset asset) {
this.parent = parent;
this.asset = asset;
}
public Long getId() {
return id;
}
public DcoParent getParent() {
return parent;
}
public DcoAsset getAsset() {
return asset;
}
}
@@ -0,0 +1,54 @@
package org.tests.model.deleteorder;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistRequest;
/**
* Persists a bean from inside a delete callback of {@link DcoLink}, the way an audit or an outbox row
* is written. The BeanPersistController javadoc documents this as a supported use case.
* <p>
* Off by default so that the callbacks only fire for the tests that ask for them.
*/
public class DcoLinkAdapter extends BeanPersistAdapter {
private static boolean writeOnPreDelete;
private static boolean writeOnPostDelete;
public static void writeOnPreDelete(boolean enabled) {
writeOnPreDelete = enabled;
}
public static void writeOnPostDelete(boolean enabled) {
writeOnPostDelete = enabled;
}
public static void reset() {
writeOnPreDelete = false;
writeOnPostDelete = false;
}
@Override
public boolean isRegisterFor(Class<?> cls) {
return DcoLink.class.equals(cls);
}
@Override
public boolean preDelete(BeanPersistRequest<?> request) {
if (writeOnPreDelete) {
audit(request, "pre");
}
return true;
}
@Override
public void postDelete(BeanPersistRequest<?> request) {
if (writeOnPostDelete) {
audit(request, "post");
}
}
private void audit(BeanPersistRequest<?> request, String phase) {
DcoLink link = (DcoLink) request.bean();
request.database().save(new DcoAudit(phase + " delete of link " + link.getId()), request.transaction());
}
}
@@ -0,0 +1,50 @@
package org.tests.model.deleteorder;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
/**
* Parent of a join entity, see {@link DcoLink}.
*/
@Entity
public class DcoParent {
@Id
@GeneratedValue
Long id;
String name;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent", orphanRemoval = true)
List<DcoLink> links = new ArrayList<>();
public DcoParent(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<DcoLink> getLinks() {
return links;
}
public void addAsset(DcoAsset asset) {
links.add(new DcoLink(this, asset));
}
}
@@ -0,0 +1,49 @@
package org.tests.model.deleteorder;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistRequest;
/**
* Persists a bean from inside the insert callback of {@link DcoParent}, the way an audit or an outbox
* row is written. The BeanPersistController javadoc documents this as a supported use case.
* <p>
* Off by default so that the callback only fires for the tests that ask for it.
*/
public class DcoParentAdapter extends BeanPersistAdapter {
private static boolean writeOnPreInsert;
private static boolean sqlUpdateOnPreInsert;
public static void writeOnPreInsert(boolean enabled) {
writeOnPreInsert = enabled;
}
/**
* Same as {@link #writeOnPreInsert(boolean)} but through SqlUpdate, which takes the
* BatchControl.executeStatementOrBatch path rather than executeOrQueue.
*/
public static void sqlUpdateOnPreInsert(boolean enabled) {
sqlUpdateOnPreInsert = enabled;
}
@Override
public boolean isRegisterFor(Class<?> cls) {
return DcoParent.class.equals(cls);
}
@Override
public boolean preInsert(BeanPersistRequest<?> request) {
DcoParent parent = (DcoParent) request.bean();
if (writeOnPreInsert) {
request.database().save(new DcoAudit("inserting " + parent.getName()), request.transaction());
}
if (sqlUpdateOnPreInsert) {
request.database().sqlUpdate("update dco_audit set message = ? where id = ?")
.setParameter(1, "inserting " + parent.getName())
.setParameter(2, -1L)
.usingTransaction(request.transaction())
.execute();
}
return true;
}
}
@@ -0,0 +1,52 @@
package org.tests.model.deleteorder;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
/**
* Self referencing tree, as reported on #1852.
*/
@Entity
public class DcoTree {
@Id
@GeneratedValue
Long id;
String name;
@ManyToOne
DcoTree parent;
@ManyToOne
DcoTreeContainer container;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
List<DcoTree> children = new ArrayList<>();
public DcoTree(String name) {
this.name = name;
}
public Long getId() {
return id;
}
public List<DcoTree> getChildren() {
return children;
}
public DcoTree addChild(String name) {
DcoTree child = new DcoTree(name);
child.parent = this;
children.add(child);
return child;
}
}
@@ -0,0 +1,32 @@
package org.tests.model.deleteorder;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
/**
* Holds the roots of a {@link DcoTree}, as reported on #1852.
*/
@Entity
public class DcoTreeContainer {
@Id
@GeneratedValue
Long id;
@OneToMany(cascade = CascadeType.ALL)
List<DcoTree> trees = new ArrayList<>();
public Long getId() {
return id;
}
public List<DcoTree> getTrees() {
return trees;
}
}
@@ -191,6 +191,7 @@ public class TestCustomerFinder extends BaseTestCase {
request.maxTimeMillis(10_000);
List<MetaQueryPlan> plans0 = server().metaInfo().queryPlanCollectNow(request);
assertThat(plans0).isNotEmpty();
assertThat(plans0).extracting(MetaQueryPlan::dbName).containsOnly(server().name());
for (MetaQueryPlan plan : plans0) {
logger.info("queryPlan label:{}, queryTimeMicros:{} captureMicros:{} whenCaptured:{} captureCount:{} loc:{} sql:{} bind:{} plan:{}",
@@ -1,12 +1,17 @@
package org.tests.transaction;
import io.ebean.xtest.BaseTestCase;
import io.ebean.DB;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.annotation.Transactional;
import io.ebean.annotation.TxIsolation;
import io.ebean.meta.MetaTimedMetric;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
import org.tests.model.basic.Customer;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
@@ -38,6 +43,33 @@ public class TestTransactionalReadOnly extends BaseTestCase {
assertThat(metric(timedMetrics, "txn.readonly")).isEmpty();
}
/**
* #3407 read-only TxScope must honor isolation (previously dropped for createReadOnlyTransaction).
*/
@Test
public void test_readonly_honors_isolation() throws SQLException {
TxScope scope = TxScope.required()
.setReadOnly(true)
.setIsolation(TxIsolation.SERIALIZABLE);
DB.execute(scope, () -> {
Transaction txn = DB.currentTransaction();
assertThat(txn).isNotNull();
try {
assertThat(txn.connection().getTransactionIsolation())
.isEqualTo(Connection.TRANSACTION_SERIALIZABLE);
} catch (SQLException e) {
throw new RuntimeException(e);
}
DB.find(Customer.class).findCount();
});
}
@Test
public void test_readonly_annotation_honors_isolation() throws SQLException {
executeTransactionalReadOnlyWithIsolation();
}
private Optional<MetaTimedMetric> metric(List<MetaTimedMetric> timedMetrics, String name) {
return timedMetrics.stream()
.filter(metaTimedMetric -> metaTimedMetric.name().equals(name))
@@ -53,4 +85,13 @@ public class TestTransactionalReadOnly extends BaseTestCase {
private void executeTransactionalUsingMainDataSource() {
DB.find(Customer.class).findCount();
}
@Transactional(readOnly = true, isolation = TxIsolation.SERIALIZABLE)
private void executeTransactionalReadOnlyWithIsolation() throws SQLException {
Transaction txn = DB.currentTransaction();
assertThat(txn).isNotNull();
assertThat(txn.connection().getTransactionIsolation())
.isEqualTo(Connection.TRANSACTION_SERIALIZABLE);
DB.find(Customer.class).findCount();
}
}
+2 -2
View File
@@ -15,8 +15,8 @@ mvn -T 4 clean package
mvn -T 4 deploy -pl '!composites,!platforms' -Pcentral -DskipTests
## git commit, git tag, git push --tags
git commit -am 'Version 18.4.0'
git tag 18.4.0
git commit -am 'Version 18.5.0'
git tag 18.5.0
git push --tags
### convert to javax
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<name>kotlin querybean generator</name>
@@ -21,7 +21,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
@@ -35,7 +35,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
@@ -56,14 +56,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
+14 -14
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,67 +16,67 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hsqldb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlanywhere</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<!-- Provided scope so that the H2HistoryTrigger can live in Ebean core
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,13 +16,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<artifactId>platforms</artifactId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
<dependency>
@@ -0,0 +1,11 @@
[
{"name": "org.postgis.DriverWrapperLW", "allDeclaredConstructors": true},
{"name": "org.postgis.DriverWrapper", "allDeclaredConstructors": true},
{"name": "org.postgis.DriverWrapper$TypesAdder80", "allDeclaredConstructors": true},
{"name": "org.postgis.PGbox2d", "allDeclaredConstructors": true},
{"name": "org.postgis.PGbox3d", "allDeclaredConstructors": true},
{"name": "org.postgis.PGgeography", "allDeclaredConstructors": true},
{"name": "org.postgis.PGgeographyLW", "allDeclaredConstructors": true},
{"name": "org.postgis.PGgeometry", "allDeclaredConstructors": true},
{"name": "org.postgis.PGgeometryLW", "allDeclaredConstructors": true}
]
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
</dependency>
</dependencies>
+4 -4
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<packaging>pom</packaging>
<name>ebean parent</name>
@@ -51,9 +51,9 @@
<ebean-migration-auto.version>1.2</ebean-migration-auto.version>
<ebean-migration.version>14.4.0</ebean-migration.version>
<ebean-test-containers.version>8.2</ebean-test-containers.version>
<ebean-datasource.version>10.10</ebean-datasource.version>
<ebean-agent.version>18.3.0</ebean-agent.version>
<ebean-maven-plugin.version>18.3.0</ebean-maven-plugin.version>
<ebean-datasource.version>10.12</ebean-datasource.version>
<ebean-agent.version>18.5.0</ebean-agent.version>
<ebean-maven-plugin.version>18.5.0</ebean-maven-plugin.version>
<surefire.useModulePath>false</surefire.useModulePath>
</properties>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>18.4.0</version>
<version>18.5.0</version>
</parent>
<name>querybean generator</name>
@@ -45,7 +45,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>18.4.0</version>
<version>18.5.0</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -234,9 +234,11 @@ class DtoMapperWriter {
Set<String> nestedAssocPaths = new LinkedHashSet<>();
for (DtoPropertyMeta property : activeProperties) {
if ((property.kind() == DtoPropertyMeta.Kind.NESTED_ONE || property.kind() == DtoPropertyMeta.Kind.NESTED_MANY)
&& !property.isUnfetchable()
&& !property.hasComputedSegment()) {
nestedAssocPaths.add(property.sourcePropertyPath().get(0));
} else if (property.kind() == DtoPropertyMeta.Kind.SCALAR && property.isListTarget()
&& !property.isScalarCollection()
&& !property.hasComputedSegment() && property.sourcePropertyPath().size() == 1) {
// a single-segment SCALAR property whose DTO field is a List with no registered nested
// DTO mapping of its own (e.g. @DtoConvert reducing a ToMany association) - still fully
@@ -253,7 +255,7 @@ class DtoMapperWriter {
switch (property.kind()) {
case NESTED_ONE:
case NESTED_MANY:
if (property.hasComputedSegment()) {
if (property.hasComputedSegment() || property.isUnfetchable()) {
// a single-hop @DtoPath rename traversing a computed/derived getter (no backing
// field) that happens to target a nested DTO type - just as unfetchable via
// fetch(path, mapper.fetchGroup()) as the analogous SCALAR case, since "path" here
@@ -267,7 +269,7 @@ class DtoMapperWriter {
property.sourcePropertyPath().get(0), mapperFieldName(property)));
break;
case SCALAR:
if (property.hasComputedSegment()) {
if (property.hasComputedSegment() || property.isUnfetchable()) {
// the path traverses a computed/derived getter (no backing field) - its own segments
// past that point aren't real Ebean fetch paths, so don't add them to pathSelect/
// rootSelect at all; @DtoPath#requires() (plus the real prefix, if any) already names
@@ -277,7 +279,7 @@ class DtoMapperWriter {
}
List<String> path = property.sourcePropertyPath();
if (path.size() == 1) {
if (property.isListTarget()) {
if (property.isListTarget() && !property.isScalarCollection()) {
// a single-segment path whose DTO field type is a List, but with no registered
// nested DTO mapping of its own (e.g. a @DtoConvert-backed property reducing a
// ToMany association to a simpler element type) - the source side is still a real
@@ -819,32 +819,37 @@ class DtoMappingReader {
// straight into a NullPointerException. Default to the primitive's zero-equivalent value,
// or fail fast with a clear message instead when @DtoPath(failOnNull = true).
boolean isListTarget = listElementType(field.asType()) != null;
boolean scalarCollection = isListTarget && isScalarCollectionProperty(lastOwnerType, properties.get(properties.size() - 1));
DtoConverterMeta pathConverter = isListTarget ? converter
: autoTypeConverter(converter, lastOwnerType != null ? getterReturnTypeMirror(lastOwnerType, lastGetter) : null, field.asType());
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.SCALAR, getters, properties, null, pathConverter,
field.asType().getKind().isPrimitive(), pathPrism.failOnNull(), computedFrom >= 0, requiredFetchPaths,
isListTarget, false);
isListTarget, scalarCollection, false);
}
TypeMirror fieldType = field.asType();
TypeMirror listElementType = listElementType(fieldType);
boolean unfetchable = isUnfetchableProperty(meta.source(), name);
if (listElementType != null) {
DtoBeanMeta nested = lookupByTarget(listElementType);
if (nested != null) {
rejectConverterOnNested(field, converter, name, meta);
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.NESTED_MANY, List.of(getterName(meta.source(), name)), List.of(name), nested);
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.NESTED_MANY, List.of(getterName(meta.source(), name)), List.of(name), nested,
unfetchable, List.of());
}
} else {
DtoBeanMeta nested = lookupByTarget(fieldType);
if (nested != null) {
rejectConverterOnNested(field, converter, name, meta);
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.NESTED_ONE, List.of(getterName(meta.source(), name)), List.of(name), nested);
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.NESTED_ONE, List.of(getterName(meta.source(), name)), List.of(name), nested,
unfetchable, List.of());
}
}
String getter = getterName(meta.source(), name);
boolean scalarCollection = listElementType != null && isScalarCollectionProperty(meta.source(), name);
DtoConverterMeta scalarConverter = listElementType != null ? converter
: autoTypeConverter(converter, getterReturnTypeMirror(meta.source(), getter), fieldType);
return new DtoPropertyMeta(name, DtoPropertyMeta.Kind.SCALAR, List.of(getter), List.of(name), null, scalarConverter,
fieldType.getKind().isPrimitive(), listElementType != null);
fieldType.getKind().isPrimitive(), false, false, List.of(), listElementType != null, scalarCollection, unfetchable, false);
}
/**
@@ -1055,7 +1060,35 @@ class DtoMappingReader {
for (TypeElement current = type; current != null; current = superclassOf(current)) {
for (VariableElement f : ElementFilter.fieldsIn(current.getEnclosedElements())) {
if (f.getSimpleName().contentEquals(propertyName)) {
return true;
return !ctx.isTransientField(f);
}
}
}
return false;
}
private boolean isScalarCollectionProperty(TypeElement type, String propertyName) {
if (type == null) {
return false;
}
for (TypeElement current = type; current != null; current = superclassOf(current)) {
for (VariableElement field : ElementFilter.fieldsIn(current.getEnclosedElements())) {
if (field.getSimpleName().contentEquals(propertyName)) {
return ctx.isScalarCollectionField(field);
}
}
}
return false;
}
private boolean isUnfetchableProperty(TypeElement type, String propertyName) {
if (type == null) {
return false;
}
for (TypeElement current = type; current != null; current = superclassOf(current)) {
for (VariableElement field : ElementFilter.fieldsIn(current.getEnclosedElements())) {
if (field.getSimpleName().contentEquals(propertyName)) {
return ctx.isTransientField(field);
}
}
}
@@ -30,6 +30,8 @@ class DtoPropertyMeta {
private final boolean computedSegment;
private final List<String> requiredFetchPaths;
private final boolean listTarget;
private final boolean scalarCollection;
private final boolean unfetchable;
private final boolean ignored;
DtoPropertyMeta(String dtoFieldName, Kind kind, List<String> sourceGetterPath, List<String> sourcePropertyPath, DtoBeanMeta nested) {
@@ -44,7 +46,7 @@ class DtoPropertyMeta {
* before ever consulting them.
*/
static DtoPropertyMeta ignored(String dtoFieldName, boolean listTarget) {
return new DtoPropertyMeta(dtoFieldName, Kind.SCALAR, List.of(), List.of(), null, null, false, false, false, List.of(), listTarget, true);
return new DtoPropertyMeta(dtoFieldName, Kind.SCALAR, List.of(), List.of(), null, null, false, false, false, List.of(), listTarget, false, false, true);
}
/**
@@ -90,6 +92,22 @@ class DtoPropertyMeta {
DtoPropertyMeta(String dtoFieldName, Kind kind, List<String> sourceGetterPath, List<String> sourcePropertyPath,
DtoBeanMeta nested, DtoConverterMeta converter, boolean primitiveTarget, boolean failOnNull,
boolean computedSegment, List<String> requiredFetchPaths, boolean listTarget, boolean ignored) {
this(dtoFieldName, kind, sourceGetterPath, sourcePropertyPath, nested, converter, primitiveTarget, failOnNull,
computedSegment, requiredFetchPaths, listTarget, false, ignored);
}
DtoPropertyMeta(String dtoFieldName, Kind kind, List<String> sourceGetterPath, List<String> sourcePropertyPath,
DtoBeanMeta nested, DtoConverterMeta converter, boolean primitiveTarget, boolean failOnNull,
boolean computedSegment, List<String> requiredFetchPaths, boolean listTarget,
boolean scalarCollection, boolean ignored) {
this(dtoFieldName, kind, sourceGetterPath, sourcePropertyPath, nested, converter, primitiveTarget, failOnNull,
computedSegment, requiredFetchPaths, listTarget, scalarCollection, false, ignored);
}
DtoPropertyMeta(String dtoFieldName, Kind kind, List<String> sourceGetterPath, List<String> sourcePropertyPath,
DtoBeanMeta nested, DtoConverterMeta converter, boolean primitiveTarget, boolean failOnNull,
boolean computedSegment, List<String> requiredFetchPaths, boolean listTarget,
boolean scalarCollection, boolean unfetchable, boolean ignored) {
this.dtoFieldName = dtoFieldName;
this.kind = kind;
this.sourceGetterPath = sourceGetterPath;
@@ -101,6 +119,8 @@ class DtoPropertyMeta {
this.computedSegment = computedSegment;
this.requiredFetchPaths = requiredFetchPaths;
this.listTarget = listTarget;
this.scalarCollection = scalarCollection;
this.unfetchable = unfetchable;
this.ignored = ignored;
}
@@ -177,6 +197,18 @@ class DtoPropertyMeta {
return listTarget || kind == Kind.NESTED_MANY;
}
/**
* Return true when the source property is a scalar collection such as an Ebean {@code @DbArray},
* rather than a to-many association.
*/
boolean isScalarCollection() {
return scalarCollection;
}
boolean isUnfetchable() {
return unfetchable;
}
/**
* {@code true} when this property is marked {@code @DtoIgnore} - permanently excluded from
* every mapping (base and every named variant alike), always given its empty default rather
@@ -245,4 +277,3 @@ class DtoPropertyMeta {
sb.append(')');
}
}
@@ -223,6 +223,15 @@ class ProcessingContext implements Constants {
);
}
boolean isTransientField(Element field) {
if (field.getKind() != ElementKind.FIELD) {
return false;
}
VariableElement variable = (VariableElement) field;
return variable.getModifiers().contains(Modifier.TRANSIENT)
|| hasAnnotations(variable, "jakarta.persistence.Transient");
}
private static boolean hasAnnotations(Element element, String... annotations) {
return getAnnotation(element, annotations) != null;
}
@@ -268,17 +277,21 @@ class ProcessingContext implements Constants {
/**
* Return true if it is a DbJson field.
*/
private static boolean dbJsonField(Element field) {
boolean isDbJsonField(Element field) {
return hasAnnotations(field, DBJSON, DBJSONB);
}
/**
* Return true if it is a DbArray field.
*/
private static boolean dbArrayField(Element field) {
boolean isDbArrayField(Element field) {
return hasAnnotations(field, DBARRAY);
}
boolean isScalarCollectionField(Element field) {
return isDbArrayField(field) || isDbJsonField(field);
}
private static boolean dbToMany(Element field) {
return hasAnnotations(field, ONE_TO_MANY, MANY_TO_MANY);
}
@@ -417,10 +430,10 @@ class ProcessingContext implements Constants {
}
boolean toMany = dbToMany(field);
if (dbJsonField(field)) {
if (isDbJsonField(field)) {
return propertyTypeMap.getDbJsonType();
}
if (dbArrayField(field)) {
if (isDbArrayField(field)) {
// get generic parameter type
DeclaredType declaredType = (DeclaredType) field.asType();
TypeMirror arrayElementType = declaredType.getTypeArguments().get(0);

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