* Metrics - support both CUMULATIVE and DELTA metrics collection concurrently
ebean insight [and StatsD] work off DELTA metrics, where as OTEL
and Prometheus want CUMULATIVE. With this change we can have a metrics
collection for ebean insight using DELTA mode and have a second collection
use CUMULATIVE for reporting to OTEL - for the case of fan-out metrics
going to 2 places.
This isn't strictly needed when only one collection mode is used.
* Metrics - Add Mode with RESET, DELTA and CUMULATIVE
Previously we were overloading RESET and DELTA but we really need
these to be 2 separate modes for the existing tests and the
get(reset) api
* Fix test TestNatKeyCacheWithForeignKey with cache stats reset
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* Add FinableQuery, StreamableQuery common interfaces for Query, DtoQuery, MappedQuery, SqlQuery
* QueryBean.cancel() added to support the new common FindableQuery interface
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
Adding this as I am seeing a LOT of cases where ebean can derive a good enough
error message (id case, plus single unique key case) that it is worth adding
this syntax sugar.
Yes we have discussed this a well it's not so useful for the multi-lingual /
non-english case and also marginal when the error message needs to be built
case.
* Version 18.3.0
* InsertOnConflict - change to exclude non-updatable and generated on insert only (e.g. @WhenCreated)
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* #2540 Add DTO mapping from Entity beans via generated mapper code
A large feature, refer to the docs / guides etc. This is a bit like MapStruct in that it generates mapping code via annotation processing but it ALSO creates an optimised FetchGroup for the destination DTO graph and it also implements a generic interface such that it can be fluently used via query.mapTo().
The value is that it automates the mapping code from entity -> dto plus optimises the query used to support building the DTO graph.
Expected to be better to use than MapStruct for ebean orm users.
* Bump ebean-annotation to 8.6 with the dto annotations
* Adjust the build order to via test dependency on ebean-api
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* #3848 Fix regression for Sql Server and Oracle with exists() introduced in 18.2.0
Fix#3848: exists() generates invalid scalar SQL on SQL Server and Oracle
Query.exists() generated `select exists(<subquery>)`, which is valid on
Postgres/H2/MySQL but rejected by SQL Server and Oracle since both only
support EXISTS as a predicate, not as a directly selectable scalar boolean
expression. ┃
┃
Add DatabasePlatform.existsWithCaseWhen/existsFromClause capability flags
and use them in CQueryBuilder.wrapSelectExists() to generate
`select case when exists(<subquery>) then 1 else 0 end` on platforms that ┃
need it, with an additional ` from dual` suffix for Oracle (which requires ┃
a FROM clause on every select). CQueryExists reads the boolean result via ┃
ResultSet.getBoolean(1), which correctly interprets the resulting 0/1 int.
* Fix tests TestInsertCheckUnique for Oracle and SQL Server
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* Throw exception on duplicate Database registration (#3838)
DatabaseFactory.create() with register(true) previously returned the
existing Database instance when another instance was already registered
under the same name (added in #3759). This silently caused two
independently created Database instances (e.g. with different
DataSourceConfig.url values) to collide/share state, since the
second "instance" was actually the first one in disguise.
Change this to throw an IllegalStateException instead, forcing
callers to either use a unique DatabaseConfig name or setRegister(false)
when the Database is not intended to be registered/looked up by name.
* Also handle deregistration on shutdown
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* checkUniqueness supports queryCache and skipClean
* Keep the DB.checkUniqueness() as per original (not overload there)
---------
Co-authored-by: Roland Praml <roland.praml@foconis.de>
* Add RawSqlBuilder.withPlaceholders() for CTEs and other complex SQL (#3649)
RawSqlBuilder.parse() uses keyword-based scanning to locate SELECT
columns and the WHERE/HAVING injection points. This fails for CTEs,
window functions and other complex SQL where "select"/"from" keywords
appear in places the scanner doesn't expect.
withPlaceholders(sql) skips SELECT/FROM column parsing entirely and
only locates the ${where}, ${andWhere}, ${having} and ${andHaving}
placeholder positions, requiring explicit columnMapping() calls (as
with unparsed()). This lets dynamic where()/having() expressions be
injected into otherwise unparseable SQL.
Also fixes two bugs in the underlying placeholder-position splitting:
- static SQL following a ${having}/${andHaving} placeholder (e.g. a
trailing ORDER BY) was silently dropped when both a where and a
having placeholder were present
- using only ${having}/${andHaving} (no where placeholder) caused a
dynamically added HAVING clause to be appended after trailing
static SQL, producing invalid SQL
Changes:
- RawSqlBuilder.withPlaceholders(sql) + SpiRawSqlService.withPlaceholders()
- DRawSqlParser.parseAsTemplate() / parseTemplate() - placeholder-position
only parsing, correctly splitting preWhere/preHaving/trailing SQL
- CQueryBuilderRawSql - skip column-list and "select" prefix handling
in template mode (signalled by an empty preFrom)
- Unit tests in DRawSqlServiceTest covering where/andWhere/having/andHaving
placeholder combinations, including the two fixed edge cases
- Integration tests in TestRawSqlWithPlaceholders (ebean-test) covering
CTE queries with dynamic where/having and verifying generated SQL
* Add examples test using query beans
* Add docs guides for RawSql
* Add ${orderBy} ${andOrderBy}
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* Add tenant-partitioned L2 caches (#2956)
When `tenantPartitionedCache` is enabled, each tenant gets its own
cache namespace (keys include the tenant id), improving cache-hit
ratio by preventing cross-tenant key collisions.
Refactor BeanDescriptorCacheHelp to abstract base class with two
concrete subclasses:
- BeanDescriptorCacheHelpFixed - static cache refs (original behaviour)
- BeanDescriptorCacheHelpPartitioned - per-request Supplier<> lookup
so the correct tenant-scoped cache is resolved on each access
Fix background-thread invalidation: clear(name) in partitioned mode
now scans all matching cache entries by key prefix rather than calling
tenantProvider.currentId() which is null/wrong on background executor
threads.
Add SpiCacheManager.clearTenant(tenantId) to allow removal of all
cache entries for a deactivated tenant, preventing unbounded memory
growth in long-running multi-tenant deployments.
Also load cache settings (cacheMaxSize, cacheMaxIdleTime, etc.) from
application properties - these were previously missing from loadSettings().
* Fix versions in test-java16
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
## Problem
SequenceIdGenerator captured a single DataSource at deploy time and held one shared pre-fetch buffer. Under multi-tenancy this is wrong:
- TenantMode.DB/DB_WITH_MASTER — there is no bootstrap DataSource, so BeanDescriptorManager passed null; sequence allocation couldn't resolve the current tenant's database.
- TenantMode.SCHEMA/CATALOG — a single shared buffer let one tenant's pre-fetched ids be handed to another (cross-tenant bleed), and pre-fetch used a connection that wasn't scoped to the requesting tenant.
(Supersedes the per-datasource delegator approach in #2305, which leaked via a WeakHashMap whose values strongly referenced the keys, only handled DB mode, and re-resolved the tenant on the background thread.)
## Fix:
Make SequenceIdGenerator itself tenant aware, keeping all platform modules untouched.
- New TenantConnectionSource (ebean-api, additive): optional interface a DataSource may implement — currentTenantId() + connectionForTenant(tenantId).
- SequenceIdGenerator: the shared idList/lock/loading flag become a per-tenant TenantBuffer keyed by tenantId in a ConcurrentHashMap. Connections are obtained per tenant. Background pre-fetch captures the tenant at submit time (the executor thread has no tenant in scope) and fetches by explicit tenantId — fixing a latent ThreadLocal-propagation bug.
- SequenceDataSource (ebean-core): adapts DataSourceSupplier to TenantConnectionSource; routes to the tenant DB (DB mode) or sets schema/catalog (SCHEMA/CATALOG).
- Wiring: InternalConfiguration exposes the DataSourceSupplier; BeanDescriptorManager wraps it only for dynamic-datasource tenant modes.
## Performance (single-tenant unaffected)
- A cached single buffer field short-circuits the ConcurrentHashMap for the non-tenant key.
- NONE/PARTITION pass the plain DataSource (not wrapped), so tenantSource == null and the hot path is just a couple of cheap branches — equivalent to the original.
## Compatibility
- Platform constructor signature (be, ds, seqName, allocationSize) unchanged — no changes to the 9 platform modules.
- One protected-method signature changed: getMoreIds(int) → getMoreIds(Object tenantKey, int). Rarely overridden (subclasses override getSql/readIds), but a source-incompat for any external custom platform that did.
## Tests
- TenantSequenceTest — DB-per-tenant: tenant 1 → 1,2,3; tenant 2 independently → 1.
- SequenceBatchIdGeneratorTest adapted to the per-tenant buffer.
- Existing sequence + multitenancy suites pass.
## Potential Follow-ups (not in this PR)
- Add a SCHEMA-mode test.
- Optional removeTenant(tenantId) hook if unbounded tenant churn is a concern (buffers hold only Longs + a lock, no DataSource, so no real leak).
- SimpleSequenceIdGenerator (non-batching) left as-is — already uses the txn connection.
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
Adds transaction-level control over whether Ebean auto-generates values for @WhenCreated, @WhenModified, @WhoCreated and @WhoModified properties.
Motivation
Backup/restore scenarios need to preserve the original audit timestamps and user values when re-inserting exported data. Without this, every save overwrites those fields with the current time/user.
Usage
try (Transaction txn = DB.beginTransaction()) {
txn.setGeneratedPropertiesEnabled(false);
bean.setWhenCreated(originalTimestamp);
bean.setWhenModified(originalTimestamp);
DB.save(bean);
txn.commit();
}
Behaviour
- Disabled (false): generated property values are only written if the property currently has a null value. Any non-null value set on the bean is preserved.
- @Version is unaffected: the version property always auto-increments regardless of this setting, preserving optimistic locking integrity.
- Default is true: all existing behaviour is unchanged.
Files changed
- Transaction — new setGeneratedPropertiesEnabled(boolean) with javadoc
- SpiTransaction — new isGeneratedPropertiesEnabled()
- SpiTransactionProxy — delegates both methods
- JdbcTransaction — field + implementation (default true)
- NoTransaction, ImplicitReadOnlyTransaction — no-op setter, true getter
- PersistRequestBean — onInsertGeneratedProperties, onUpdateGeneratedProperties, onFailedUpdateUndoGeneratedProperties all gate on isGeneratedPropertiesEnabled()
- TestGeneratedProperties — 3 new tests covering insert-preserves, insert-null-still-filled, and update-preserves
Supersedes
PR #2943 — same feature, renamed from setOverwriteGeneratedProperties to setGeneratedPropertiesEnabled for a clearer, positive-sense API.
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
* Add support for Java 21 SequencedSet and SequencedMap
Such that these can be used in place of Set and Map if desired.
* Refactor BeanSet, BeanList, BeanMap replacing setActualSet|List|Map
Replace with collectionAdd() and refresh() methods.
* Refactor rename method getBeanCollectionAdd() -> collectionAdd()
* Build needs to use Java 21 to support the multi-release jar
* Update SequencedSet etc from recent changes
* Update build, needs package to use MR-JAR
---------
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>