We now use ConcurrentHashMap as type instead of HashMap to properly synchronize concurrent updates to missing cache elements.
The previously used HashMap was not thread-safe so concurrent modifications resulted in ConcurrentModificationException.
We now apply best-effort caching instead of atomic caching for custom conversions and type mapping. This change is a workaround for a Java 8 bug in ConcurrentHashMap where the computeIfAbsent(…) operation unconditionally locks nodes even when the node is already present.
The workaround is to assume the optimistic case by looking up the key and then falling back to computeIfAbsent if the key is absent.
Before:
TypicalEntityReaderBenchmark.simpleEntityReflectivePropertyAccessWithCustomConversionRegistry thrpt 10 6487423,969 ± 349449,326 ops/s
DefaultTypeMapperBenchmark.readTyped thrpt 10 38213392,961 ± 5080789,480 ops/s
DefaultTypeMapperBenchmark.readUntyped thrpt 10 47565238,929 ± 855200,560 ops/s
After:
TypicalEntityReaderBenchmark.simpleEntityReflectivePropertyAccessWithCustomConversionRegistry thrpt 10 7361251,834 ± 278530,209 ops/s
DefaultTypeMapperBenchmark.readTyped thrpt 10 122523380,422 ± 3839365,439 ops/s
DefaultTypeMapperBenchmark.readUntyped thrpt 10 181767673,793 ± 3549021,260 ops/s
Original pull request: #319.
We now correctly calculate the number of defaulting masks used to represent constructor arguments. Previously, we've been one off which caused that Kotlin classes with 32/33 parameters weren't able to be instantiated.
We also now reuse KotlinDefaultMask to apply defaulting calculation and removed code duplicates.
Original pull request: #317.
We now check whether to use the repository configuration as last check to clarify store module responsibility first. This allows to place store-specific checks in RepositoryConfigurationExtensionSupport.useRepositoryConfiguration(…) such as rejecting reactive repositories for a store module that does not support reactive repositories
Previously, we called useRepositoryConfiguration(…) before checking whether the actual repository interface is handled by the store module. This resulted in rejection of reactive repositories by store modules that do not provide reactive support whereas the repository did not belong to the actual store module.
Related ticket: DATACMNS-1174.
We now avoid using a Lambda to provide a default ObjectMapper instance in the code that's reflectively guarded against Jackson not being present. The lambda causes a method to be generated for the class that will require ObjectMapper to be present on reflection inspection of that method. Switching to a method reference to ObjectMapper's constructor resolves that problem as the indirection via the additional, offending method is not needed.
Further reading: https://www.javabullets.com/how-lambdas-and-anonymous-inner-classesaic-work/
One of the constructors of Pageable wasn't properly checking for assignability of Pageable parameters to detect them but was expecting Pageable itself being used under all circumstances. This has now been opened up by an assignability check.
We now reject reactive repository metadata by RepositoryConfigurationExtensionSupport.useRepositoryConfiguration(…) assuming that Spring Data modules provide only imperative repository support by default. Reactive store modules are required to override useRepositoryConfiguration(…) anyway to not accidentally implement imperative repositories with a reactive Repository extension.
We now attempt to use private MethodHandles lookup as the first mechanism to resolve a MethodHandle for default interface methods and fall back to reflection-based Lookup construction if private lookup is not available. Reflective availability is checked lazily to prevent illegal access on enum constant construction. This approach prevents an illegal access which was logged by attempting a reflection-based lookup first.
We also introduced a FALLBACK mechanism to split encapsulated access from a fallback mechanism.
Original pull request: #307.
We now use Spring's ReflectUtils.defineClass(…) to load generated EntityInstantiators which uses internally either MethodHandles.Lookup.defineClass(…) (on Java 9 and higher) or reflective defineClass invocation on the originating ClassLoader. Class injection into the originating ClassLoader assigns the ClassLoader of the entity to the generated class which allows optimized instantiation of package-protected classes and constructors.
Original pull request: #308.
We now use a presized HashMap and Weak references in BasicPersistentEntity to improve memory and CPU profile and and avoid unmodifiable collection creation in BasicPersistentEntity.iterator(). Refactored ClassTypeInformation.from(…) lambda to method reference and predefined collection size for the cache. Reduced object instantiations during TypeDiscoverer.equals(…) by checking for type variable map emptiness to avoid Map iterator creation in Map.equals(…).
Original pull request: #305.
Added debug logging in RepositoryFactorySupport and RepositoryConfigurationDelegate so that the scanning and the instantiation of repositories can be a lot easier identified in the logs.
Use weak references in annotation and property annotation cache to retain references until the last GC root is cleared. Remove trailing whitespaces. Reformat.
Original pull request: #304.
We now use HashMap to store persistent properties of a PersistentEntity. An entity is built in a single thread so no concurrent modification happens. Concurrent reads may happen during entity usage which is fine as the PersistentEntity is not changed anymore. Previously, we used ConcurrentReferenceHashMap defaulting to soft references. Soft references can be cleared at the discretion of the GC in response to memory demand. So a default ConcurrentReferenceHashMap is memory-sensitive and acts like a cache with memory-based eviction rules.
Persistent properties are not subject to be cached but elements of a PersistentEntity and cannot be recovered once cleared.
Original pull request: #304.
We now eagerly look up the aggregate root type of a repository in the MappingContext. Some implementations might not have pre-populated the context with all entities and we need to make sure it knows about the aggregate root as other clients (e.g. the auditing subsystem) might only defensively access the entities via PersistentEntities which is not adding new entities to avoid store clashes.