1300 Commits
Author SHA1 Message Date
robin.bygrave 3d2cc2d4af Version 18.1.0 2026-07-01 13:28:23 +12:00
Rob Bygrave 291966cea9 Test and Fix for #3806 - @OneToMany query that populates from the immutable cache gets NPE (#3807)
## Root cause

  A findList() triggers a secondary @OneToMany query that populates from the immutable cache. ImmutableBeanCaches.QueryLoader runs find(type).setUnmodifiable(true)...findMap(), which hits the L2 bean cache (cacheIdLookup → loadBeanDirect). Two coupled bugs in the unmodifiable path:

## 2 Bugs:
   1. Null pc (the NPE). In BeanDescriptorCacheHelp.loadBeanDirect, the if (context == null) context = new DefaultPersistenceContext() fallback was nested inside if (!unmodifiable). So an unmodifiable load passed a null context to CachedBeanDataToBean.load; converting a @ManyToOne (refBean → contextGet → pc.get) NPE'd — exactly Eddie's trace.
   2. Mutable reference can't freeze. Once a context was provided, refBean created the @ManyToOne ref via createRef → a mutable InterceptReadWrite bean. The subsequent unmodifiableFreeze then threw UnsupportedOperationException("never expected") (only InterceptReadOnly is freezable).

  ## Fix (2 files)

   - BeanDescriptorCacheHelp.loadBeanDirect — always ensure a non-null context before CachedBeanDataToBean.load (hoisted out of !unmodifiable).
   - BeanPropertyAssocOne.setCacheDataValue/refBean — when the owning bean is unmodifiable (derived via !(intercept instanceof InterceptReadWrite), the same idiom CachedBeanDataToBean already uses), create the ref with createReference(unmodifiable, false, id, pc) → a freezable InterceptReadOnly reference. Also guards contextGet against null. Modifiable path is unchanged.

## Notes
  The "fetch(path) without a FetchGroup" clue

  That's the trigger, not a misuse. A restricting FetchGroup can exclude the @ManyToOne, so no refBean runs and the bug stays hidden. The default/fetch("path") select includes the FK, so the cached-bean conversion creates the assoc-one reference and hits the bug. His usage was fine — this was an Ebean gap (no immutable-cached entity with a @ManyToOne was test-covered).
2026-07-01 12:54:12 +12:00
robin.bygrave ad05ed051b Modify SequenceIdGenerator to internally use ArrayDeque 2026-06-30 22:49:19 +12:00
Rob Bygraveandrobin.bygrave 01b8c3dbcb Multi-tenant aware DB sequence id generation (replaces #2305) (#3805)
## 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>
2026-06-30 22:28:28 +12:00
robin.bygrave 443b68a3b0 For #3082 - Fix and test for History findVersions() that joins to other history table
Bug (#3083): findVersions() on a @History root that joins to another @History entity bound the joined table's effective-date predicate with a null as-of timestamp → systime <@ null (Postgres) / sys_period_start <= null (H2) → zero rows.

  Fix (CQueryPredicates.bind(), +6 lines): when the as-of is null but there are history-view join predicates to bind (asOfTableCount > 0 — only ever true for findVersions/findVersionsBetween), default to the current timestamp. Semantics: all versions of the root, joined to related @History entities as they are now. The root table is untouched (still returns all versions).

  Test ported from #3082 :
   - New HistoryManyToOne entity (@History + @SoftDelete) — kept lean, dropped the PR's unneeded @OneToMany
   - HistorylessOneToOne gains @ManyToOne HistoryManyToOne
   - New testVersionsWithHistoryOverHistoryless() — replaced the PR's always-failing placeholder (assertSql(...).contains("this does not exist")) with real assertions: count == 1 and SQL contains no asOf null bind
   - Left the existing @OneToOne unchanged (the PR's optional = false tweak to the shared model wasn't needed)
2026-06-30 21:08:30 +12:00
Andrey Glushkov 98f0d42b7e Fix: defer savepoint cache changes to parent transaction commit (#3804) 2026-06-30 18:47:24 +12:00
Rob Bygraveandrobin.bygrave 97bd0e1bb8 Add Transaction.setGeneratedPropertiesEnabled(boolean) (#3799)
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>
2026-06-26 18:53:29 +12:00
Rob Bygraveandrobin.bygrave d7d040d031 Add @Formula2 support on @ManyToOne association properties (#3798)
Extends @Formula2 so it can be placed on a @ManyToOne field. Instead of embedding physical SQL aliases (as @Formula(join=...) requires), a @Formula2 expression uses logical bean paths — the required joins are derived automatically.

  Example
``
   // Before — physical SQL, brittle alias wiring:
   @Formula(select = "coalesce(${ta}.some_bean_id, j1.some_bean_id)", join = PARENTS_JOIN)
   @ManyToOne EBasic effectiveBean;
```
```
   // After — logical paths, joins resolved automatically:
   @Formula2("coalesce(someBean.id, parent.someBean.id)")
   @ManyToOne EBasic effectiveBean;
``
  The generated SQL is identical; the annotation is far more readable and maintainable.

  What changed

  Annotation parsing (AnnotationAssocOnes) — @Formula2 on a @ManyToOne is now recognised and the expression parsed into a select fragment and a set of dependency join paths.

  Query tree building (SqlTreeBuilder) — three scenarios all handled correctly:

   - Fetched as a tree node — dependency joins are inserted before the formula2 join using addChildFirst
   - Partial parent fetch — dependency joins are registered even when the parent chunk only selects its ID column
   - Predicate-only (where clause, no fetch) — addFormula2JoinsFromPredicates runs before buildSelectChain so dependency join paths are populated before buildExtraJoins constructs the extra-join tree; IncludesDistiller uses addChildFirst to preserve ordering within the extra-join tree

  Init ordering (BeanDescriptorManager) — initFormula2Properties() moved to a dedicated pass 5, after all descriptors are fully initialised, so cross-descriptor path resolution (e.g. parent.parent.someBean.id) is always safe.

  SqlTreeNodeExtraJoin — gained addChildFirst() to match SqlTreeNodeBean, enabling formula2 dependency joins to be prepended ahead of the formula2 property join in the extra-join tree.

  Fixes

  Supersedes and resolves #2773 — the reported bug (wrong join ordering when combining fetch and where on a formula-joined field) is eliminated for ChildPerson.effectiveBean and ParentPerson.effectiveBean, which are now expressed as @Formula2.

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-06-25 22:18:10 +12:00
Rob Bygraveandrobin.bygrave d5f32690ea Support aggregation functions like sum on Formula2 (#3796)
Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-06-24 21:44:40 +12:00
robin.bygraveandCopilot 691f153d89 Fix DtoMetaDeployProperty: remove unused Method param; fix findField superclass traversal; wire up findMetaAnnotations for setter annotation support
- DtoMetaDeployProperty: remove unused Method parameter from constructor
- DtoMetaProperty.findField: use loop variable 'type' (not outer 'dtoType') so
  superclass fields are correctly found
- DtoMetaProperty constructor: call findMetaAnnotations() which merges both
  field and setter annotations, rather than reading field annotations only
- DefaultTypeManager: keep both MethodType import and Annotation import
- Fix missing imports in EbeanServerFactory_ServerConfigStart_Test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-24 18:16:41 +12:00
Roland Praml dd1acf58a0 DbJson Support for Dto-Queries 2026-06-24 18:14:45 +12:00
Rob Bygrave 7bf8f7fbce With Database register(true) check for existing registered Database by name (#3759)
And return the existing Database instance if present.

In theory this should not be needed. There are some test setups that hit this situation.
2026-06-24 18:02:45 +12:00
Rob Bygraveandrobin.bygrave 490c70a7b7 PR #3656 fix - lazy initialised O2M relation are not correctly persisted on subsequent saves (#3793)
- Root cause: SaveManyBeans.removeAssocManyOrphans() only called setModifyListening when insertedParent=true. A first save with null O2M skipped it; the lazily-initialized collection on subsequent saves had no listen mode → .clear() untracked → orphan not deleted.
 - Fix: Added else { setListenMode(c, many); } to set the listen mode for existing collections that were never initialized (uses the existing null-guard helper).
 - Tests: testModifyListenModeSet2 now passes; all 26 cascade tests green.

-------------
Original #3656 description:

We found an issue, when O2M relations are not correctly persisted to the DB.

This happens, when

a bean is saved and the O2M property is empty
something is added and cleared again in two subsequent saves.
the same master-bean object has to be used
The issue here is, that the BeanCollection is lazily initialized with no modifyListenMode set after the first save.
This happens only for O2M relations with no order column. (Others work fine) See: https://github.com/ebean-orm/ebean-agent/blob/d4c40f1ce85c58f99cb0a85152aaa0a0075a9c01/ebean-agent/src/main/java/io/ebean/enhance/entity/FieldMeta.java#L469

And we need also a save, where the bean is saved with an empty/null value in the O2M property.
Subsequent saves will not update the modifyListenMode. See SaveManyBeans

      if (insertedParent) {
        // after insert set the modify listening mode for private owned etc
        c.setModifyListening(many.modifyListenMode());
      }

We found this in one of our unit-tests, where we've configured a bean for different states. It is probably something, that should not be too critical in real code, as you normally save a bean only once (When the bean was retrieved from DB, it should not occur)

Co-authored-by: robin.bygrave <robin.bygrave@eroad.com>
2026-06-24 17:05:11 +12:00
Rob Bygrave ca19a78c23 Two related tidy-ups to query-cache dependent-table handling. (#3792)
This is a replacement for #3150

 **1. Read `dependentTables` from the query plan (single source of truth)**

 `OrmQueryRequest` accumulated query-cache `dependentTables` into a field via
 `addDependentTables(...)`, fed from per-CQuery `dependentTables()` helpers. The
 `CQueryPlan` already owns this set, so the copying is redundant.

 - `putToQueryCache(...)` now reads `dependentTables` directly from the request's
   `CQueryPlan` at cache-put time.
 - Removed the `OrmQueryRequest.dependentTables` field and `addDependentTables(Set)`.
 - Removed the now-unused `dependentTables()` helpers from `CQuery`,
   `CQueryRowCount` and `CQueryFetchSingleAttribute` (and their `Set` imports).

 This is behaviour-preserving: each CQuery is built with exactly the plan stored
 under `request.queryPlanKey`, so `request.queryPlan().dependentTables()` is the
 same set the per-CQuery helpers returned.

 **2. Fix a query-plan trim race that could null the plan at put time**

 A freshly built `CQueryPlan` was stored in the plan cache with
 `lastQueryTime == 0`, making it immediately eligible for `trimQueryPlans`
 (runs every 60s; TTL default 300s) during its *first* execution. If the trim
 fired mid-execution, `request.queryPlan()` could return `null` at put time — and
 a cache entry with `null` dependentTables is never invalidated by table
 modifications (`TableModState.isValid`), i.e. latent stale data.

 - `CQueryPlanStats` now initialises `lastQueryTime` to construction time, so a
   new plan is only trim-eligible after a genuine TTL idle. `trimQueryPlans` is
   the sole consumer of `lastQueryTime()`, so this is safe.
 - `putToQueryCache(...)` skips the put when the plan is `null` (fail safe rather
   than caching an un-invalidatable entry) for the residual pathological case
   (a single query running past the TTL on first execution).

 ### Tests

 - Added `testFindSingleAttributeOnDependent` and `testFindListOnDependent` to
   `TestQueryCacheTableDependency`, asserting cache-hit then dependent-table
   invalidation for the single-attribute and findMany paths (the count path was
   already covered).
 - All cache/query tests pass: 69 in `ebean-test` `org.tests.cache.**`, 929 in
   `ebean-core` cache + query packages.
2026-06-23 22:44:28 +12:00
Rob Bygraveandrobin.bygrave 66d599faa2 Add support for Java 21 SequencedSet and SequencedMap (#3302)
* 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>
2026-06-23 21:43:19 +12:00
robin.bygrave f50d56faee Add support for @Formula2 with logical paths and automatic joins
@Formula2 is a logical-path alternative to @Formula and a full replacement for it.

 Where @Formula requires physical SQL (${ta} placeholders and hand-written joins),
 @Formula2 takes a property-path expression and resolves the required joins
 automatically:

     @Formula2("coalesce(familyName, parent.familyName)")
     String derivedFamilyName;

 The path prefixes (parent, parent.parent, ...) define the joins needed to
 satisfy the formula. Supported everywhere @Formula is:

   - select()      — root and nested-fetch paths, auto-joined
   - where()       — predicate paths trigger the extra joins
   - orderBy()     — order-by paths trigger the extra joins
   - having()
   - DDL           — excluded from generated columns (read-only, like @Formula)

 Default behaviour matches @Formula: included in the default select unless marked
 @Transient, in which case it is opt-in but still auto-joins when selected.

 Implementation:
   - Parse @Formula2 after associations are wired, into a logical select fragment
     plus the set of join paths (BeanDescriptor/BeanProperty).
   - DeployPropertyParser registers formula2 join paths into query includes so the
     existing predicate-include -> extra-join machinery builds the LEFT JOINs.
   - ElPropertyChain prefixes all ${}/${path} placeholders for nested-path use.
   - SqlTreeBuilder accumulates formula2 joins for select paths.
2026-06-23 19:02:37 +12:00
Rob Bygrave a7fddf1981 Version 18.0.0 2026-06-22 21:11:45 +12:00
Rob Bygrave 852f199ffb Merge pull request #3789 from ebean-orm/feature/avaje-json-core
Refactor from Jackson Core to avaje-json-core
2026-06-22 21:02:52 +12:00
Rob Bygrave 0e6cd9db8c Version 16.11.1 2026-06-22 20:33:00 +12:00
robin.bygrave 230d7a36a7 Simplify @DbJson Map dispatch and support Map<Enum,String>
Replace isMapValueTypeObject + isMapStringString with isBuiltinJsonMap:
 the built-in JSON map handles a String or enum key with a String, Object
 or wildcard value. This also routes Map<Enum,String> to the built-in type
 and fixes non-String/enum keys with Object value (e.g. Map<Integer,Object>)
 being mis-routed to the built-in type (ClassCastException) instead of the
 object mapper.
2026-06-19 21:47:20 +12:00
robin.bygrave 10bf5dbbbd Support Map<Enum,Object> in @DbJson(B) fields
Addresses #3735. Enum-keyed JSON maps previously failed (enum key cast to
 String). Add ScalarTypeJsonMapEnum which converts enum keys via their
 ScalarType (honouring @DbEnumValue) and reuses ScalarTypeJsonMap for all
 storage/platform handling, so it works for VARCHAR/CLOB/BLOB and Postgres
 JSON/JSONB with no per-storage variants.

 - JsonStorage now reports jdbcType(), enabling a shared storageFor(...) reused
   by the plain and enum-key Map types
 - DefaultTypeManager routes Map<Enum,Object> to the new type and calls
   setAccessible on the @DbEnumValue method (supports nested/non-public enums)
 - add TypeReflectHelper.getMapKeyTypeRaw and TestEnumKeyMap
2026-06-19 20:03:27 +12:00
robin.bygrave 4fd32b45d2 Regression introduced by #3779 in sql inline comment for label (missing bean type prefix)
So when we used to get an inline comment like:
```sql
select /* Customer.hiLabel */ ...
```
We started to instead have (missing bean type):
```sql
select /* hiLabel */ ...
```
This fixes that regression that was introduced in #3779
2026-06-18 22:23:22 +12:00
robin.bygrave 313fdda857 Refactor from Jackson Core to avaje-json-core 2026-06-18 22:00:33 +12:00
mvanhorn 6d53e89a80 Fix refresh on soft-deleted beans 2026-06-14 02:25:47 -07:00
Rob Bygrave 7c5ee5b555 Version 16.11.0 2026-06-14 21:06:57 +12:00
robin.bygrave 6295faa351 Version 16.11.0-RC1 2026-06-13 10:46:40 +12:00
robin.bygrave 7b8e1713dd Test: Fix for EA build to skip the TestMockitoMock 2026-06-11 21:31:22 +12:00
robin.bygrave 3ea9a5fa7f Test: Fix DtoQueryPlanCaptureTest by draining plans 2026-06-11 20:28:10 +12:00
robin.bygrave 4fab5dfe4b Test: support EA build via net.bytebuddy.experimental true 2026-06-11 19:59:12 +12:00
robin.bygrave 9fac046f39 Dep: bump ebean-datasource to 10.7 2026-06-11 18:57:34 +12:00
robin.bygrave 50bfd04987 Tests: Oracle CI, limit connections and prepared statements for oracle CI test 2026-06-11 16:56:04 +12:00
robin.bygrave d71ced32e8 Tests: Oracle CI, limit connections and prepared statements for oracle CI test 2026-06-11 16:26:29 +12:00
robin.bygrave 6722cf50e9 Tests: Fix DbMigrationTest for waits and SqlQueryPlanCaptureTest for draining 2026-06-11 14:53:55 +12:00
robin.bygrave 13cebbcacb Tests: Fix ResourceEntityTest for test pollution 2026-06-11 14:19:49 +12:00
robin.bygrave e70777bdb6 Tests: Adjust SqlQueryPlanCaptureTest (for clash on sql) 2026-06-11 13:30:46 +12:00
robin.bygrave 819aaece4d SqlQuery - add support for query plan capture 2026-06-09 00:20:53 +12:00
robin.bygrave 82e8494f42 Add profile location test 2026-06-08 23:47:21 +12:00
robin.bygrave a7d1253dba DtoQuery skip bind capture when it's actually an orm query 2026-06-08 23:31:52 +12:00
robin.bygrave a081d08621 DtoQuery initiate the bind capture 2026-06-08 23:24:13 +12:00
robin.bygrave 8c37b53bad DtoQuery modified to support query plan capture 2026-06-08 23:06:09 +12:00
Rob Bygrave 3f6d565800 Version 16.9.0 2026-06-08 19:09:46 +12:00
Rob Bygrave 032f4de857 Version 16.8.0 2026-06-06 20:06:30 +12:00
Rob Bygrave a8e92791cd Version 16.7.0 2026-06-03 07:44:59 +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
Sergey Kuznetsov 46bb1ca060 Add timeout support to UpdateQuery and DefaultUpdateQuery (#3771) 2026-05-14 15:25:07 +12:00
Rob Bygrave 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 63a1e9907d Version 16.6.0 2026-05-06 23:06:25 +12:00
Rob Bygrave 4dde7064ba Add ImmutableBeanCache - some parts of the query result graph populated via ImmutableBeanCache (#3753)
* Change [implicit] Lazy loading to be by TYPE rather than by PATH

We have some graph models where a common type (in the tests it is Label)
is used in many different *paths* of the graph. When we are loading via
*path* then all the loading of the Labels isn't in a single batch / load context
but instead split into different load contexts PER PATH.

This change, means that lazy loading operates by TYPE instead of by PATH.

In the tests, all the Labels are read via a single lazy loading query rather
that one lazy loading query per PATH, and this is more efficient.

* ImmutableBeanCache part 1

* ImmutableBeanCache part 2

* ImmutableBeanCache part 3 - add ImmutableBeanCaches

* ImmutableBeanCache - add propagation of caches to secondary queries

* ImmutableBeanCache - use and improve BeanDescriptor.merge() to positional

Using positional avoids the lookup by property name.

* ImmutableBeanCache - add unit testing with BeanDescriptorMergeTest

* ImmutableBeanCache - add testing for the further lazy loading on mutable beans

Also add doc / guides

* Add ImmutableCacheBuilder with underlying DefaultServerCache implementation

This provides a cache with maxSize, maxIdleSeconds, maxSecondsToLive

* Improve javadoc and doc / guides for ImmutableBeanCache

* Add cache invalidation for ImmutableBeanCache

* Use immutable cache direct hits for unmodifiable assoc-one refs

 - add ImmutableBeanCache.getIfPresent() as a non-loading probe
 - use immutable cache hits in AssocOneHelp / AssocOneHelpRefInherit
 - avoid creating ref beans and merge/copy for unmodifiable cache hits
 - keep existing mutable-query and miss/backfill behavior unchanged
2026-05-06 22:35:30 +12:00
Rob Bygrave 7e4a2db9aa Add Query alsoIfPresent() as helper for Nullable conditional expressions (#3756)
Add this as a helper for when using alsoIf(BooleanPredicate) seems overkill
and there isn't a built in IfPresent option for the desired expression and
wanting to keep the fluid style.
2026-04-28 23:24:50 +12:00