Compare commits

...
28 Commits
Author SHA1 Message Date
Rob Bygrave 032f4de857 Version 16.8.0 2026-06-06 20:06:30 +12:00
Rob Bygrave 59431814ce Merge branch 'master' of github.com:ebean-orm/ebean 2026-06-06 19:57:18 +12:00
Rob BygraveandGitHub e7194055be Merge pull request #3776 from ebean-orm/feature/PString_eqIfNotBlank
Add PString eqIfNotBlank() helper query expression
2026-06-06 18:37:59 +12:00
robin.bygrave 806c7cd752 Fix test with overlapping cases 2026-06-06 18:16:11 +12:00
Rob BygraveandGitHub dfc7f92160 Merge pull request #3777 from ebean-orm/fature/otel-query-hash
[open telemetry] Add query hash as an attribute to the query spans
2026-06-06 18:04:18 +12:00
robin.bygrave 26351325a8 Fix test with overlapping cases 2026-06-06 18:03:39 +12:00
robin.bygrave 2d37f9d01e [open telemetry] Add query hash as an attribute to the query spans 2026-06-06 17:55:59 +12:00
robin.bygrave d3fd03ce5b Add PString eqIfNotBlank() helper query expression
Just to make this relatively common case pretty nice and clean
2026-06-06 17:45:45 +12:00
Rob Bygrave a8e92791cd Version 16.7.0 2026-06-03 07:44:59 +12:00
Rob BygraveandGitHub 94e63cc30f Merge pull request #3774 from ebean-orm/feature/timedMetric-max-reset
Add collectMetrics(reset) option, with change to query time metric MA…
2026-06-02 14:48:15 +12:00
Rob BygraveandGitHub 3567263250 Merge pull request #3775 from ebean-orm/feature/fix-error-profiling-batch
open telemetry: Fix for IndexOutOfBoundsException with Batched PreparedStatements and open telemetry
2026-06-02 14:46:54 +12:00
robin.bygrave bc03f8d516 open telemetry: Fix for IndexOutOfBoundsException with Batched PreparedStatements and open telemetry
Caused by: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
 at jdk.internal.util.Preconditions.outOfBounds(Unknown Source)
 at jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Unknown Source)
 at jdk.internal.util.Preconditions.checkIndex(Unknown Source)
 at java.util.Objects.checkIndex(Unknown Source)
 at java.util.ArrayList.get(Unknown Source)
 at io.ebeaninternal.server.persist.BatchedPstmt.profile(BatchedPstmt.java:132)
2026-06-02 14:42:44 +12:00
robin.bygrave 7035ff20eb Docs: Add a doc guide for adding open telemetry 2026-05-29 17:11:04 +12:00
Rob BygraveandGitHub f38479022a Merge pull request #3773 from ebean-orm/dependabot/maven/ebean-opentelemetry/io.opentelemetry-opentelemetry-api-1.62.0
Build(deps): Bump io.opentelemetry:opentelemetry-api from 1.51.0 to 1.62.0 in /ebean-opentelemetry
2026-05-21 22:12:11 +12:00
robin.bygrave 986cc905d8 Fix test only - Resource test entity table name invalid for Oracle 2026-05-21 22:10:58 +12:00
robin.bygrave 8f3fe688cb Fix test only - ResourceEntityTest SQL for Postgres uses ANY rather than IN 2026-05-21 22:05:59 +12:00
robin.bygrave eee26d9ed3 Add collectMetrics(reset) option, with change to query time metric MAX value to reset
So the desire here is to better support sending metrics to Prometheus style metrics collectors that prefer CUMULATIVE metrics rather than DELTA based metrics.

To do this, expose additional MetaInfoManager.collectMetrics(reset) method.

In supporting this, the MAX value really does need to reset even with CUMULATIVE metrics and act more like a gauge as otherwise it becomes almost useless as the max value over the lifetime. So we need to adjust MAX to always reset and act like a gauge to be useful in this CUMULATIVE metrics reporting mode.
2026-05-21 22:03:15 +12:00
dependabot[bot]andGitHub 44bd60e586 Build(deps): Bump io.opentelemetry:opentelemetry-api
Bumps [io.opentelemetry:opentelemetry-api](https://github.com/open-telemetry/opentelemetry-java) from 1.51.0 to 1.62.0.
- [Release notes](https://github.com/open-telemetry/opentelemetry-java/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-java/compare/v1.51.0...v1.62.0)

---
updated-dependencies:
- dependency-name: io.opentelemetry:opentelemetry-api
  dependency-version: 1.62.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 16:44:36 +00:00
Rob BygraveandGitHub 20d7af6d25 Merge pull request #3772 from ebean-orm/feature/otel-isRecording
open telemetry: Use Span.current().isRecording() rather than isValid()
2026-05-14 16:22:57 +12:00
robin.bygrave e5aeda9d4e open telemetry: Use Span.current().isRecording() rather than isValid()
Change to use isRecording() to determine if ProfileStream should be
created for collecting profiling events
2026-05-14 16:20:28 +12:00
Sergey KuznetsovandGitHub 46bb1ca060 Add timeout support to UpdateQuery and DefaultUpdateQuery (#3771) 2026-05-14 15:25:07 +12:00
robin.bygrave 0652167101 docs: add next steps to test container guide
- Suggest TestEntityBuilder for test data creation
- Link to testing-with-testentitybuilder.md
2026-05-13 13:57:40 +12:00
robin.bygrave 6633293151 docs: update guide versions and fix defaults
- ebean.version 17.2.0 -> 17.5.0 (minimum for TestEntityBuilder)
- avaje-inject 11.5 -> 12.5
- Add io.avaje:junit:1.8 bundle to test dependencies
- Remove .skipDataSourceCheck(true) from database config example
- Add note about Configuration DI wiring in Step 3
2026-05-13 13:57:39 +12:00
Rob BygraveandGitHub 9e603f2848 Deprecate DatabaseConfig and DatabaseFactory, prefer Database.builder() (#3769)
* Deprecate DatabaseConfig and DatabaseFactory, prefer Database.builder()

Deprecate the DatabaseConfig way of creating Database instance.
Migrate to use Database.builder().

* Deprecate DatabaseConfig and DatabaseFactory, migrate test code

* Use @Deprecated(forRemoval = true) on DatabaseConfig and DatabaseFactory

* Tidy up Database javadoc for deprecation
2026-05-08 00:06:43 +12:00
Rob Bygrave fff345ffc8 Merge branch 'master' of github.com:ebean-orm/ebean 2026-05-07 22:13:16 +12:00
Rob BygraveandGitHub dd1fe845aa Remove the DefaultProfileHandler & DefaultProfileStream (Replaced by ebean-opentelemetry) (#3767)
So migrate to use opentelemetry, and remove these internal profilers as
they are never expected to be used now.
2026-05-07 22:10:08 +12:00
Rob BygraveandGitHub 80451c3c62 Performance: Improve OrmQueryProperties, include } into immutableHashSuffix (#3768) 2026-05-07 21:42:20 +12:00
Rob Bygrave 2d199737aa Docs: Add docs/notes on potential future L2 immutable bean cache 2026-05-07 21:36:45 +12:00
136 changed files with 1626 additions and 1185 deletions
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-clickhouse</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-db2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-hana</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mariadb</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-mysql</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-net-postgis-types</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-nuodb</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-oracle</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -47,19 +47,19 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlite</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -42,13 +42,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
<relativePath>../..</relativePath>
</parent>
@@ -17,13 +17,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -41,7 +41,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -60,13 +60,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>composites</artifactId>
+1
View File
@@ -11,6 +11,7 @@ Step-by-step guides for common Ebean tasks (Maven setup, database config, query
Key guides (fetch and follow when performing the relevant task):
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-maven-pom.md
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-database-config.md
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-test-container.md
+15
View File
@@ -18,6 +18,18 @@ existing Maven project. Complete the steps in order.
| 2 | [Test container setup](add-ebean-postgres-test-container.md) | Start a PostgreSQL (or PostGIS) Docker container for tests using `@TestScope @Factory` with Avaje Inject; verify the test database works with `mvn verify` before adding production configuration |
| 3 | [Database configuration](add-ebean-postgres-database-config.md) | Configure the production Ebean `Database` bean using `DataSourceBuilder` and `DatabaseBuilder` with Avaje Inject |
## Migration & upgrades
| Guide | Description |
|-------|-------------|
| [Migrate to `Database.builder()`](migrating-to-database-builder.md) | Replace legacy `new DatabaseConfig()` and `DatabaseFactory.create(...)` code with `Database.builder()` and `DatabaseBuilder.build()`. Includes common rewrites, fluent builder equivalents, and manual-review cases for semi-automated upgrades |
## Observability
| Guide | Description |
|-------|-------------|
| [Ebean OpenTelemetry tracing](add-ebean-opentelemetry.md) | Add `ebean-opentelemetry`, register `GlobalOpenTelemetry` once before Ebean databases are built, and troubleshoot missing spans or double-registration errors |
## Entity beans
| Guide | Description |
@@ -133,8 +145,10 @@ tasks are at: https://github.com/ebean-orm/ebean/tree/HEAD/docs/guides/
Key guides (fetch and follow these when performing the relevant task):
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-maven-pom.md
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-database-config.md
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
- Immutable bean cache for read-only references: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/immutable-bean-cache.md
- Ebean OpenTelemetry tracing: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-opentelemetry.md
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-test-container.md
- DB migration generation: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-db-migration-generation.md
@@ -158,6 +172,7 @@ tasks are at: https://github.com/ebean-orm/ebean/tree/HEAD/docs/guides/
Key guides (fetch and follow these when performing the relevant task):
- Maven POM setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-maven-pom.md
- Database configuration: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-database-config.md
- Migrate to `Database.builder()`: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/migrating-to-database-builder.md
- Write queries with query beans: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/writing-ebean-query-beans.md
- Persisting and transactions: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/persisting-and-transactions-with-ebean.md
- Test container setup: https://raw.githubusercontent.com/ebean-orm/ebean/HEAD/docs/guides/add-ebean-postgres-test-container.md
+158
View File
@@ -0,0 +1,158 @@
# Guide: Add Ebean OpenTelemetry tracing
## Purpose
This guide explains how to enable Ebean transaction tracing with OpenTelemetry and,
most importantly, how to order startup so Ebean sees the intended global
OpenTelemetry instance.
Use this guide when adding `ebean-opentelemetry`, diagnosing missing Ebean spans,
or fixing `GlobalOpenTelemetry` double-registration errors.
---
## Overview
`ebean-opentelemetry` provides an Ebean profiling handler that creates transaction
spans as children of the current active OpenTelemetry span. It does not create
top-level request, job, or Lambda invocation spans by itself.
The handler resolves its tracer from `GlobalOpenTelemetry` when the Ebean
`Database` is configured. For that reason, the application must build and register
the OpenTelemetry SDK before any Ebean `Database` beans are created.
Rules of thumb:
- Register the global OpenTelemetry instance once.
- Register it before building Ebean databases.
- Model that ordering as a real DI dependency.
- Do not call `GlobalOpenTelemetry.set(...)` or `buildAndRegisterGlobal()` in
multiple places.
---
## Step 1 - Add the dependency
```xml
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-opentelemetry</artifactId>
<version>${ebean.version}</version>
</dependency>
```
The module registers the Ebean OpenTelemetry profile handler via `ServiceLoader`.
No manual Ebean plugin registration is normally required.
---
## Step 2 - Build OpenTelemetry before Ebean databases
Create one application-owned OpenTelemetry bean. For example, when using
`avaje-metrics-otel`:
```java
import io.avaje.config.Configuration;
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.avaje.metrics.otel.MetricsOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
import java.time.Duration;
@Factory
class OpenTelemetryConfig {
@Bean
OpenTelemetry openTelemetry(Configuration config) {
return MetricsOpenTelemetry.builder()
.endpoint(config.get("otel.endpoint"))
.serviceName(config.get("otel.serviceName", "orders"))
.deploymentEnvironmentName(config.get("app.env", "local"))
.meterInterval(Duration.ofSeconds(30))
.traceInterval(Duration.ofSeconds(30))
.buildAndRegisterGlobal();
}
}
```
If you build the SDK directly, use the same principle: create the SDK once and
register that instance globally before any Ebean databases are built.
---
## Step 3 - Make database beans depend on OpenTelemetry
In DI code, make the `Database` bean method accept `OpenTelemetry`. This parameter
is intentionally present to make startup order deterministic: OpenTelemetry is
created and registered before Ebean configures the database and profile handler.
```java
import io.avaje.config.Configuration;
import io.avaje.inject.Bean;
import io.avaje.inject.Factory;
import io.ebean.Database;
import io.ebean.datasource.DataSourceBuilder;
import io.opentelemetry.api.OpenTelemetry;
@Factory
class DatabaseConfig {
@Bean
Database database(OpenTelemetry openTelemetry, Configuration config) {
var dataSource = DataSourceBuilder.create()
.url(config.get("db.url"))
.username(config.get("db.username"))
.password(config.get("db.password"));
return Database.builder()
.name("db")
.dataSourceBuilder(dataSource)
.build();
}
}
```
For Spring, use the same dependency shape: either inject `OpenTelemetry` into the
database `@Bean` method or use `@DependsOn` to ensure the OpenTelemetry bean is
initialized first.
Do not invert the dependency by making OpenTelemetry depend on the Ebean
`Database`. That creates a startup cycle and can still initialize Ebean before the
global OpenTelemetry instance is ready.
---
## Step 4 - Create a parent span at the application boundary
Ebean transaction spans are child spans. They are only created when a recording
OpenTelemetry span is active on the current thread.
Use HTTP server instrumentation, Lambda instrumentation, or an application-level
root span around the top-level request/job boundary. Ebean will then attach
transaction spans beneath that current span.
---
## Troubleshooting
### `GlobalOpenTelemetry.set has already been called`
This usually means more than one component is trying to register a global SDK, or
some startup path touched the global before the application registered its SDK.
Fixes:
1. Keep exactly one `buildAndRegisterGlobal()` / `GlobalOpenTelemetry.set(...)`
call in the application.
2. Build that OpenTelemetry bean before Ebean `Database` beans.
3. Remove duplicate OTEL setup from tests, helper factories, or secondary modules.
### No Ebean spans appear
Check:
1. `ebean-opentelemetry` is on the runtime classpath.
2. OpenTelemetry is registered before Ebean databases are built.
3. There is a current recording parent span when Ebean transactions run.
4. Sampling is not dropping the parent trace.
@@ -1,4 +1,4 @@
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 2: Database Configuration
# Guide: Add Ebean ORM (PostgreSQL) to an Existing Maven Project — Step 3: Database Configuration
## Purpose
@@ -148,7 +148,6 @@ Database database(Configuration config) {
return Database.builder()
.name("db")
.dataSourceBuilder(dataSource)
.skipDataSourceCheck(true)
.build();
}
```
@@ -156,6 +155,11 @@ Database database(Configuration config) {
If the project has a dedicated config-wrapper class (a `@Component` that reads config
keys), accept it as a parameter instead of `Configuration`.
> **Note:** Injecting `Configuration` requires that `avaje-config` is properly wired
> into the DI context. If you encounter "No dependency provided for
> io.avaje.config.Configuration" errors, use `Config.get(...)` static access instead
> (as shown in Step 2).
---
## Step 4 (Optional) — Add a read-only datasource
+13 -4
View File
@@ -85,7 +85,7 @@ already exist:
```xml
<properties>
<!-- add this line; use latest stable from https://github.com/ebean-orm/ebean/releases -->
<ebean.version>17.2.0</ebean.version>
<ebean.version>17.5.0</ebean.version>
</properties>
```
@@ -141,8 +141,17 @@ for Postgres test instances:
<version>${ebean.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>junit</artifactId>
<version>1.8</version>
<scope>test</scope>
</dependency>
```
The `io.avaje:junit` bundle includes JUnit Jupiter (API + engine) and AssertJ,
avoiding the need to declare those dependencies separately.
---
## Step 4b — Add DI framework dependencies (if applicable)
@@ -156,12 +165,12 @@ annotation processor. Skip this step if the user chose Spring or no DI.
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>11.5</version>
<version>12.5</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-test</artifactId>
<version>11.5</version>
<version>12.5</version>
<scope>test</scope>
</dependency>
```
@@ -185,7 +194,7 @@ the final `<annotationProcessorPaths>` block should include both:
<path> <!-- generate avaje-inject DI code -->
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>11.5</version>
<version>12.5</version>
</path>
</annotationProcessorPaths>
```
@@ -433,3 +433,13 @@ mkdir -p ~/.ebean && touch ~/.ebean/ignore-docker-shutdown
```
On CI servers, omit this file so containers are cleaned up after each build.
---
## Next Steps
- **Add `TestEntityBuilder`** to your test configuration for rapid test data creation
with auto-populated random values. See `testing-with-testentitybuilder.md`.
- **Proceed to Step 3** — production database configuration
(`add-ebean-postgres-database-config.md`). Verify this step passes with
`mvn verify` before continuing.
@@ -0,0 +1,242 @@
# Guide: Migrate from `DatabaseConfig` / `DatabaseFactory` to `Database.builder()`
## Purpose
This guide shows how to migrate legacy programmatic database creation code from:
- `new DatabaseConfig()`
- `DatabaseFactory.create(...)`
- old `setXxx(...)` builder-style configuration methods
…to the preferred builder-based style using:
- `Database.builder()`
- fluent `DatabaseBuilder` methods such as `name(...)`, `register(...)`, and `defaultDatabase(...)`
- `DatabaseBuilder.build()`
Use this guide when upgrading older Ebean setup code or when building an automated/semi-automated migration.
---
## Preferred pattern
Prefer code shaped like this:
```java
Database database = Database.builder()
.name("db")
.loadFromProperties()
.dataSourceBuilder(dataSource)
.register(true)
.defaultDatabase(true)
.build();
```
The important points are:
1. Start with `Database.builder()`
2. Configure via `DatabaseBuilder`
3. Finish with `.build()`
---
## Step 1 — Replace `new DatabaseConfig()` with `Database.builder()`
### Before
```java
DatabaseConfig config = new DatabaseConfig();
config.setName("db");
config.loadFromProperties();
```
### After
```java
DatabaseBuilder config = Database.builder()
.name("db")
.loadFromProperties();
```
### Notes
- Prefer the `DatabaseBuilder` type for local variables and parameters when possible.
- If existing code only uses standard builder methods, this change is usually mechanical.
- If existing code later reads configuration back, use `config.settings()`.
---
## Step 2 — Replace `DatabaseFactory.create(config)` with `config.build()`
### Before
```java
DatabaseConfig config = new DatabaseConfig();
config.setName("db");
config.loadFromProperties();
Database database = DatabaseFactory.create(config);
```
### After
```java
DatabaseBuilder config = Database.builder()
.name("db")
.loadFromProperties();
Database database = config.build();
```
### Short form
```java
Database database = Database.builder()
.name("db")
.loadFromProperties()
.build();
```
---
## Step 3 — Replace `DatabaseFactory.create("name")`
### Before
```java
Database database = DatabaseFactory.create("other");
```
### After
```java
Database database = Database.builder()
.name("other")
.loadFromProperties()
.build();
```
### Important
For **named databases**, set `.name("...")` before `.loadFromProperties()` so the named configuration is loaded.
---
## Step 4 — Replace legacy `setXxx(...)` methods with fluent builder methods
`DatabaseBuilder` already exposes preferred fluent names for most configuration methods.
Use those names when migrating older setup code.
| Legacy call | Preferred call |
|---|---|
| `setName("db")` | `name("db")` |
| `setRegister(false)` | `register(false)` |
| `setDefaultServer(false)` | `defaultDatabase(false)` |
| `setContainerConfig(cfg)` | `containerConfig(cfg)` |
| `setDbSchema("app")` | `dbSchema("app")` |
| `setDataSourceConfig(ds)` | `dataSourceBuilder(ds)` |
| `setReadOnlyDataSourceConfig(ro)` | `readOnlyDataSourceBuilder(ro)` |
| `setRunMigration(true)` | `runMigration(true)` |
| `setDisableClasspathSearch(true)` | `disableClasspathSearch(true)` |
| `setPersistBatch(batch)` | `persistBatch(batch)` |
### Full example
#### Before
```java
DatabaseConfig config = new DatabaseConfig();
config.setName("db");
config.setRegister(false);
config.setDefaultServer(false);
config.setDataSourceConfig(dataSource);
Database database = DatabaseFactory.create(config);
```
#### After
```java
Database database = Database.builder()
.name("db")
.register(false)
.defaultDatabase(false)
.dataSourceBuilder(dataSource)
.build();
```
---
## Step 5 — Verify semantics after migration
The migration should preserve behavior, but verify these points:
- `register(true)` is still the default
- `defaultDatabase(true)` is still the default
- call `loadFromProperties()` if the old code loaded configuration from properties
- for named databases, set the name before loading properties
- explicit entity registration via `addClass(...)` / `addAll(...)` is unchanged
- custom datasource wiring via `dataSourceBuilder(...)` and `readOnlyDataSourceBuilder(...)` is unchanged
---
## Manual-review cases
These cases are **not** simple search-and-replace migrations and should be reviewed manually:
### `DatabaseFactory.createWithContextClassLoader(...)`
There is no direct builder shorthand for this today. Keep this as-is for now and migrate the surrounding builder configuration first.
### `DatabaseFactory.initialiseContainer(...)`
This is a container lifecycle concern, not a normal database-builder call. Keep it as-is unless you are intentionally moving the `ContainerConfig` onto the first builder via `containerConfig(...)`.
### `DatabaseFactory.shutdown()`
This is also a lifecycle concern rather than normal builder setup. Leave it alone unless you are making a deliberate lifecycle change.
### Variables or method signatures typed as `DatabaseConfig`
If the code only uses standard builder operations, switch the type to `DatabaseBuilder`.
If the code depends on implementation-specific `DatabaseConfig` methods, review it manually.
### Code that needs read access to builder settings
Use:
```java
DatabaseBuilder builder = Database.builder();
DatabaseBuilder.Settings settings = builder.settings();
```
rather than relying on the concrete `DatabaseConfig` type only to read getters.
---
## Automation notes for AI agents and bulk refactors
This migration is a good candidate for semi-automated upgrading.
### Safe mechanical rewrites
These are usually safe to rewrite automatically:
- `new DatabaseConfig()``Database.builder()`
- `DatabaseFactory.create(builder)``builder.build()`
- `DatabaseFactory.create("name")``Database.builder().name("name").loadFromProperties().build()`
- legacy `setXxx(...)` calls → preferred fluent builder methods
### Flag for manual review
Automatically flag, but do not blindly rewrite:
- `DatabaseFactory.createWithContextClassLoader(...)`
- `DatabaseFactory.initialiseContainer(...)`
- `DatabaseFactory.shutdown()`
- parameters, fields, or return types declared as `DatabaseConfig`
- any use that clearly depends on `DatabaseConfig` implementation details rather than `DatabaseBuilder`
---
## Related guides
- [Database configuration](add-ebean-postgres-database-config.md) — preferred modern setup style using `Database.builder()`
- [Guide index](README.md) — full list of Ebean setup and migration guides
+333
View File
@@ -0,0 +1,333 @@
# Immutable Bean Cache — notes on multi-level / remote caching
These notes capture design thoughts for a possible future multi-level immutable bean cache,
where immutable beans may be cached remotely (for example Redis or a Postgres cache table)
in addition to an in-JVM cache.
## Current important constraint
`AssocOneHelp.read()` now uses `ImmutableBeanCache.getIfPresent(id)` as a direct-hit fast path.
That means:
- `getIfPresent(id)` is on the **row read hot path**
- it must remain **cheap and local**
- it should **not** perform network I/O
- it should **not** deserialize remote payloads
- it should **not** trigger loading or record misses
## Strong recommendation
For any multi-level cache design:
- **L1 cache** = in-JVM cache of already materialized immutable beans
- **L2 cache** = remote/shared cache of serialized immutable snapshots
- **Loader** = Ebean query using the configured fetch group
With that split:
- `getIfPresent(id)` => **L1 only**
- `getAll(ids)` => batch through **L1 -> L2 -> loader**
This preserves the `AssocOneHelp` fast path.
---
## Snapshot mindset
Remote cache entries should be treated as **immutable snapshots**, not just arbitrary beans.
A cached value is specific to:
- bean type
- bean id
- tenant (if multi-tenant)
- fetch-group / cache identity
- serializer/schema version
This matters because a `Customer` cached with:
- `select("name,version")`
is not equivalent to a `Customer` cached with:
- `select("name,version").fetch("billingAddress", "line1,city")`
## Key design recommendation
Remote keys should include at least:
- bean type
- bean id
- tenant id (if applicable)
- cache/fetch-group identity
- optionally serializer/schema version
Example shape:
- `immutable:Customer:basic:42`
- `immutable:Customer:withAddresses:42`
---
## Recommended multi-level flow
### L1
Store actual read-only `EntityBean` instances.
Responsibilities:
- support `getIfPresent(id)`
- avoid repeated deserialize cost
- avoid network calls on row read path
### L2
Store serialized immutable snapshots.
Responsibilities:
- batch lookup only
- support cross-JVM sharing
- feed L1 with materialized immutable beans
### Loader
Use the existing query/fetch-group-based loader for misses.
### Suggested `getAll(ids)` flow
1. Check L1
2. Batch remaining ids to L2
3. Deserialize L2 hits into read-only beans
4. Put those beans into L1
5. Batch remaining misses to DB loader
6. Freeze / ensure read-only beans
7. Write through to L2
8. Put into L1
9. Negative-cache true misses if desired
---
## Invalidation is more important than serialization
Things to think about:
- update/delete invalidation across JVMs
- local L1 invalidation when L2 entry is removed
- ordering relative to DB commit
- multiple cache instances for the same bean type but different fetch groups
- tenant-scoped invalidation
Recommended direction:
- keep current immutable-cache invalidation semantics
- add a remote invalidation/event mechanism for L2-backed caches
- each JVM should evict affected L1 entries when notified
Examples:
- Redis: pub/sub or streams
- Postgres cache table: NOTIFY/listen, polling, or invalidation table/outbox pattern
---
## Serialization format considerations
## JSON
### Pros
- human readable / debuggable
- easier rolling upgrades
- field-name based, so generally more tolerant of schema evolution
- good fit for Redis strings or Postgres JSONB
- easier operational debugging
### Cons
- larger payloads
- more CPU to serialize/deserialize
- nested graphs / enums / dates / inheritance need disciplined handling
## Kryo / generic binary serialization
### Pros
- smaller payloads
- often faster than JSON
- can preserve object graphs efficiently
### Cons
- more fragile across versions and rolling deploys
- class registration / compatibility pain
- harder to inspect/debug
- tighter coupling to JVM/class layout
- riskier for long-lived shared cache entries
## Recommendation
For a first remote/shared implementation:
- prefer **JSON** or another self-describing structured format
- if a binary format is later needed, prefer a stable schema-based format over generic object-graph serialization
- **do not start with Kryo** unless short-lived entries and tight deployment coordination are acceptable
---
## What to serialize
Avoid thinking in terms of serializing arbitrary live entity bean graphs directly.
A cleaner model is:
- serialize a **snapshot representation**
- deserialize into a fresh entity bean
- mark loaded properties appropriately
- freeze / ensure read-only state
- store the resulting materialized bean in L1
This gives more control over:
- loaded-property semantics
- read-only state
- subtype handling
- schema/version evolution
## Practical recommendation
Remote cache entries should represent exactly the configured fetch-group snapshot.
That means:
- cache what the fetch group loaded
- include nested associations loaded by that fetch group
- treat it as a self-contained immutable snapshot
This is simpler than trying to normalize the graph into many remote cache fragments and re-link it later.
---
## Redis vs Postgres cache table
## Redis
### Good for
- low latency
- batch lookup via MGET / pipelining
- TTL/eviction support
- natural shared-cache use case
### Tradeoffs
- extra infrastructure
- memory cost
- invalidation/event coordination still required
## Postgres cache table (including unlogged-style approach)
### Good for
- simpler ops if Postgres is already present
- easy batch lookup with `IN (...)`
- fewer moving parts than introducing Redis
### Tradeoffs
- slower than Redis for hot shared-cache usage
- adds pressure to Postgres
- TTL/cleanup becomes application responsibility
- still network/database I/O, so should remain off the `getIfPresent()` hot path
## Recommendation
- if the goal is a serious shared L2 cache, Redis is the more natural fit
- if the goal is pragmatic shared caching with minimal extra infrastructure, Postgres can work but should still be treated as L2-only
---
## Versioning / evolution
Whatever serializer is used, include versioning information.
Useful dimensions:
- serializer/schema version
- cache implementation version
- fetch-group/cache identity version
This helps when:
- fields are added/removed
- graph shape changes
- fetch-group definitions evolve
---
## Compression
If remote snapshots become large:
- compress only above a size threshold
- avoid compressing tiny payloads
This is especially relevant for JSON in Redis or Postgres L2.
---
## Observability
A multi-level cache should expose at least:
- L1 hit rate
- L2 hit rate
- DB loader rate
- deserialize failures
- invalidation counts
- average payload size
- cold-start amplification
Without this, it will be hard to judge whether the remote cache is helping.
---
## Overall recommended architecture
### Recommended model
- **L1**: actual read-only `EntityBean` instances
- **L2**: serialized immutable snapshots
- **Loader**: fetch-group-based DB query
### Method responsibilities
- `getIfPresent(id)` => **L1 only**
- `getAll(ids)` => **L1 + L2 + DB loader** in batches
This aligns well with the current `AssocOneHelp` optimization and keeps the row-read path fast.
---
## Bottom line
If/when multi-level immutable caching is explored, the main points to preserve are:
1. keep `getIfPresent()` local-only
2. do remote work only in batched `getAll()`
3. key by type + id + tenant + fetch-group/cache identity
4. treat remote values as immutable snapshots
5. prefer JSON/self-describing format first
6. be cautious with generic binary serializers like Kryo
---
## Possible follow-up
If this becomes active design work later, consider promoting these notes into one of:
- a dedicated design note under `docs/notes/`
- a GitHub issue / discussion for design iteration
- a lightweight ADR if this becomes a committed architectural direction
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean api</name>
+14 -10
View File
@@ -23,11 +23,18 @@ import java.util.concurrent.Callable;
/**
* Provides the API for fetching and saving beans to a particular database.
*
* <h5>Constructing a Database</h5>
* <p>
* Databases are typically constructed via {@link #builder()} and {@link DatabaseBuilder#build()}.
* They can also be automatically constructed on demand using configuration information in
* the application.properties file. The underlying implementation is provided by
* {@link DatabaseFactory}.
*
* <h5>Registration with the DB singleton</h5>
* <p>
* When a Database instance is created it can be registered with the DB
* singleton (see {@link DatabaseConfig#setRegister(boolean)}). The DB
* singleton is essentially a map of Database's that have been registered
* When a Database instance is created it can be registered with the {@link DB}
* singleton (see {@link DatabaseBuilder#register(boolean)}). The {@link DB}
* singleton is essentially a map of {@link Database}'s that have been registered
* with it.
* <p>
* The Database can then be retrieved later via {@link DB#byName(String)}.
@@ -35,16 +42,10 @@ import java.util.concurrent.Callable;
* <h5>The 'default' Database</h5>
* <p>
* One Database can be designated as the 'default' or 'primary' Database
* (see {@link DatabaseConfig#setDefaultServer(boolean)}). Many methods on DB
* (see {@link DatabaseBuilder#defaultDatabase(boolean)}). Many methods on {@link DB}
* such as {@link DB#find(Class)} etc are actually just a convenient way to
* call methods on the 'default/primary' Database.
*
* <h5>Constructing a Database</h5>
* <p>
* Databases are constructed by the DatabaseFactory. They can be created
* programmatically via {@link DatabaseFactory#create(DatabaseBuilder)} or they
* can be automatically constructed on demand using configuration information in
* the application.properties file.
*
* <h5>Example: Get a Database</h5>
* <pre>{@code
@@ -80,6 +81,7 @@ import java.util.concurrent.Callable;
* method. Example: a single thread requires more than one transaction.
*
* @see DB
* @see DatabaseBuilder
* @see DatabaseFactory
* @see DatabaseConfig
*/
@@ -94,11 +96,13 @@ public interface Database {
* // from application.properties / application.yaml
*
* Database db = Database.builder()
* .name("db")
* .loadFromProperties()
* .build();
*
* }</pre>
*/
@SuppressWarnings("removal")
static DatabaseBuilder builder() {
return new DatabaseConfig();
}
@@ -723,19 +723,6 @@ public interface DatabaseBuilder {
@Deprecated
DatabaseBuilder setReadAuditPrepare(ReadAuditPrepare readAuditPrepare);
/**
* Set the configuration for profiling.
*/
default DatabaseBuilder profilingConfig(ProfilingConfig profilingConfig) {
return setProfilingConfig(profilingConfig);
}
/**
* @deprecated migrate to {@link #profilingConfig(ProfilingConfig)}.
*/
@Deprecated
DatabaseBuilder setProfilingConfig(ProfilingConfig profilingConfig);
/**
* Set the suffix appended to the base table to derive the view that contains the union
* of the base table and the history table in order to support asOf queries.
@@ -995,7 +982,7 @@ public interface DatabaseBuilder {
* <p>
* Use this to override the default known aggregation functions.
*/
DatabaseConfig aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext);
DatabaseBuilder aggregateFormulaContext(AggregateFormulaContext aggregateFormulaContext);
/**
* Set to true if all DB column and table names should use quoted identifiers.
@@ -2234,7 +2221,7 @@ public interface DatabaseBuilder {
*
* @param includeLabelInSql When true include a SQL inline comment in generated SELECT queries.
*/
DatabaseConfig includeLabelInSql(boolean includeLabelInSql);
DatabaseBuilder includeLabelInSql(boolean includeLabelInSql);
/**
* Set the naming convention to apply to metrics names.
@@ -2252,7 +2239,7 @@ public interface DatabaseBuilder {
/**
* Sets the length check mode.
*/
DatabaseConfig lengthCheck(LengthCheck lengthCheck);
DatabaseBuilder lengthCheck(LengthCheck lengthCheck);
/**
* Provides read access (getters) for the DatabaseBuilder configuration
@@ -2490,11 +2477,6 @@ public interface DatabaseBuilder {
*/
TenantCatalogProvider getTenantCatalogProvider();
/**
* Return the configuration for profiling.
*/
ProfilingConfig getProfilingConfig();
/**
* Return the DB schema to use.
*/
@@ -8,18 +8,18 @@ import jakarta.persistence.PersistenceException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Creates Database instances.
* Low-level factory for creating {@link Database} instances.
* <p>
* This uses either DatabaseConfig or properties in the application.properties file to
* configure and create a Database instance.
* Most applications should prefer {@link Database#builder()} together with {@link DatabaseBuilder#build()}.
* This factory remains for legacy creation entry points plus container lifecycle methods.
* <p>
* The Database instance can either be registered with the DB singleton or
* not. The DB singleton effectively holds a map of Database by a name.
* If the Database is registered with the DB singleton you can retrieve it
* The Database instance can either be registered with the {@link DB} singleton or
* not. The {@link DB} singleton effectively holds a map of {@link Database} by name.
* If the Database is registered with the {@link DB} singleton you can retrieve it
* later via {@link DB#byName(String)}.
* <p>
* One Database can be nominated as the 'default/primary' Database. Many
* methods on the DB singleton such as {@link DB#find(Class)} are just a
* methods on the {@link DB} singleton such as {@link DB#find(Class)} are just a
* convenient way of using the 'default/primary' Database.
*/
public final class DatabaseFactory {
@@ -36,7 +36,8 @@ public final class DatabaseFactory {
* Initialise the container with clustering configuration.
* <p>
* Call this prior to creating any Database instances or alternatively set the
* ContainerConfig on the DatabaseConfig when creating the first Database instance.
* {@link ContainerConfig} on the first {@link DatabaseBuilder} via
* {@link DatabaseBuilder#containerConfig(ContainerConfig)}.
*/
public static void initialiseContainer(ContainerConfig containerConfig) {
lock.lock();
@@ -48,8 +49,11 @@ public final class DatabaseFactory {
}
/**
* Create using properties to configure the database.
* Create using configuration loaded from properties for the given database name.
*
* @deprecated migrate to {@code Database.builder().name(name).loadFromProperties().build()}.
*/
@Deprecated
public static Database create(String name) {
lock.lock();
try {
@@ -60,18 +64,9 @@ public final class DatabaseFactory {
}
/**
* Create using the DatabaseConfig object to configure the database.
*
* <pre>{@code
*
* DatabaseConfig config = new DatabaseConfig();
* config.setName("db");
* config.loadProperties();
*
* Database database = DatabaseFactory.create(config);
*
* }</pre>
* @deprecated migrate to {@link DatabaseBuilder#build()}.
*/
@Deprecated(forRemoval = true)
public static Database create(DatabaseBuilder builder) {
lock.lock();
try {
@@ -97,7 +92,8 @@ public final class DatabaseFactory {
}
/**
* Create using the DatabaseConfig additionally specifying a classLoader to use as the context class loader.
* Create using the {@link DatabaseBuilder}, additionally specifying a classLoader to use as the
* context class loader.
*/
public static Database createWithContextClassLoader(DatabaseBuilder config, ClassLoader classLoader) {
lock.lock();
@@ -92,6 +92,7 @@ final class DbContext {
/**
* Read, create and put of Databases.
*/
@SuppressWarnings("deprecation")
private Database getWithCreate(String name) {
lock.lock();
try {
@@ -205,4 +205,20 @@ public interface UpdateQuery<T> {
*/
int update();
/**
* Return the timeout used to execute this statement.
*/
int getTimeout();
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
UpdateQuery<T> setTimeout(int secs);
}
@@ -31,38 +31,15 @@ import java.util.function.Consumer;
import java.util.function.Function;
/**
* The configuration used for creating a Database.
* <p>
* Used to programmatically construct an Database and optionally register it
* with the DB singleton.
* <p>
* If you just use DB thout this programmatic configuration Ebean will read
* the application.properties file and take the configuration from there. This usually
* includes searching the class path and automatically registering any entity
* classes and listeners etc.
* <pre>{@code
*
* DatabaseConfig config = new DatabaseConfig();
*
* // read the ebean.properties and load
* // those settings into this DatabaseConfig object
* config.loadFromProperties();
*
* // explicitly register the entity beans to avoid classpath scanning
* config.addClass(Customer.class);
* config.addClass(User.class);
*
* Database db = DatabaseFactory.create(config);
*
* }</pre>
* Deprecated migrate to {@link Database#builder()} rather than constructing {@code DatabaseConfig} directly.
*
* <p>
* Note that DatabaseConfigProvider provides a standard Java ServiceLoader mechanism that can
* be used to apply configuration to the DatabaseConfig.
* Note that {@link DatabaseConfigProvider} provides a standard Java ServiceLoader mechanism that can
* be used to apply configuration to the {@link DatabaseBuilder}.
*
* @author emcgreal
* @author rbygrave
* @see DatabaseFactory
* @see Database#builder()
*/
public class DatabaseConfig implements DatabaseBuilder.Settings {
@@ -539,8 +516,6 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
private SlowQueryListener slowQueryListener;
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
* The mappingLocations for searching xml mapping.
*/
@@ -560,12 +535,14 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
private Function<String, String> metricNaming = MetricNamingMatch.INSTANCE;
/**
* Construct a Database Configuration for programmatically creating an Database.
* @deprecated migrate to {@link Database#builder()} and configure the returned {@link DatabaseBuilder}.
*/
@Deprecated(forRemoval = true)
public DatabaseConfig() {
}
@Override
@SuppressWarnings("removal")
public Database build() {
return DatabaseFactory.create(this);
}
@@ -1028,17 +1005,6 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
return this;
}
@Override
public ProfilingConfig getProfilingConfig() {
return profilingConfig;
}
@Override
public DatabaseConfig setProfilingConfig(ProfilingConfig profilingConfig) {
this.profilingConfig = profilingConfig;
return this;
}
@Override
public String getDbSchema() {
return dbSchema;
@@ -2137,7 +2103,6 @@ public class DatabaseConfig implements DatabaseBuilder.Settings {
*/
protected void loadSettings(PropertiesWrapper p) {
dbSchema = p.get("dbSchema", dbSchema);
profilingConfig.loadSettings(p, name);
platformConfig.loadSettings(p);
if (platformConfig.isAllQuotedIdentifiers()) {
adjustNamingConventionForAllQuoted();
@@ -3,14 +3,14 @@ package io.ebean.config;
import io.ebean.DatabaseBuilder;
/**
* Provides a ServiceLoader based mechanism to configure a DatabaseConfig.
* Provides a ServiceLoader based mechanism to configure a {@link DatabaseBuilder}.
* <p>
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
* via a file at <code>META-INF/services/io.ebean.config.DatabaseConfigProvider</code>.
* </p>
* <p>
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
* spring specific configuration. When we are not using a DI container we may use this mechanism to
* spring specific configuration. When we are not using a DI container we may use this mechanism to
* explicitly register the entity beans and avoid classpath scanning.
* </p>
* <pre>{@code
@@ -18,7 +18,7 @@ import io.ebean.DatabaseBuilder;
* public class EbeanConfigProvider implements DatabaseConfigProvider {
*
* @Override
* public void apply(DatabaseConfig config) {
* public void apply(DatabaseBuilder config) {
*
* // register the entity bean classes explicitly
* config.addClass(Customer.class);
@@ -32,10 +32,9 @@ import io.ebean.DatabaseBuilder;
public interface DatabaseConfigProvider {
/**
* Apply the configuration to the DatabaseConfig.
* Apply the configuration to the {@link DatabaseBuilder}.
* <p>
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
* </p>
*/
void apply(DatabaseBuilder config);
}
@@ -1,148 +0,0 @@
package io.ebean.config;
/**
* Configuration for transaction profiling.
*/
public class ProfilingConfig {
/**
* When true transaction profiling is enabled.
*/
private boolean enabled;
/**
* Set true for verbose mode.
*/
private boolean verbose;
/**
* The minimum transaction execution time to be included in profiling.
*/
private long minimumMicros;
/**
* A specific set of profileIds to include in profiling.
*/
private int[] includeProfileIds = {};
/**
* The number of profiles to write per file.
*/
private long profilesPerFile = 1000;
private String directory = "profiling";
/**
* Return true if transaction profiling is enabled.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Set to true to enable transaction profiling.
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* Return true if verbose mode is used.
*/
public boolean isVerbose() {
return verbose;
}
/**
* Set to true to use verbose mode.
*/
public void setVerbose(boolean verbose) {
this.verbose = verbose;
}
/**
* Return the minimum transaction execution to be included in profiling.
*/
public long getMinimumMicros() {
return minimumMicros;
}
/**
* Set the minimum transaction execution to be included in profiling.
*/
public void setMinimumMicros(long minimumMicros) {
this.minimumMicros = minimumMicros;
}
/**
* Return the specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public int[] getIncludeProfileIds() {
return includeProfileIds;
}
/**
* Set a specific set of profileIds to include in profiling.
* When not set all transactions with profileIds are included.
*/
public void setIncludeProfileIds(int[] includeProfileIds) {
this.includeProfileIds = includeProfileIds;
}
/**
* Return the number of profiles to write to a single file.
*/
public long getProfilesPerFile() {
return profilesPerFile;
}
/**
* Set the number of profiles to write to a single file.
*/
public void setProfilesPerFile(long profilesPerFile) {
this.profilesPerFile = profilesPerFile;
}
/**
* Return the directory profiling files are put into.
*/
public String getDirectory() {
return directory;
}
/**
* Set the directory profiling files are put into.
*/
public void setDirectory(String directory) {
this.directory = directory;
}
/**
* Load setting from properties.
*/
public void loadSettings(PropertiesWrapper p, String name) {
enabled = p.getBoolean("profiling", enabled);
verbose = p.getBoolean("profiling.verbose", verbose);
directory = p.get("profiling.directory", directory);
profilesPerFile = p.getLong("profiling.profilesPerFile", profilesPerFile);
minimumMicros = p.getLong("profiling.minimumMicros", minimumMicros);
String includeIds = p.get("profiling.includeProfileIds");
if (includeIds != null) {
includeProfileIds = parseIds(includeIds);
}
}
private int[] parseIds(String includeIds) {
String[] ids = includeIds.split(",");
int[] vals = new int[ids.length];
for (int i = 0; i < ids.length; i++) {
vals[i] = Integer.parseInt(ids[i]);
}
return vals;
}
}
@@ -10,12 +10,23 @@ public interface MetaInfoManager {
/**
* Return the metrics for the database instance.
* <p>
* This will reset the metrics (reset counters back to zero etc) and
* will only return the non-empty metrics.
* This is equivalent to {@link #collectMetrics(boolean)} with reset set to true.
* It will reset the metrics (reset counters back to zero etc) and will only return
* the non-empty metrics.
* </p>
*/
ServerMetrics collectMetrics();
/**
* Return the metrics for the database instance using the given reset behavior.
* <p>
* When reset is false, count and total values remain cumulative between collections.
* </p>
*/
default ServerMetrics collectMetrics(boolean reset) {
return collectMetrics();
}
/**
* Visit the metrics resetting and collecting/reporting as desired.
*/
@@ -0,0 +1,46 @@
package io.ebean.meta;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
class MetaInfoManagerTest {
@Test
void collectMetricsBooleanDefaultsToCollectMetrics() {
ServerMetrics metrics = new BasicMetricVisitor();
MetaInfoManager manager = new MetaInfoManager() {
@Override
public ServerMetrics collectMetrics() {
return metrics;
}
@Override
public void visitMetrics(MetricVisitor visitor) {
}
@Override
public BasicMetricVisitor visitBasic() {
return new BasicMetricVisitor();
}
@Override
public void resetAllMetrics() {
}
@Override
public List<MetaQueryPlan> queryPlanInit(QueryPlanInit initRequest) {
return List.of();
}
@Override
public List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
return List.of();
}
};
assertThat(manager.collectMetrics(false)).isSameAs(metrics);
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-bench</artifactId>
+28 -28
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean bom</name>
@@ -89,25 +89,25 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -125,13 +125,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -155,37 +155,37 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>kotlin-querybean-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-redis</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-spring-txn</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<!-- platforms -->
@@ -193,91 +193,91 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-clickhouse</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-db2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-h2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-hana</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mariadb</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-mysql</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-nuodb</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-oracle</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-postgis-types</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-pgvector-types</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlite</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-sqlserver</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
</dependencies>
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>io.ebean</groupId>
<artifactId>ebean-parent</artifactId>
<version>16.6.0</version>
<version>16.8.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>16.6.0</version>
<version>16.8.0</version>
</dependency>
<!-- Jackson core used internally by Ebean -->
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-core-type</artifactId>
@@ -16,7 +16,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
+7 -7
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-core</artifactId>
@@ -22,13 +22,13 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-json</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -52,7 +52,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -165,21 +165,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-sqlserver</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
@@ -10,18 +10,6 @@ import org.jspecify.annotations.Nullable;
*/
public interface SpiProfileHandler {
/**
* Process the collected transaction profiling information.
* <p>
* Note that profileId and totalMicros are part of the profilingData but passed separately as the handler
* may filter what it processed based on this information (ignore short transactions, only process specific
* profileId transactions etc).
* </p>
*
* @param transactionProfile The transaction profile that has just been collected
*/
void collectTransactionProfile(TransactionProfile transactionProfile);
/**
* Create a profiling stream for this transaction, or return null to not profile this transaction.
* <p>
@@ -35,14 +35,17 @@ final class DefaultMetaInfoManager implements MetaInfoManager {
@Override
public ServerMetrics collectMetrics() {
return visitBasic();
return collectMetrics(true);
}
@Override
public ServerMetrics collectMetrics(boolean reset) {
return collectBasic(reset);
}
@Override
public BasicMetricVisitor visitBasic() {
BasicMetricVisitor basic = new BasicMetricVisitor(server.name(), naming);
visitMetrics(basic);
return basic;
return collectBasic(true);
}
@Override
@@ -50,6 +53,12 @@ final class DefaultMetaInfoManager implements MetaInfoManager {
server.visitMetrics(new ResetVisitor());
}
private BasicMetricVisitor collectBasic(boolean reset) {
BasicMetricVisitor basic = new BasicMetricVisitor(server.name(), naming, reset, true, true, true);
visitMetrics(basic);
return basic;
}
/**
* Visitor that resets the statistics but doesn't collect them.
*/
@@ -405,12 +405,7 @@ public final class InternalConfiguration {
if (handler != null) {
return plugin(handler);
}
ProfilingConfig profilingConfig = config.getProfilingConfig();
if (!profilingConfig.isEnabled()) {
return new NoopProfileHandler();
}
return plugin(new DefaultProfileHandler(profilingConfig));
return new NoopProfileHandler();
}
/**
@@ -116,9 +116,12 @@ public final class BatchedPstmt implements SpiProfileTransactionEvent {
getGeneratedKeys();
}
postExecute();
addTimingMetrics();
list.clear();
transaction.profileEvent(this);
try {
addTimingMetrics();
transaction.profileEvent(this);
} finally {
list.clear();
}
}
private void addTimingMetrics() {
@@ -19,9 +19,7 @@ final class DTimeMetricStats implements TimedMetricStats {
this.collected = collected;
this.count = count;
this.total = total;
// collection is racy so sanitize the max value if it has not been set
// this most likely would happen when count = 1 so max = mean
this.max = max != Long.MIN_VALUE ? max : (count < 1 ? 0 : Math.round(total / count));
this.max = max;
}
@Override
@@ -17,7 +17,7 @@ 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, Long.MIN_VALUE);
private final LongAccumulator max = new LongAccumulator(Math::max, 0);
private boolean collected;
private String reportName;
@@ -84,11 +84,8 @@ final class DTimedMetric implements TimedMetric {
*/
private DTimeMetricStats stats(boolean reset, String name, long countSum) {
try {
if (reset) {
return new DTimeMetricStats(name, collected, countSum, total.sumThenReset(), max.getThenReset());
} else {
return new DTimeMetricStats(name, collected, countSum, total.sum(), max.get());
}
final long totalSum = reset ? total.sumThenReset() : total.sum();
return new DTimeMetricStats(name, collected, countSum, totalSum, max.getThenReset());
} finally {
collected = true;
}
@@ -569,7 +569,7 @@ public final class CQuery<T> implements DbReadContext, CancelableQuery, SpiProfi
public void profile() {
transaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), loadedBeanCount, query.profileId(), query.getGeneratedSql());
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), loadedBeanCount, query.profileId(), queryPlan.hash(), query.getGeneratedSql());
}
QueryIterator<T> readIterate(int bufferSize, OrmQueryRequest<T> request) {
@@ -165,7 +165,7 @@ final class CQueryFetchSingleAttribute implements SpiProfileTransactionEvent, Ca
public void profile() {
transaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), query.getGeneratedSql());
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), queryPlan.hash(), query.getGeneratedSql());
}
Set<String> dependentTables() {
@@ -137,7 +137,7 @@ final class CQueryRowCount implements SpiProfileTransactionEvent, CancelableQuer
public void profile() {
transaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), query.getGeneratedSql());
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), queryPlan.hash(), query.getGeneratedSql());
}
Set<String> dependentTables() {
@@ -115,7 +115,7 @@ final class CQueryUpdate implements SpiProfileTransactionEvent, CancelableQuery
public void profile() {
transaction()
.profileStream()
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), query.getGeneratedSql());
.addQueryEvent(query.profileEventId(), profileOffset, desc.name(), rowCount, query.profileId(), queryPlan.hash(), query.getGeneratedSql());
}
@Override
@@ -84,4 +84,15 @@ public final class DefaultUpdateQuery<T> implements UpdateQuery<T> {
public int update() {
return query.update();
}
@Override
public int getTimeout() {
return query.timeout();
}
@Override
public UpdateQuery<T> setTimeout(int secs) {
query.setTimeout(secs);
return this;
}
}
@@ -169,7 +169,7 @@ public final class OrmQueryProperties implements Serializable {
}
private static String buildImmutableQueryPlanHashSuffix(FetchConfig fetchConfig) {
return fetchConfig == null ? "" : "/c" + fetchConfig.hashCode();
return fetchConfig == null ? "}" : "/c" + fetchConfig.hashCode() + '}';
}
/**
@@ -468,7 +468,6 @@ public final class OrmQueryProperties implements Serializable {
filterMany.queryPlanHash(builder);
}
builder.append(immutableHashSuffix);
builder.append('}');
}
private static void appendSet(StringBuilder builder, Set<String> values) {
@@ -1,271 +0,0 @@
package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import io.ebean.config.ProfilingConfig;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebean.util.IOUtils;
import io.ebeaninternal.api.CoreLog;
import io.ebeaninternal.api.SpiProfileHandler;
import org.jspecify.annotations.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static java.lang.System.Logger.Level.*;
import static java.time.temporal.ChronoField.*;
/**
* Default profile handler.
* <p>
* Uses ConcurrentLinkedQueue to minimise contention on threads calling collectTransactionProfile().
* <p>
* Uses a sleep backoff on the single threaded consumer that reads the profiles and writes them to files.
*/
public final class DefaultProfileHandler implements SpiProfileHandler, Plugin {
private static final System.Logger log = CoreLog.internal;
private static final DateTimeFormatter DTF;
static {
DTF = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.appendValue(YEAR, 4)
.appendValue(MONTH_OF_YEAR, 2)
.appendValue(DAY_OF_MONTH, 2)
.appendLiteral('-')
.appendValue(HOUR_OF_DAY, 2)
.appendValue(MINUTE_OF_HOUR, 2)
.appendValue(SECOND_OF_MINUTE, 2)
.appendLiteral('-')
.appendValue(MILLI_OF_SECOND, 3)
.toFormatter();
}
/**
* Low contention choice.
*/
private final Queue<TransactionProfile> queue = new ConcurrentLinkedQueue<>();
private final ExecutorService executor;
private final ReentrantLock lock = new ReentrantLock();
private final File dir;
private final long minMicros;
private final long profilesPerFile;
private final boolean verbose;
private volatile boolean shutdown;
private long profileCounter;
/**
* Slow down polling of transaction profiling queue.
*/
private int sleepBackoff;
private Writer out;
public DefaultProfileHandler(ProfilingConfig config) {
this.verbose = config.isVerbose();
this.minMicros = config.getMinimumMicros();
this.profilesPerFile = config.getProfilesPerFile();
// dedicated single threaded executor for consuming the
// profiling and writing it to file(s)
this.executor = Executors.newSingleThreadExecutor();
this.dir = new File(config.getDirectory());
if (!dir.exists() && !dir.mkdirs()) {
log.log(ERROR, "failed to mkdirs " + dir.getAbsolutePath());
}
incrementFile();
}
/**
* Low contention adding the transaction profile to the queue.
* Minimise the impact to the normal transaction processing (threads).
*/
@Override
public void collectTransactionProfile(TransactionProfile transactionProfile) {
queue.add(transactionProfile);
}
/**
* Create and return a ProfileStream, or null if location is null (implicit transactions
* are not profiled by the default file-based handler).
*/
@Override
public ProfileStream createProfileStream(@Nullable ProfileLocation location, String label) {
if (location == null) {
return null;
}
return new DefaultProfileStream(location, verbose);
}
private void flushCurrentFile() {
lock.lock();
try {
if (out != null) {
try {
out.close();
out = null;
} catch (IOException e) {
log.log(ERROR, "Failed to flush and close transaction profiling file ", e);
}
}
} finally {
lock.unlock();
}
}
/**
* Move to the next file to write to.
*/
private void incrementFile() {
lock.lock();
try {
flushCurrentFile();
try {
String now = DTF.format(LocalDateTime.now());
File file = new File(dir, "txprofile-" + now + ".tprofile");
out = IOUtils.newWriter(file);
} catch (IOException e) {
log.log(ERROR, "Not expected", e);
}
} finally {
lock.unlock();
}
}
/**
* Main loop for polling the queue and processing profiling messages.
*/
private void collect() {
try {
while (!shutdown) {
TransactionProfile profile = queue.poll();
if (profile == null) {
sleep();
} else if (include(profile)) {
write(profile);
}
}
flushCurrentFile();
} catch (Exception e) {
log.log(WARNING, "Error on collect", e);
}
}
/**
* Write the profile to the current file.
*/
private void write(TransactionProfile profile) {
try {
sleepBackoff = 0;
++profileCounter;
StringBuilder sb = new StringBuilder(80);
// header
sb.append(profile.getStartTime()).append(' ')
.append(profile.getLabel()).append(' ')
.append(profile.getTotalMicros()).append(' ');
// summary
appendSummary(profile, sb);
out.write(sb.toString());
if (verbose) {
out.write(' ');
out.write(profile.getData());
}
out.write('\n');
if (profileCounter % profilesPerFile == 0) {
incrementFile();
log.log(DEBUG, "profiled {0} transactions", profileCounter);
}
} catch (IOException e) {
log.log(WARNING, "Error writing transaction profiling", e);
}
}
private void appendSummary(TransactionProfile profile, StringBuilder sb) {
TransactionProfile.Summary summary = profile.getSummary();
sb.append("z:").append(rate(profile.getTotalMicros(), summary.persistCount + summary.queryCount)).append(' ');
sb.append("p:").append(rate(summary.persistMicros, summary.persistBeans)).append(' ');
sb.append("q:").append(rate(summary.queryMicros, summary.queryCount)).append(' ');
sb.append("qm:").append(summary.queryMax).append(' ');
sb.append("qt:").append(summary.queryMicros).append(' ');
sb.append("qc:").append(summary.queryCount).append(' ');
sb.append("qb:").append(summary.queryBeans).append(' ');
sb.append("pt:").append(summary.persistMicros).append(' ');
sb.append("pc:").append(summary.persistCount).append(' ');
sb.append("pb:").append(summary.persistBeans).append(' ');
sb.append("po:").append(summary.persistOneCount).append(' ');
sb.append("pz:").append(rate(summary.persistBeans, summary.persistCount));
}
private int rate(long micros, long count) {
return count < 1 ? 0 : (int) (micros / count);
}
/**
* Return true if the profile should be included (or false for ignored).
*/
private boolean include(TransactionProfile profile) {
return profile.getTotalMicros() >= minMicros;
}
/**
* Sleep backing off towards 250 millis when there is no activity.
* This seems to be simple and decent for our queue consumer.
*/
private void sleep() {
try {
// backoff sleep when nothing is happening
int sleepFor = Math.min(++sleepBackoff, 250);
Thread.sleep(sleepFor);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void configure(SpiServer server) {
// do nothing
}
@Override
public void online(boolean online) {
if (online) {
executor.submit(this::collect);
}
}
@Override
public void shutdown() {
shutdown = true;
log.log(TRACE, "shutting down");
try {
executor.shutdown();
if (!executor.awaitTermination(4, TimeUnit.SECONDS)) {
log.log(INFO, "Shut down timeout exceeded. Terminating profiling consumer thread.");
executor.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.log(WARNING, "Interrupt on shutdown", e);
}
flushCurrentFile();
}
}
@@ -1,94 +0,0 @@
package io.ebeaninternal.server.transaction;
import io.ebean.ProfileLocation;
import org.jspecify.annotations.Nullable;
/**
* Default transaction profiling event collection.
*/
public final class DefaultProfileStream implements ProfileStream {
private final long startNanos;
private final StringBuilder buffer;
private final TransactionProfile profile;
private final TransactionProfile.Summary summary;
DefaultProfileStream(@Nullable ProfileLocation location, boolean verbose) {
this.startNanos = System.nanoTime();
this.profile = new TransactionProfile(System.currentTimeMillis(), location);
this.summary = profile.getSummary();
this.buffer = (verbose) ? new StringBuilder(200) : null;
}
/**
* Return the time offset from the beginning of the transaction.
*/
@Override
public long offset() {
return ((System.nanoTime() - startNanos) / 1_000L);
}
private long exeMicros(long offset) {
return offset() - offset;
}
/**
* Add a query execution event.
*/
@Override
public void addQueryEvent(String event, long offset, String beanName, int beanCount, String queryId, String sql) {
long micros = exeMicros(offset);
summary.addQuery(micros, beanCount);
if (buffer != null) {
add(micros, event, offset, beanName, beanCount, queryId);
}
}
/**
* Add a persist event.
*/
@Override
public void addPersistEvent(String event, long offset, String beanName, int beanCount) {
long micros = exeMicros(offset);
summary.addPersist(micros, beanCount);
if (buffer != null) {
add(micros, event, offset, beanName, beanCount, "");
}
}
/**
* Add the commit/rollback event.
*/
@Override
public void addEvent(String event, long offset) {
long micros = exeMicros(offset);
summary.commitMicros = micros;
if (buffer != null) {
buffer.append(event).append(',');
buffer.append(offset).append(',');
buffer.append(micros).append(';');
}
}
private void add(long micros, String event, long offset, String beanName, int beanCount, String queryId) {
buffer.append(event).append(',');
buffer.append(offset).append(',');
buffer.append(micros).append(',');
buffer.append(beanName).append(',');
buffer.append(beanCount).append(',');
buffer.append(queryId).append(';');
}
/**
* End the transaction profiling.
*/
@Override
public void end(TransactionManager manager, String label) {
profile.setTotalMicros(offset());
if (buffer != null) {
profile.setData(buffer.toString());
}
manager.profileCollect(profile);
}
}
@@ -501,7 +501,7 @@ final class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEve
active = false;
manager.collectMetricReadOnly((System.nanoTime() - startNanos) / 1000L);
if (profileStream != null) {
profileStream.end(manager, null);
profileStream.end(null);
}
}
@@ -856,7 +856,7 @@ class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
}
manager.collectMetric(exeMicros);
if (profileStream != null) {
profileStream.end(manager, label);
profileStream.end(label);
}
}
}
@@ -9,13 +9,9 @@ import org.jspecify.annotations.Nullable;
*/
public final class NoopProfileHandler implements SpiProfileHandler {
@Nullable
@Override
public void collectTransactionProfile(TransactionProfile transactionProfile) {
// do nothing
}
@Override
public ProfileStream createProfileStream(@Nullable ProfileLocation location, String label) {
public ProfileStream createProfileStream(@Nullable ProfileLocation location, @Nullable String label) {
// always return null
return null;
}
@@ -13,7 +13,7 @@ public interface ProfileStream {
/**
* Add a query event.
*/
void addQueryEvent(String event, long offset, String beanName, int beanCount, String queryId, String sql);
void addQueryEvent(String event, long offset, String beanName, int beanCount, String queryId, String hash, String sql);
/**
* Add a persist event.
@@ -28,5 +28,5 @@ public interface ProfileStream {
/**
* Transaction completed collect the profiling information.
*/
void end(TransactionManager manager, String label);
void end(String label);
}
@@ -428,13 +428,6 @@ public class TransactionManager implements SpiTransactionManager {
cacheNotify.notify(new ServerCacheNotification(touchedTables));
}
/**
* Process the collected transaction profiling information.
*/
final void profileCollect(TransactionProfile transactionProfile) {
profileHandler.collectTransactionProfile(transactionProfile);
}
/**
* Collect execution time for an explicit transaction.
*/
@@ -0,0 +1,46 @@
package io.ebeaninternal.server.persist;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import io.ebeaninternal.api.SpiTransaction;
import org.junit.jupiter.api.Test;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class BatchedPstmtTest {
@Test
void executeBatch_profilesBeforeClearingBatch() throws SQLException {
var pstmt = mock(PreparedStatement.class);
var transaction = mock(SpiTransaction.class);
var postExecute = mock(BatchPostExecute.class);
when(pstmt.executeBatch()).thenReturn(new int[]{1});
when(transaction.profileOffset()).thenReturn(42L);
doAnswer(invocation -> {
var event = invocation.getArgument(0, SpiProfileTransactionEvent.class);
event.profile();
return null;
}).when(transaction).profileEvent(any());
var batched = new BatchedPstmt(pstmt, false, "insert into foo values (?)", transaction);
batched.add(postExecute);
batched.executeBatch(false);
verify(postExecute).checkRowCount(1);
verify(postExecute).postExecute();
verify(postExecute).addTimingBatch(anyLong(), eq(1));
verify(postExecute).profile(42L, 1);
assertThat(batched.isEmpty()).isTrue();
}
}
@@ -44,4 +44,50 @@ class DQueryPlanMetricTest {
assertThat(result.get(0).total()).isEqualTo(410);
}
}
@Test
void visitCumulativeResetsMax() {
DQueryPlanMeta meta = new DQueryPlanMeta(Object.class, "lab", null, "sql");
DTimedMetric metric = new DTimedMetric("org.timed.plan");
DQueryPlanMetric planMetric = new DQueryPlanMetric(meta, metric);
metric.add(560);
metric.add(260);
{
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).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(560);
}
{
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).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);
}
metric.add(410);
{
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).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);
}
}
}
@@ -90,4 +90,30 @@ public class DTimedMetricTest {
assertThat(result.get(0).total()).isEqualTo(410);
}
}
@Test
void collectCumulativeResetsMax() {
DTimedMetric metric = new DTimedMetric("org.timed");
metric.add(560);
metric.add(500);
DTimeMetricStats stats = metric.collect(false);
assertThat(stats.count()).isEqualTo(2);
assertThat(stats.total()).isEqualTo(1060);
assertThat(stats.max()).isEqualTo(560);
stats = metric.collect(false);
assertThat(stats.count()).isEqualTo(2);
assertThat(stats.total()).isEqualTo(1060);
assertThat(stats.max()).isEqualTo(0);
metric.add(160);
metric.add(100);
metric.add(150);
stats = metric.collect(false);
assertThat(stats.count()).isEqualTo(5);
assertThat(stats.total()).isEqualTo(1470);
assertThat(stats.max()).isEqualTo(160);
}
}
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean ddl generation</name>
@@ -28,14 +28,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -65,7 +65,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
@@ -1,9 +1,7 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebean.DB;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import io.ebean.Database;
import io.ebean.config.DbConstraintNaming;
import io.ebean.platform.h2.H2Platform;
import io.ebean.platform.sqlserver.SqlServer17Platform;
@@ -57,27 +55,25 @@ public class ModelBuildBeanVisitorTest extends BaseTestCase {
ModelContainer model = new ModelContainer();
DatabaseConfig config = new DatabaseConfig();
config.setName("h2");
config.loadFromProperties();
config.setName("h2other");
config.setAllQuotedIdentifiers(true);
config.setDdlGenerate(false);
config.setDdlRun(false);
config.setDdlExtra(false);
config.setDefaultServer(false);
config.setRegister(false);
config.addClass(CKeyDetail.class);
config.addClass(CKeyParent.class);
config.addClass(CKeyAssoc.class);
config.addClass(CKeyParentId.class);
config.setDbOffline(true);
config.setDatabasePlatform(new SqlServer17Platform());
final SpiEbeanServer database = (SpiEbeanServer) DatabaseFactory.create(config);
final SpiEbeanServer database = (SpiEbeanServer) Database.builder()
.name("h2")
.loadFromProperties()
.name("h2other")
.allQuotedIdentifiers(true)
.ddlGenerate(false)
.ddlRun(false)
.ddlExtra(false)
.defaultDatabase(false)
.register(false)
.addClass(CKeyDetail.class)
.addClass(CKeyParent.class)
.addClass(CKeyAssoc.class)
.addClass(CKeyParentId.class)
.offline(true)
.databasePlatform(new SqlServer17Platform())
.build();
try {
ModelBuildContext ctx = new ModelBuildContext(model, config.getDatabasePlatform(), config.getConstraintNaming(), true);
ModelBuildContext ctx = new ModelBuildContext(model, database.config().getDatabasePlatform(), database.config().getConstraintNaming(), true);
ModelBuildBeanVisitor addTable = new ModelBuildBeanVisitor(ctx);
@@ -1,10 +1,7 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebean.DatabaseBuilder;
import io.ebean.Database;
import io.localtest.BaseTestCase;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.Helper;
import io.ebeaninternal.dbmigration.migration.Migration;
@@ -25,23 +22,20 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ModelBuild_compoundKeyTest extends BaseTestCase {
private SpiEbeanServer createServer() {
DatabaseBuilder config = new DatabaseConfig();
config.setName("h2");
config.loadFromProperties();
config.setName("h2other");
config.setDdlGenerate(false);
config.setDdlRun(false);
config.setDdlExtra(false);
config.setDefaultServer(false);
config.setRegister(false);
config.addClass(CKeyDetail.class);
config.addClass(CKeyParent.class);
config.addClass(CKeyAssoc.class);
config.addClass(CKeyParentId.class);
return (SpiEbeanServer) DatabaseFactory.create(config);
return (SpiEbeanServer) Database.builder()
.name("h2")
.loadFromProperties()
.name("h2other")
.ddlGenerate(false)
.ddlRun(false)
.ddlExtra(false)
.defaultDatabase(false)
.register(false)
.addClass(CKeyDetail.class)
.addClass(CKeyParent.class)
.addClass(CKeyAssoc.class)
.addClass(CKeyParentId.class)
.build();
}
@Test
@@ -1,10 +1,7 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebean.DatabaseBuilder;
import io.ebean.Database;
import io.localtest.BaseTestCase;
import io.ebean.DatabaseFactory;
import io.ebean.config.DatabaseConfig;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.Helper;
import io.ebeaninternal.dbmigration.model.CurrentModel;
@@ -21,18 +18,18 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ModelBuild_compound_IdClassTest extends BaseTestCase {
private SpiEbeanServer createServer() {
DatabaseBuilder config = new DatabaseConfig();
config.setName("h2");
config.loadFromProperties();
config.setName("h2other");
config.setDdlGenerate(false);
config.setDdlRun(false);
config.setDdlExtra(false);
config.setDefaultServer(false);
config.setRegister(false);
config.addClass(CKSiteUser.class);
config.addClass(CKEmbId.class);
return (SpiEbeanServer) DatabaseFactory.create(config);
return (SpiEbeanServer) Database.builder()
.name("h2")
.loadFromProperties()
.name("h2other")
.ddlGenerate(false)
.ddlRun(false)
.ddlExtra(false)
.defaultDatabase(false)
.register(false)
.addClass(CKSiteUser.class)
.addClass(CKEmbId.class)
.build();
}
@Test
@@ -1,9 +1,6 @@
package io.ebeaninternal.dbmigration.model.build;
import io.ebean.DatabaseFactory;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.Database;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.ddlgeneration.DdlOptions;
import io.ebeaninternal.dbmigration.ddlgeneration.Helper;
@@ -20,22 +17,19 @@ import static org.assertj.core.api.Assertions.assertThat;
class ModelBuild_explicitSequencesTest extends BaseTestCase {
private SpiEbeanServer createServer(boolean postgres) {
DatabaseBuilder config = new DatabaseConfig();
config.setName("h2");
config.loadFromProperties();
config.setName("h2other");
config.setDdlGenerate(false);
config.setDdlRun(false);
config.setDdlExtra(false);
config.setDefaultServer(false);
config.setRegister(false);
config.setDatabasePlatformName(postgres ? "postgres" : "h2");
config.addClass(Person.class);
config.addClass(Phone.class);
return (SpiEbeanServer) DatabaseFactory.create(config);
return (SpiEbeanServer) Database.builder()
.name("h2")
.loadFromProperties()
.name("h2other")
.ddlGenerate(false)
.ddlRun(false)
.ddlExtra(false)
.defaultDatabase(false)
.register(false)
.databasePlatformName(postgres ? "postgres" : "h2")
.addClass(Person.class)
.addClass(Phone.class)
.build();
}
@Test
+2 -2
View File
@@ -3,7 +3,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -15,7 +15,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core-type</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean net postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -54,7 +54,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-opentelemetry</artifactId>
@@ -12,7 +12,7 @@
<description>Ebean OpenTelemetry integration - transaction and query tracing via SpiProfileHandler</description>
<properties>
<opentelemetry.version>1.51.0</opentelemetry.version>
<opentelemetry.version>1.62.0</opentelemetry.version>
<surefire.useModulePath>false</surefire.useModulePath>
</properties>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -71,21 +71,21 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -5,7 +5,6 @@ import io.ebean.plugin.Plugin;
import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.server.transaction.ProfileStream;
import io.ebeaninternal.server.transaction.TransactionProfile;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanKind;
@@ -39,8 +38,8 @@ public final class OtelProfileHandler implements SpiProfileHandler, Plugin {
@Override
public void configure(SpiServer server) {
if (this.tracer == null) {
this.tracer = GlobalOpenTelemetry.getTracer(INSTRUMENTATION_NAME);
if (tracer == null) {
tracer = GlobalOpenTelemetry.getTracer(INSTRUMENTATION_NAME);
}
}
@@ -64,7 +63,7 @@ public final class OtelProfileHandler implements SpiProfileHandler, Plugin {
*/
@Override
public @Nullable ProfileStream createProfileStream(@Nullable ProfileLocation location, @Nullable String label) {
if (!Span.current().getSpanContext().isValid()) {
if (!Span.current().isRecording()) {
// No active OTel trace context — don't create spans to avoid noise
return null;
}
@@ -79,11 +78,4 @@ public final class OtelProfileHandler implements SpiProfileHandler, Plugin {
return new OtelProfileStream(tracer, txnSpan);
}
/**
* The stream handles span lifecycle inline — nothing to do here.
*/
@Override
public void collectTransactionProfile(TransactionProfile transactionProfile) {
// no-op: OtelProfileStream.end() already closed the span
}
}
@@ -26,6 +26,7 @@ final class OtelProfileStream implements ProfileStream {
static final AttributeKey<String> DB_QUERY_TEXT = AttributeKey.stringKey("db.query.text");
static final AttributeKey<Long> DB_QUERY_TIME = AttributeKey.longKey("db.query.time");
static final AttributeKey<String> EBEAN_BEAN_TYPE = AttributeKey.stringKey("ebean.bean_type");
static final AttributeKey<String> EBEAN_QUERY_HASH = AttributeKey.stringKey("ebean.query_hash");
static final AttributeKey<Long> EBEAN_ROW_COUNT = AttributeKey.longKey("ebean.row_count");
static final AttributeKey<Long> EBEAN_TOTAL_MICROS = AttributeKey.longKey("ebean.total_micros");
@@ -45,18 +46,21 @@ final class OtelProfileStream implements ProfileStream {
}
@Override
public void addQueryEvent(String event, long offset, String beanName, int beanCount, String queryId, String sql) {
public void addQueryEvent(String event, long offset, String beanName, int beanCount, String queryId, String hash, String sql) {
long exeMicros = offset() - offset;
var now = Instant.now();
String operation = operationName(event);
String name = queryId != null ? queryId : operation + " " + beanName;
Span child = childSpanBuilder(name, now.minus(exeMicros, ChronoUnit.MICROS))
SpanBuilder builder = childSpanBuilder(name, now.minus(exeMicros, ChronoUnit.MICROS))
.setAttribute(DB_OPERATION, operation)
.setAttribute(EBEAN_BEAN_TYPE, beanName)
.setAttribute(EBEAN_ROW_COUNT, (long) beanCount)
.setAttribute(DB_QUERY_TEXT, sql)
.setAttribute(DB_QUERY_TIME, exeMicros)
.startSpan();
.setAttribute(DB_QUERY_TIME, exeMicros);
if (hash != null) {
builder.setAttribute(EBEAN_QUERY_HASH, hash);
}
Span child = builder.startSpan();
child.end(now);
}
@@ -84,7 +88,7 @@ final class OtelProfileStream implements ProfileStream {
}
@Override
public void end(TransactionManager manager, String label) {
public void end(String label) {
txnSpan.setAttribute(EBEAN_TOTAL_MICROS, offset());
if (label != null) {
txnSpan.updateName("txn." + label);
@@ -85,7 +85,7 @@ class OtelProfileHandlerTest {
var stream = handler.createProfileStream(null, null);
assertNotNull(stream);
stream.addEvent("c", 0); // commit
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -98,7 +98,7 @@ class OtelProfileHandlerTest {
var stream = handler.createProfileStream(mockLocation("OrderService.placeOrder"), null);
assertNotNull(stream);
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -119,9 +119,9 @@ class OtelProfileHandlerTest {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(null, null);
assertNotNull(stream);
// First query event should update the transaction span name
stream.addQueryEvent("fm", stream.offset(), "Customer", 5, "qplan-1", "select ...");
stream.addQueryEvent("fm", stream.offset(), "Customer", 5, "qplan-1", "h1", "select ...");
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -141,9 +141,9 @@ class OtelProfileHandlerTest {
assertNotNull(stream);
long offset = stream.offset();
// Simulate some time passing then record a find_many
stream.addQueryEvent("fm", offset, "Order", 42, "plan-abc", "select ...");
stream.addQueryEvent("fm", offset, "Order", 42, "plan-abc", "abc123hash", "select ...");
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -155,6 +155,7 @@ class OtelProfileHandlerTest {
assertEquals("Order", querySpan.getAttributes().get(OtelProfileStream.EBEAN_BEAN_TYPE));
assertEquals(42L, querySpan.getAttributes().get(OtelProfileStream.EBEAN_ROW_COUNT));
assertEquals("select ...", querySpan.getAttributes().get(OtelProfileStream.DB_QUERY_TEXT));
assertEquals("abc123hash", querySpan.getAttributes().get(OtelProfileStream.EBEAN_QUERY_HASH));
}
@Test
@@ -163,10 +164,10 @@ class OtelProfileHandlerTest {
try (Scope ignored = parent.makeCurrent()) {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(mockLocation("MyService.doAll"), null);
assertNotNull(stream);
stream.addQueryEvent("fo", stream.offset(), "User", 1, "p1", "select from user");
stream.addQueryEvent("fm", stream.offset(), "Order", 10, "p2", "select from order");
stream.addQueryEvent("fo", stream.offset(), "User", 1, "p1", "h1", "select from user");
stream.addQueryEvent("fm", stream.offset(), "Order", 10, "p2", "h2", "select from order");
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -184,15 +185,17 @@ class OtelProfileHandlerTest {
try (Scope ignored = parent.makeCurrent()) {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(mockLocation("MyService.emptySql"), null);
assertNotNull(stream);
stream.addQueryEvent("fo", stream.offset(), "User", 1, "p1", "");
stream.addQueryEvent("fo", stream.offset(), "User", 1, "p1", null, "");
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
SpanData querySpan = findSpan("p1");
assertNotNull(querySpan);
assertEquals("", querySpan.getAttributes().get(OtelProfileStream.DB_QUERY_TEXT));
// null hash should not set the attribute
assertNull(querySpan.getAttributes().get(OtelProfileStream.EBEAN_QUERY_HASH));
}
// ----------------------------------------------------------
@@ -208,7 +211,7 @@ class OtelProfileHandlerTest {
long offset = stream.offset();
stream.addPersistEvent("i", offset, "Order", 1);
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -230,7 +233,7 @@ class OtelProfileHandlerTest {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(mockLocation("Svc.ok"), null);
assertNotNull(stream);
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -246,7 +249,7 @@ class OtelProfileHandlerTest {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(mockLocation("Svc.fail"), null);
assertNotNull(stream);
stream.addEvent("r", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -266,7 +269,7 @@ class OtelProfileHandlerTest {
OtelProfileStream stream = (OtelProfileStream) handler.createProfileStream(mockLocation("Svc.timed"), null);
assertNotNull(stream);
stream.addEvent("c", 0);
stream.end(null, null);
stream.end(null);
} finally {
parent.end();
}
@@ -2,7 +2,6 @@ package org.example.domain;
import io.ebean.Database;
import io.ebean.Transaction;
import io.ebean.config.ProfilingConfig;
import io.ebean.datasource.DataSourceBuilder;
import io.ebean.datasource.DataSourcePool;
import io.ebean.opentelemetry.OtelProfileHandler;
@@ -123,9 +122,6 @@ class OtelJaegerIntegrationTest {
}
private Database createDatabase(Tracer tracer) {
ProfilingConfig profilingConfig = new ProfilingConfig();
profilingConfig.setEnabled(true);
DataSourcePool ds = DataSourceBuilder.create()
.url("jdbc:h2:mem:otelit;DB_CLOSE_DELAY=-1")
.username("sa")
@@ -140,7 +136,6 @@ class OtelJaegerIntegrationTest {
.loadFromProperties()
.ddlGenerate(true)
.ddlRun(true)
.profilingConfig(profilingConfig)
.putServiceObject(SpiProfileHandler.class, new OtelProfileHandler(tracer))
.addClass(OtelOrder.class)
.dataSource(ds)
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean pgvector types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -54,7 +54,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean postgis types</name>
@@ -19,14 +19,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-postgres</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<!-- provided scope -->
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -62,7 +62,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
+5 -5
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean querybean</name>
@@ -17,7 +17,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -59,14 +59,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
@@ -80,7 +80,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -26,6 +26,35 @@ public final class PString<R> extends PBaseComparable<R, String> {
super(name, root, prefix);
}
/**
* Is equal to if the value is non-null and not blank, otherwise no expression is added to the query.
* <p>
* This is like {@code eqIfPresent()} but additionally treats an empty or whitespace-only value as
* "not present". When present, the value is trimmed before being used in the equal to expression.
* <p>
* This is a helper for building queries in fluid style where a String predicate is effectively
* optional, avoiding a separate {@code if} block and {@code trimToNull()} style helper.
*
* <pre>{@code
* List<Customer> customers = new QCustomer()
* .name.eqIfNotBlank(nameFilter)
* .status.eqIfPresent(statusFilter)
* .findList();
* }</pre>
*
* @param value the value which can be null or blank
* @return the root query bean instance
*/
public R eqIfNotBlank(@Nullable String value) {
if (value != null) {
String trimmed = value.trim();
if (!trimmed.isEmpty()) {
expr().eq(_name, trimmed);
}
}
return _root;
}
/**
* Case insensitive is equal to.
*
@@ -0,0 +1,79 @@
package org.querytest;
import io.ebean.DB;
import io.ebean.Database;
import org.example.domain.Customer;
import org.example.domain.query.QCustomer;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.example.domain.query.QCustomer.Alias.name;
class QStringEqIfNotBlankTest {
@Test
void present_addsEqPredicate() {
var q = new QCustomer()
.select(name)
.name.eqIfNotBlank("rob")
.query();
q.findList();
assertThat(q.getGeneratedSql()).contains("where t0.name = ?");
}
@Test
void null_skipsPredicate() {
var q = new QCustomer()
.select(name)
.name.isNotNull()
.name.eqIfNotBlank(null)
.query();
q.findList();
assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?");
}
@Test
void blankWhitespace_skipsPredicate() {
var q = new QCustomer()
.select(name)
.name.isNotNull()
.name.eqIfNotBlank(" ")
.query();
q.findList();
assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?");
}
@Test
void empty_skipsPredicate() {
var q = new QCustomer()
.select(name)
.name.isNotNull()
.name.eqIfNotBlank("")
.query();
q.findList();
assertThat(q.getGeneratedSql()).doesNotContain("t0.name = ?");
}
@Test
void present_trimsBoundValue() {
Database db = DB.getDefault();
String unique = "eqIfNotBlank-" + System.nanoTime();
Customer customer = new Customer();
customer.name = unique;
db.save(customer);
Customer found = new QCustomer(db)
.name.eqIfNotBlank(" " + unique + " ")
.findOne();
assertThat(found).isNotNull();
assertThat(found.name).isEqualTo(unique);
db.delete(customer);
}
}
@@ -24,7 +24,7 @@ class QueryAlsoIfTest {
.query();
q.findList();
assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.also */ t0.id, t0.name from be_customer t0 where t0.name is not null and t0.email is not null");
assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null and t0.email is not null");
}
@Test
@@ -61,7 +61,7 @@ class QueryAlsoIfTest {
.query();
q.findList();
assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.applyIfPresent */ t0.id, t0.name from be_customer t0 where t0.name is not null and t0.status = ?");
assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null and t0.status = ?");
}
@Test
@@ -74,7 +74,7 @@ class QueryAlsoIfTest {
.query();
q.findList();
assertThat(q.getGeneratedSql()).isEqualTo("select /* QueryAlsoIfTest.notApplyIfPresent */ t0.id, t0.name from be_customer t0 where t0.name is not null");
assertThat(q.getGeneratedSql()).contains("from be_customer t0 where t0.name is not null");
}
@Test
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-redis</artifactId>
@@ -29,35 +29,35 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-api</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-querybean</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>querybean-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
+2 -2
View File
@@ -12,13 +12,13 @@ to integrate with Springs JDBC Transaction manager.
## To use
```java
DatabaseConfig config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
// set SpringJdbcTransactionManager ... as the external transaction manager
config.setExternalTransactionManager(new SpringJdbcTransactionManager());
...
Database database = DatabaseFactory.create(config);
Database database = config.build();
```
+3 -3
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<artifactId>ebean-spring-txn</artifactId>
@@ -28,7 +28,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
@@ -77,7 +77,7 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-test</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
@@ -1,7 +1,6 @@
package org.example;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.annotation.PersistBatch;
import io.ebean.DatabaseBuilder;
import org.springframework.beans.factory.FactoryBean;
@@ -33,7 +32,7 @@ public class EbeanServerFactoryBean implements InitializingBean, FactoryBean<Dat
serverConfig.setPersistBatch(PersistBatch.ALL);
// Create the new EbeanServer using the configuration
this.ebeanServer = DatabaseFactory.create(serverConfig);
this.ebeanServer = serverConfig.build();
}
public Database getObject() throws Exception {
+6 -6
View File
@@ -4,7 +4,7 @@
<parent>
<artifactId>ebean-parent</artifactId>
<groupId>io.ebean</groupId>
<version>16.6.0</version>
<version>16.8.0</version>
</parent>
<name>ebean test</name>
@@ -33,20 +33,20 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-h2</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-core</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-ddl-generator</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
</dependency>
<dependency>
@@ -149,14 +149,14 @@
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-jackson-mapper</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.ebean</groupId>
<artifactId>ebean-platform-all</artifactId>
<version>16.6.0</version>
<version>16.8.0</version>
<scope>test</scope>
</dependency>
@@ -1,8 +1,8 @@
package io.ebean.test.config.platform;
import io.ebean.Database;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.datasource.DataSourceBuilder;
import io.ebeaninternal.api.DbOffline;
import org.junit.jupiter.api.AfterAll;
@@ -29,7 +29,7 @@ class ConfigTest {
@Test
void trimExtensions() {
Config config = new Config("db", "db", "db", new DatabaseConfig());
Config config = new Config("db", "db", "db", Database.builder().settings());
assertThat(config.trimExtensions("a,b")).isEqualTo("a,b");
assertThat(config.trimExtensions(" a , b ")).isEqualTo("a,b");
@@ -38,10 +38,10 @@ class ConfigTest {
@Test
void extensions_whenNoSetValues() {
DatabaseConfig databaseBuilder = new DatabaseConfig();
DatabaseBuilder databaseBuilder = Database.builder();
databaseBuilder.loadFromProperties(new Properties());
Config config = new Config("db", "postgis", "db", databaseBuilder);
Config config = new Config("db", "postgis", "db", databaseBuilder.settings());
config.setUsernameDefault();
config.setPasswordDefault();
@@ -56,13 +56,13 @@ class ConfigTest {
@Test
void extensions_whenSetValues() {
DatabaseConfig databaseBuilder = new DatabaseConfig();
DatabaseBuilder databaseBuilder = Database.builder();
Properties properties = new Properties();
properties.setProperty("ebean.test.extensions", "x,y");
properties.setProperty("ebean.test.extraDb.extensions", "z");
databaseBuilder.loadFromProperties(properties);
Config config = new Config("db", "postgis", "db", databaseBuilder);
Config config = new Config("db", "postgis", "db", databaseBuilder.settings());
config.setExtensions("a,b");
config.setExtraExtensions("c,d");
@@ -78,7 +78,7 @@ class ConfigTest {
Properties p = new Properties();
p.setProperty("ebean.test.extraDb", "other");
DatabaseConfig serverConfig = new DatabaseConfig();
DatabaseBuilder.Settings serverConfig = Database.builder().settings();
serverConfig.loadFromProperties(p);
Config config = new Config("other", "postgres", "other", serverConfig);
@@ -100,7 +100,7 @@ class ConfigTest {
Properties p = new Properties();
p.setProperty("ebean.test.extraDb.dbName", "other");
DatabaseConfig serverConfig = new DatabaseConfig();
DatabaseBuilder.Settings serverConfig = Database.builder().settings();
serverConfig.loadFromProperties(p);
Config config = new Config("other", "postgres", "other", serverConfig);
@@ -126,7 +126,7 @@ class ConfigTest {
p.setProperty("ebean.test.extraDb.password", "other_pwd");
p.setProperty("ebean.test.extraDb.url", "other_url");
DatabaseConfig serverConfig = new DatabaseConfig();
DatabaseBuilder.Settings serverConfig = Database.builder().settings();
serverConfig.setName("scOther");
serverConfig.loadFromProperties(p);
@@ -151,7 +151,7 @@ class ConfigTest {
sourceProperties.setProperty("ebean.test.dbName", "main");
sourceProperties.setProperty("ebean.test.extraDb.dbName", "central");
DatabaseConfig serverConfig = new DatabaseConfig();
DatabaseBuilder.Settings serverConfig = Database.builder().settings();
serverConfig.setName("main");
serverConfig.loadFromProperties(sourceProperties);
@@ -164,8 +164,8 @@ class ConfigTest {
assertThat(mainProps.getProperty("datasource.main.username")).isEqualTo("main");
DatabaseBuilder centralConfig = new DatabaseConfig();
centralConfig.setName("central");
DatabaseBuilder centralConfig = Database.builder().settings();
centralConfig.name("central");
centralConfig.loadFromProperties(sourceProperties);
Config extraConfig = new Config("central", "postgres", "central", serverConfig);
@@ -220,10 +220,10 @@ class ConfigTest {
}
private Config createConfig(Properties p) {
DatabaseConfig serverConfig = new DatabaseConfig();
serverConfig.setName("scOther");
DatabaseBuilder serverConfig = Database.builder();
serverConfig.name("scOther");
serverConfig.loadFromProperties(p);
return new Config("db_name", "postgres", "db_name", serverConfig);
return new Config("db_name", "postgres", "db_name", serverConfig.settings());
}
}
@@ -3,7 +3,6 @@ package io.ebean.xtest.base;
import io.ebean.DatabaseBuilder;
import io.ebean.xtest.BaseTestCase;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.config.*;
import io.ebean.platform.mysql.MySqlPlatform;
import io.ebean.platform.postgres.PostgresPlatform;
@@ -28,7 +27,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
TenantDataSourceProvider dataSourceProvider = Mockito.mock(TenantDataSourceProvider.class);
Mockito.doReturn(mockedDataSource).when(dataSourceProvider).dataSource(tenant);
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("multiTenantDb");
config.loadFromProperties();
config.setRegister(false);
@@ -44,7 +43,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
//config.setDdlRun(false);
config.setDatabasePlatform(new PostgresPlatform());
final Database database = DatabaseFactory.create(config);
final Database database = config.build();
database.shutdown();
}
@@ -63,7 +62,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
TenantSchemaProvider schemaProvider = Mockito.mock(TenantSchemaProvider.class);
Mockito.doReturn("tenant_schema").when(schemaProvider).schema(tenant);
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2");
config.loadFromProperties();
config.setName("multi-tenancy");
@@ -77,7 +76,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
config.setDdlRun(false);
config.setDatabasePlatform(new MySqlPlatform());
final Database database = DatabaseFactory.create(config);
final Database database = config.build();
database.shutdown();
}
@@ -94,7 +93,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
TenantCatalogProvider catalogProvider = Mockito.mock(TenantCatalogProvider.class);
Mockito.doReturn("tenant_catalog").when(catalogProvider).catalog(tenant);
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2");
config.loadFromProperties();
config.setName("multi-tenancy");
@@ -108,7 +107,7 @@ public class EbeanServerFactory_MultiTenancy_Test extends BaseTestCase {
config.setDdlRun(false);
config.setDatabasePlatform(new MySqlPlatform());
final Database database = DatabaseFactory.create(config);
final Database database = config.build();
database.shutdown();
}
}
@@ -1,9 +1,7 @@
package io.ebean.xtest.base;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.ServerConfigStartup;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
@@ -46,7 +44,7 @@ public class EbeanServerFactory_ServerConfigStart_Test {
@Test
public void test() throws InterruptedException {
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2");
config.loadFromProperties();
config.setName("h2other");
@@ -65,7 +63,7 @@ public class EbeanServerFactory_ServerConfigStart_Test {
MySpiLoggerFactory loggerFactory = new MySpiLoggerFactory();
config.putServiceObject(SpiLoggerFactory.class, loggerFactory);
Database db = DatabaseFactory.create(config);
Database db = config.build();
assertThat(loggerFactory.loggers).containsExactlyInAnyOrder("io.ebean.SQL", "io.ebean.SUM", "io.ebean.TXN");
@@ -77,7 +75,7 @@ public class EbeanServerFactory_ServerConfigStart_Test {
// test server shutdown and restart using the same DatabaseConfig
db.shutdown(true, false);
Database restartedServer = DatabaseFactory.create(config);
Database restartedServer = config.build();
restartedServer.shutdown(true, false);
}
@@ -437,4 +437,14 @@ public class UpdateQueryTest extends BaseTestCase {
return b0.getId();
}
@Test
public void timeoutPropagation() {
int timeout = 7;
var updateQuery = DB.update(Customer.class)
.setTimeout(timeout);
assertThat(updateQuery.getTimeout()).isEqualTo(timeout);
}
}
@@ -1,11 +1,9 @@
package io.ebean.xtest.config;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.Transaction;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DbIdentity;
import io.ebean.config.dbplatform.IdType;
import io.ebean.platform.h2.H2Platform;
@@ -89,7 +87,7 @@ public class PlatformNoGeneratedKeysTest {
private static Database testH2Server() {
DatabaseConfig config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2_noGeneratedKeys");
OtherH2Platform platform = new OtherH2Platform();
@@ -101,10 +99,10 @@ public class PlatformNoGeneratedKeysTest {
dbIdentity.setSelectLastInsertedIdTemplate("select identity() --{table}");
config.setDatabasePlatform(platform);
config.getDataSourceConfig().setUsername("sa");
config.getDataSourceConfig().setPassword("");
config.getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;MODE=LEGACY");
config.getDataSourceConfig().setDriver("org.h2.Driver");
config.settings().getDataSourceConfig().setUsername("sa");
config.settings().getDataSourceConfig().setPassword("");
config.settings().getDataSourceConfig().setUrl("jdbc:h2:mem:withPCQuery;MODE=LEGACY");
config.settings().getDataSourceConfig().setDriver("org.h2.Driver");
config.setDisableLazyLoading(true);
config.setDisableL2Cache(true);
@@ -116,7 +114,7 @@ public class PlatformNoGeneratedKeysTest {
config.addClass(BasicDraftableBean.class);
config.loadFromProperties(); // trigger auto config for H2 1.x
return DatabaseFactory.create(config);
return config.build();
}
public static class OtherH2Platform extends H2Platform {
@@ -1,11 +1,8 @@
package io.ebean.xtest.config;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.xtest.ForPlatform;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -27,7 +24,7 @@ public class ServerConfigSqlServerTest {
// no explicit databasePlatformName set ..
//props.setProperty("ebean.some_sqlserver.databasePlatformName", "sqlserver17");
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("some_sqlserver");
config.loadFromProperties(props);
@@ -39,7 +36,7 @@ public class ServerConfigSqlServerTest {
config.setRegister(false);
config.addClass(EBasicVer.class);
Database sqlServer = DatabaseFactory.create(config);
Database sqlServer = config.build();
assertThat(sqlServer).isNotNull();
sqlServer.shutdown();
@@ -62,7 +59,7 @@ public class ServerConfigSqlServerTest {
String name = "testsqlserver17";
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName(name);
Properties props = props(name);
@@ -80,7 +77,7 @@ public class ServerConfigSqlServerTest {
config.loadFromProperties(props);
config.addClass(EBasicVer.class);
Database sqlServer = DatabaseFactory.create(config);
Database sqlServer = config.build();
assertThat(sqlServer).isNotNull();
sqlServer.shutdown();
@@ -95,7 +92,7 @@ public class ServerConfigSqlServerTest {
Properties props = props(name);
//props.setProperty("ebean.testsqlserver16.databasePlatformName", "sqlserver16");
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setDefaultServer(false);
config.setRegister(false);
config.setDdlGenerate(true);
@@ -106,7 +103,7 @@ public class ServerConfigSqlServerTest {
config.loadFromProperties(props);
config.addClass(EBasicVer.class);
Database sqlServer = DatabaseFactory.create(config);
Database sqlServer = config.build();
assertThat(sqlServer).isNotNull();
sqlServer.shutdown();
@@ -1,9 +1,7 @@
package io.ebean.xtest.dbmigration;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.dbmigration.DbMigration;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -47,7 +45,7 @@ class DbMigrationDropHistoryTest {
migration.setPathToResources("src/test/resources");
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("migrationtest-history");
config.loadFromProperties();
config.setRegister(false);
@@ -55,7 +53,7 @@ class DbMigrationDropHistoryTest {
config.setPackages(Arrays.asList("misc.migration.history.v1_0"));
Database server = DatabaseFactory.create(config);
Database server = config.build();
migration.setServer(server);
// First, we clean up the output-directory
@@ -71,7 +69,7 @@ class DbMigrationDropHistoryTest {
// and now for v1_1
config.setPackages(Arrays.asList("misc.migration.history.v1_1"));
server.shutdown();
server = DatabaseFactory.create(config);
server = config.build();
migration.setServer(server);
assertThat(migration.generateMigration()).isEqualTo("1.1");
assertThat(migration.generateMigration()).isNull(); // subsequent call
@@ -1,10 +1,8 @@
package io.ebean.xtest.dbmigration;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.dbmigration.DbMigration;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -89,15 +87,15 @@ public class DbMigrationGenerateTest {
migration.addPlatform(Platform.YUGABYTE);
DatabaseConfig config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("migrationtest");
config.loadFromProperties();
config.setRegister(false);
config.setDefaultServer(false);
config.getProperties().put("ebean.hana.generateUniqueDdl", "true"); // need to generate unique statements to prevent them from being filtered out as duplicates by the DdlRunner
config.settings().getProperties().put("ebean.hana.generateUniqueDdl", "true"); // need to generate unique statements to prevent them from being filtered out as duplicates by the DdlRunner
config.setPackages(Arrays.asList("misc.migration.v1_0"));
Database server = DatabaseFactory.create(config);
Database server = config.build();
migration.setServer(server);
// then we generate migration scripts for v1_0
@@ -108,7 +106,7 @@ public class DbMigrationGenerateTest {
// and now for v1_1
config.setPackages(Arrays.asList("misc.migration.v1_1"));
server.shutdown();
server = DatabaseFactory.create(config);
server = config.build();
migration.setServer(server);
assertThat(migration.generateMigration()).isEqualTo("1.1");
assertThat(migration.generateMigration()).isNull(); // subsequent call
@@ -128,7 +126,7 @@ public class DbMigrationGenerateTest {
// and now for v1_2 with
config.setPackages(Arrays.asList("misc.migration.v1_2"));
server.shutdown();
server = DatabaseFactory.create(config);
server = config.build();
migration.setServer(server);
assertThat(migration.generateMigration()).isEqualTo("1.3");
assertThat(migration.generateMigration()).isNull(); // subsequent call
@@ -2,8 +2,8 @@ package io.ebean.xtest.dbmigration;
import io.ebean.*;
import io.ebean.annotation.Platform;
import io.ebean.Database;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.config.dbplatform.DbHistorySupport;
import io.ebean.datasource.DataSourcePool;
import io.ebean.xtest.BaseTestCase;
@@ -217,7 +217,7 @@ public class DbMigrationTest extends BaseTestCase {
if (history == null) {
return;
}
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName(server().name());
config.loadFromProperties(server().pluginApi().config().getProperties());
config.setDataSource(server().dataSource());
@@ -227,7 +227,7 @@ public class DbMigrationTest extends BaseTestCase {
config.setRegister(false);
config.setPackages(Collections.singletonList("misc.migration.v1_1"));
Database tmpServer = DatabaseFactory.create(config);
Database tmpServer = config.build();
try {
EHistory hist = new misc.migration.v1_1.EHistory();
hist.setId(2);
@@ -289,7 +289,7 @@ public class DbMigrationTest extends BaseTestCase {
// do some history tests with V1.1 models
private void testReservedKeywords() {
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName(server().name());
config.loadFromProperties(server().pluginApi().config().getProperties());
config.setDataSource(server().dataSource());
@@ -299,7 +299,7 @@ public class DbMigrationTest extends BaseTestCase {
config.setRegister(false);
config.setPackages(Collections.singletonList("misc.migration.v1_0"));
Database tmpServer = DatabaseFactory.create(config);
Database tmpServer = config.build();
try {
ETable table = new misc.migration.v1_0.ETable();
table.setFrom("foo");
@@ -1,11 +1,9 @@
package io.ebean.xtest.event;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.bean.BeanCollection;
import io.ebean.common.BeanList;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.BeanFindController;
import io.ebean.event.BeanQueryRequest;
import io.ebean.xtest.BaseTestCase;
@@ -27,7 +25,7 @@ public class BeanFindControllerTest extends BaseTestCase {
@Test
public void test() {
var config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2otherfind");
config.setRegister(false);
@@ -42,9 +40,9 @@ public class BeanFindControllerTest extends BaseTestCase {
config.addClass(ECustomId.class);
EBasicFindController findController = new EBasicFindController();
config.getFindControllers().add(findController);
config.settings().getFindControllers().add(findController);
Database db = DatabaseFactory.create(config);
Database db = config.build();
assertFalse(findController.calledInterceptFind);
db.find(EBasic.class, 42);
@@ -170,7 +168,7 @@ public class BeanFindControllerTest extends BaseTestCase {
}
private Database prepareSoftRefs() {
DatabaseConfig config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2otherfind");
config.setRegister(false);
@@ -185,9 +183,9 @@ public class BeanFindControllerTest extends BaseTestCase {
config.addClass(SoftRefA.class);
config.addClass(SoftRefB.class);
config.getFindControllers().add(new TestBeanFindController());
config.settings().getFindControllers().add(new TestBeanFindController());
Database db = DatabaseFactory.create(config);
Database db = config.build();
final SoftRefA softRefA = new SoftRefA();
softRefA.setTitle("softRefA");
@@ -1,16 +1,11 @@
package io.ebean.xtest.event;
import io.ebean.Database;
import io.ebean.DatabaseBuilder;
import io.ebean.DatabaseFactory;
import io.ebean.Transaction;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.BeanDeleteIdRequest;
import io.ebean.event.BeanPersistAdapter;
import io.ebean.event.BeanPersistController;
import io.ebean.event.BeanPersistRequest;
import io.ebean.test.LoggedSql;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -151,7 +146,7 @@ public class BeanPersistControllerTest {
}
private Database createDatabase(PersistAdapter persistAdapter) {
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2ebasicver");
config.setRegister(false);
config.setDefaultServer(false);
@@ -165,7 +160,7 @@ public class BeanPersistControllerTest {
config.addClass(UTDetail.class);
config.add(persistAdapter);
return DatabaseFactory.create(config);
return config.build();
}
static class PersistAdapter extends BeanPersistAdapter {
@@ -1,12 +1,9 @@
package io.ebean.xtest.event;
import io.ebean.BeanState;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.event.BeanPostLoad;
import io.ebean.xtest.BaseTestCase;
import org.junit.jupiter.api.Test;
@@ -49,7 +46,7 @@ public class BeanPostLoadTest extends BaseTestCase {
private Database createDatabase() {
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2ebasicver");
config.setRegister(false);
@@ -63,7 +60,7 @@ public class BeanPostLoadTest extends BaseTestCase {
config.add(postLoad);
return DatabaseFactory.create(config);
return config.build();
}
static class PostLoad implements BeanPostLoad {
@@ -2,11 +2,9 @@ package io.ebean.xtest.internal.server.transaction;
import io.ebean.DB;
import io.ebean.Database;
import io.ebean.DatabaseFactory;
import io.ebean.Transaction;
import io.ebean.annotation.Platform;
import io.ebean.DatabaseBuilder;
import io.ebean.config.DatabaseConfig;
import io.ebean.xtest.BaseTestCase;
import io.ebean.xtest.ForPlatform;
import io.ebeaninternal.api.SpiTransaction;
@@ -132,7 +130,7 @@ public class DefaultTransactionThreadLocalTest extends BaseTestCase {
private Database createOtherDatabase() {
DatabaseBuilder config = new DatabaseConfig();
DatabaseBuilder config = Database.builder();
config.setName("h2ebasicver");
config.loadFromProperties();
config.setDdlGenerate(true);
@@ -145,6 +143,6 @@ public class DefaultTransactionThreadLocalTest extends BaseTestCase {
config.addClass(UTMaster.class);
config.addClass(UTDetail.class);
return DatabaseFactory.create(config);
return config.build();
}
}

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