Skip to content

Releases: FlameyosSnowy/Universal

Universal - v7.1.6

22 Apr 06:26
cc3068c

Choose a tag to compare

Fixes

  • Eliminate getDeclaredConstructor() reflection. (Making Universal fully graalvm-safe)
  • Use Types instead of string matching for safer processing.
  • Make TypeResolver a more trusted source of truth and skipping many checks (which is the intended behavior)

Improvements

  • Improve the performance by reusing computations.

Universal - v7.1.5

17 Apr 13:43

Choose a tag to compare

Improved

  • Improved performance of SQLDatabaseParameters construction heavily
  • Made the cache configuration of AbstractRelationshipHandler configurable
  • Updated to Java 25

Fixed

  • Fixed infinite resizing in AbstractRelationshipHandler; replaced with LRU eviction
  • Fixed loadFromDatabaseBatch not adding to available caches
  • Fixed isEnabled and setEnabled not being recognized by the compile time processor when working with getters and setters of boolean fields (The old workaround was to do getIsEnabled and setIsEnabled or not add is at all)
  • Fixed record-style getters and setters not being recognized by the compile time processor
  • Fixed mongodb select queries sometimes not working when working with relationships

Full Changelog: 7.1.1...7.1.5

Universal - v7.1.1

10 Mar 13:22

Choose a tag to compare

GraalVM Native Image Support

Replaced Class.forName bootstrapping with ServiceLoader, making Universal fully compatible with GraalVM native image compilation. All generated repository factory classes now implement GeneratedRepositoryFactory and are discovered at startup via ServiceLoader without requiring reflection configuration.

JSON / PostgreSQL Fix

Fixed incorrect SQL parameter placeholder for JSON fields when targeting PostgreSQL. Parameters are now emitted as ?::jsonb instead of bare ?, resolving type cast errors on insert and update operations.

JSON on SQL

Fixed an issue where JSON fields were not being handled correctly during SQL query execution, causing serialization or persistence failures for @JsonField-annotated columns.

@JsonVersioned and @JsonIgnoreNulls

Both annotations were previously accepted by the processor but had no effect at runtime, this behavior has been corrected.
@JsonVersioned now participates in version tracking and @JsonIgnoreNulls has been removed

@Named Annotation

Fixed a bug where the @Named annotation was not being applied correctly in certain contexts, causing the wrong column name to be used in generated SQL.

DefaultSession Batch Execution Logic

Fixed an inverted boolean check in DefaultSession that caused batch buffering to behave the opposite of its intended configuration - modifications were being executed immediately when buffering was enabled, and batched when it was disabled. This is now corrected.

Full Changelog: 7.1.0...7.1.1

Universal - v7.1.0

05 Mar 10:32

Choose a tag to compare

Session API overhaul

more like a fix, lol

FileSession and NetworkSession now support all SessionOptions

Both microservice session implementations previously ignored session options
entirely. They now honour the full set supported by DefaultSession:

  • NO_CACHE - bypasses the in-memory cache on reads and suppresses
    write-back. Every findById and findAllById call goes straight to storage.
  • BUFFERED_WRITE - defers all writes to a pendingOperations queue
    and flushes them only when commit() is explicitly called. Auto-flush is
    suppressed while this option is active.
  • LOG_OPERATIONS - emits an INFO log entry via Logging for every
    INSERT, UPDATE, and DELETE, including the entity value.
  • CASCADE - propagates writes through the relationship graph via
    DefaultGraphCoordinator, with a non-cascading inner view passed to the
    coordinator to prevent infinite recursion.

Rollback callbacks now correctly restore previous cache state for updates and
deletes in both sessions, matching the contract DefaultSession has always had.


New bulk methods across the entire session stack

DatabaseSession, DefaultSession, FileSession, and NetworkSession all
gained the following methods. The interface provides default implementations
that delegate to the single-entity methods, so any custom DatabaseSession
implementation inherits them automatically at no cost.

Method Description
insertAll(Iterable<T>) Insert a collection of entities
updateAll(Iterable<T>) Update a collection of entities
deleteAll(Iterable<T>) Delete a collection of entities
deleteAllById(Iterable<ID>) Delete by primary key, resolving through cache
findAllById(Collection<ID>) Batch read; cache hits are short-circuited, misses are fetched in one call
*Async variants Every bulk method has a CompletableFuture-based async counterpart

DefaultSession.findAllById goes a step further: it separates cache hits from
misses and issues a single repository.findAllById(missingIds) call for the
remainder, eliminating per-ID round-trips.


Session introspection API

Three new methods are available on DatabaseSession and all implementations:

void setAutoFlush(boolean autoFlush)  // enable / disable threshold-based flushing
int  getBatchSize()                   // returns -1 for non-batching sessions
int  getPendingOperationCount()       // staged but not yet committed operations

DefaultSession returns -1 for getBatchSize() and reflects only its
pendingOperations list in getPendingOperationCount() (non-zero only under
BUFFERED_WRITE). FileSession and NetworkSession sum all four staging
collections (insert map, update map, delete list/set, pending ops).


Relationship handler overhaul

AbstractRelationshipHandler has been significantly upgraded. The previous
implementation is still available as a reference but the active implementation
now includes:

  • Tiered L1 + L2 cache - a ThreadLocal<WeakReference<Map>> L1 cache sits
    in front of the shared ConcurrentHashMap L2 cache. Hot-path reads never
    touch shared memory. L1 hits are promoted automatically.
  • Query result cache - identical WHERE field = id queries are deduplicated
    so repeated handleOneToManyRelationship calls for the same parent do not
    re-query the database.
  • Adapter cache - resolveAdapter now caches results in a per-handler
    ConcurrentHashMap keyed on fieldName:entityClass, eliminating repeated
    RepositoryRegistry lookups.
  • Cache key pooling - a 1 024-slot intern pool eliminates String
    allocation on cache key construction for Integer/Long IDs (the common
    case).
  • MANY_TO_ONE batch prefetch - the previous prefetch() silently ignored
    MANY_TO_ONE fields. batchLoadManyToOne is now implemented and included
    in all prefetch paths.
  • Parallel batch loading - when setParallelPrefetchEnabled(true) is set,
    the three batch-load types (ONE_TO_ONE, ONE_TO_MANY, MANY_TO_ONE) run
    concurrently via a daemon ExecutorService with a 30-second timeout.
  • Deep / recursive prefetch - prefetchDeep(Collection, String...) accepts
    dot-notation field paths (e.g. "faction.warp", "faction.members") and
    recursively prefetches relationship graphs up to a configurable depth.
  • Cache warming by ID - warmCache(List<ID>, Set<String>) preloads
    relationship data from IDs alone, without needing entity objects.
  • Auto-behaviours - three opt-in flags, all off by default:
    autoWarmCache, autoDeepPrefetch (with configurable depth), and
    parallelPrefetchEnabled.
  • getRelationshipSize - returns the count of a ONE_TO_MANY collection
    without loading it. Uses the cached size if the collection is already loaded,
    otherwise issues a COUNT(*) query.
  • Performance metrics - getMetrics() returns a RelationshipMetrics
    record exposing cache hit / miss counts, L1 vs L2 hit breakdown, per-field
    query counts, cache sizes, and configuration state. resetMetrics() resets
    all counters.
  • Proper lifecycle - clear() now wipes the query result cache, adapter
    cache, and cache key pool in addition to the relationship cache. clearAll()
    also wipes the static nameCache shared across instances. shutdown()
    gracefully terminates the parallel executor.

Array handling fixes

InsertEntityGenerator / InsertCollectionEntitiesGenerator

Array fields are now routed through mutually exclusive paths depending on
whether the database supports native array types:

  • Native path (supportsArraysNatively() == true) - the array is bound
    directly via stmt.set() inside insertEntity.
  • Non-native path - the array is skipped in insertEntity and handled by
    handler.insertArray() inside insertCollectionEntities.

Previously both paths could fire for the same field.

ValueReaderGenerator

The generated readN() helper methods for array fields now emit the same
branch:

if (result.supportsArraysNatively()) {
    return (T[]) result.getArray(columnName, T.class);
} else {
    return result.getCollectionHandler().fetchArray(id, columnName, T.class, repositoryModel);
}

Maps and collections are unaffected - they are always stored relationally.


Uniform replaces Jackson for JSON serialisation

All JSON serialisation and deserialisation has been migrated from Jackson to
Uniform, a purpose-built codec layer developed in-house.

Why the switch?

| | Jackson | Uniform |
|---|---|---|---|
| Startup cost | High (reflection scan) | Little-to-none (compile-time) |
| Allocations per parse | High| Minimal |
| Custom type support | Annotation-driven | First-class |
| SIMD | No | Yes (stage-1 SIMD, stage-2 SWAR) |

Jackson's reflection-based approach was the largest single contributor to cold
start time in serverless and microservice deployments. Uniform generates codec
classes at compile time via the annotation processor, eliminating all runtime
reflection and producing significantly smaller allocation profiles per parse.

Migration impact

No changes are required for entity classes. Annotations such as @JsonProperty
are no longer needed - field-to-column mapping is handled by the existing
@Repository / @Column metadata that the processor already understands.

If you were accessing the Jackson ObjectMapper directly (e.g. via
adapter.getObjectMapper()), that method has been changed to return a JsonAdapter


Code maintainability improvements

UnifiedFactoryGenerator split into focused generators

The original 900-line UnifiedFactoryGenerator has been refactored into ten
single-responsibility classes:

Class Responsibility
UnifiedFactoryGenerator Orchestrator - delegates, owns nothing
RepositoryModelGenerator Generates *_RepositoryModel_Impl
ObjectModelGenerator Generates *_ObjectModel
RelationshipLoaderGenerator Generates *_RelationshipLoader
LazyProxyGenerator Generates lazy entity / collection proxies
RelationshipMethodGenerator oneToOne / oneToMany / manyToOne methods
CollectionLoaderMethodGenerator loadList / loadSet / loadMap / loadArray
InsertEntityGenerator insertEntity method body
InsertCollectionEntitiesGenerator insertCollectionEntities method body
GeneratorUtils Stateless shared utilities

Each generator is independently unit-testable, depends only on the types it
needs (Types, Elements, Filer, Messager), and can be extended or
replaced without touching the others.

Bug fixes

  • DefaultSession.insertDirect / updateDirect - cache write-back after a
    successful operation was not guarded by NO_CACHE. Both now skip the
    cache.put() call when NO_CACHE is set.
  • FileSession / NetworkSession rollback - rollback callbacks (which
    restore previous cache state for updates and deletes) were not being invoked.
    Both sessions now run callbacks before clearing state, matching
    DefaultSession's contract.

Universal - v7.0.0

15 Feb 10:46

Choose a tag to compare

Changelog

Major Architecture Changes

Code Generation System (Full Rewrite of Hot Paths)

  • Replaced reflection-based hot paths with compile-time code generation.
  • Eliminated runtime TypeResolver scanning in performance-critical sections.
  • Improved validation during compilation instead of failing at runtime.
  • Split monolithic files into smaller, responsibility-driven modules.
  • Reduced object allocations and removed unnecessary intermediate collections.

Impact:

  • Significant performance gains.
  • Lower GC pressure.
  • Better startup characteristics.
  • Stronger compile-time guarantees.

Consistency & Read Policy System

Introduced a formal consistency contract for reads:

  • Consistency.STRONG
  • Consistency.EVENTUAL
  • Adapter-defined default behavior

Features:

  • Explicit read policies enforced at adapter level.
  • Strong reads bypass caching layers.
  • Eventual reads prefer cache with fallback behavior.
  • Consistency abstraction decoupled from caching implementation.
  • Added @Resolves annotation for compile-time resolver binding.
  • Added count function for efficient counting of elements in the database, works for all SQL, MongoDB, File, Network, etc
  • Added match (MUST make a text index for this to work in MongoDB), like, and, or and others in the new Query API

This enables predictable read guarantees across SQL and Mongo adapters.


Query & JSON System Overhaul

New Query API

  • Fully redesigned query DSL.
Query.select()
         .where("age")
             .gte(18)
         .where("name")
             .like("A%")
         .build();
  • Better composability.
  • Improved validation logic.
  • More efficient execution planning.
  • New aggregation API (including but not limited to the example):
var query = Query.aggregate()
    .select(
        Query.field("departmentId"),
        Query.field("status").countIf(eq("active")).as("activeCount"),
        Query.field("status").countIf(eq("inactive")).as("inactiveCount"),
        Query.field("salary").avg().as("avgSalary")
    )
    .groupBy("departmentId")
    .having().field("id").count().gt(5)
    .orderBy("avgSalary", DESC)
    .build();

var results = repository.aggregate(
    Query.aggregate()
        .select(
            Query.field("status").countIf(eq("active")).as("active_count"),
            Query.field("status").countIf(eq("inactive")).as("inactive_count")
        )
        .build()
);

var deptStats = repository.aggregate(
    Query.aggregate()
        .select(
            Query.field("departmentId"),
            Query.field("id").count().as("emp_count")
        )
        .groupBy("departmentId")
        .having().field("id").count().gt(10)
        .orderBy("emp_count", DESC)
        .build()
);

New JSON API

  • First-class JSON system via JsonField annotation (which means you don't need to make TypeResolvers just to serialize something in JSON) and filtering by JSON
  • Powered by Jackson... for now 😉

Session API Improvements

Added:

  • run()
  • runVoid()
  • runAsync()

These provide automatic atomic session handling with consistent lifecycle management.

Improves safety around transaction-like behavior.


Kotlin Support

  • Added Kotlin extension module.
  • Support for suspend functions.
  • Improved Kotlin DSL ergonomics.
  • Better type inference and null-safety alignment.
  • Kotlin QOL improvements.

Now Kotlin usage feels native instead of bolted on.


Performance Optimizations

  • Replaced enhanced for-loops with raw loops in critical paths.
  • Heavy lambda, string-building and collection memory allocation micro-optimizations.
  • Removed unnecessary collection allocations.
  • Eliminated debug artifacts.
  • Heavy internal optimizations that compound under load.
  • Micro-optimized relationship traversal

These changes focus on hot-path efficiency rather than superficial tuning.


Fixes & Stability

  • Fixed microservices TypeResolver registration issues.
  • Handled no-ID entity scenarios correctly.
  • Multiple bug fixes across relationship and query layers.
  • Improved test coverage and reliability.
  • Fixed edge-case validation issues.
  • Improved handling of “no ID” edge cases
  • Fixed mongodb (as a whole, lol) not working

Tooling & Platform

  • Upgraded to Gradle 9.
  • Updated Kotlin version.
  • Version bumps and dependency updates.
  • Improved build modularization.

Date/Time Improvements

  • Added MonthDay support.
  • Improved “now” processing

Universal - v6.1.6

01 Jan 12:29

Choose a tag to compare

Improvements

  • Add RepositoryAdapter#findIterator and RepositoryAdapter#findStream for lazily iterating over ResultSets, Documents and File/Network data MAKE SURE TO CLOSE THOSE ITERATORS/STREAMS BECAUSE THEY CONTAIN DATABASE RESOURES
  • Add SelectQuery#orderBy(List)

Bug Fixes

  • Fix some memory leaks
  • Fix ResultSet not being properly closed in collection resolvers
  • Fix caches being created and used even when caching is not enabled.
  • Other misc fixes.

Full Changelog: 6.1.5...6.1.6

6.1.5

31 Dec 10:04

Choose a tag to compare

  • Fix Collections like List, Set or Queue if DatabaseImplementation#supportsArrays equals true.

Full Changelog: 6.1.4...6.1.5

Universal - v6.1.4

30 Dec 17:46

Choose a tag to compare

  • Improve deepInfo debugging in QueryParseEngine

Universal - v6.1.3

30 Dec 17:36

Choose a tag to compare

Improvements

  • Add new java.time classes to support, such as:
    • Year
    • YearMonth
    • Month
    • ZoneId
    • TimeZone
    • OffsetTime
  • Improved Multimap and collection support, it now Supports:
    • Queue interface
    • Deque interface
    • Multimaps with Queue/Deque/Set

Breaking changes

  • Remove cassandra
    We have decided that Cassandra is not a good fit for the relationship-heavy philosophy of Universal and have not only discontinued it's maintenance but we have removed it entirely.
    It will not be a good fit for our future and not for our users, please use the direct Cassandra API (advised) or any library/object mapper that supports all you need for Cassandra support.

Universal - v6.1.2

30 Dec 14:22

Choose a tag to compare

  • Fix enums not working

Full Changelog: 6.1.1...6.1.2