Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: spring-projects/spring-data-commons
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 3.4.3
Choose a base ref
...
head repository: spring-projects/spring-data-commons
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 3.4.4
Choose a head ref
  • 8 commits
  • 11 files changed
  • 2 contributors

Commits on Feb 14, 2025

  1. Prepare next development iteration.

    See #3234
    mp911de committed Feb 14, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    4689477 View commit details
  2. After release cleanups.

    See #3234
    mp911de committed Feb 14, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    7bab2c7 View commit details

Commits on Feb 20, 2025

  1. Consider getters using get as getter for boolean Kotlin properties.

    We now additionally consider get-prefixed methods in addition to is-prefixed methods as getters for boolean properties.
    
    Closes #3249
    mp911de committed Feb 20, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    3c625ca View commit details

Commits on Mar 5, 2025

  1. Remove branching overhead in BytecodeUtil by replacing if with `e…

    …lse if`.
    
    Closes #3168
    mitu2 authored and mp911de committed Mar 5, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    e79a2bd View commit details
  2. Polishing.

    Reformat code.
    
    See #3168
    mp911de committed Mar 5, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    bb59eb4 View commit details

Commits on Mar 6, 2025

  1. Revise RepositoryInformation and RepositoryComposition caching.

    We now use a refined strategy to cache RepositoryInformation and RepositoryComposition.
    
    Previously, RepositoryComposition wasn't cached at all and store modules that e.g. contributed a Querydsl (or a different) fragment based on the interface declaration returned a new RepositoryComposition (and thus a different hashCode) each time RepositoryInformation was obtained leading to memory leaks caused by HashMap caching.
    
    We now use Fragment's hashCode for the cache key resulting in RepositoryComposition being created only once for a given repository interface and input-fragments arrangement.
    
    Closes #3252
    mp911de committed Mar 6, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    50b74f6 View commit details

Commits on Mar 14, 2025

  1. Prepare 3.4.4 (2024.1.4).

    See #3246
    mp911de committed Mar 14, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    29fb105 View commit details
  2. Release version 3.4.4 (2024.1.4).

    See #3246
    mp911de committed Mar 14, 2025

    Verified

    This commit was signed with the committer’s verified signature.
    mp911de Mark Paluch
    Copy the full SHA
    aba2047 View commit details
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>3.4.3</version>
<version>3.4.4</version>

<name>Spring Data Core</name>
<description>Core Spring concepts underpinning every Spring Data module.</description>
@@ -25,7 +25,7 @@
<parent>
<groupId>org.springframework.data.build</groupId>
<artifactId>spring-data-parent</artifactId>
<version>3.4.3</version>
<version>3.4.4</version>
</parent>

<properties>
Original file line number Diff line number Diff line change
@@ -88,65 +88,35 @@ static void autoboxIfNeeded(Class<?> in, Class<?> out, MethodVisitor visitor) {

if (in.equals(Boolean.class) && out.equals(Boolean.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
}

if (in.equals(Boolean.TYPE) && out.equals(Boolean.class)) {
} else if (in.equals(Boolean.TYPE) && out.equals(Boolean.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
}

if (in.equals(Byte.class) && out.equals(Byte.TYPE)) {
} else if (in.equals(Byte.class) && out.equals(Byte.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
}

if (in.equals(Byte.TYPE) && out.equals(Byte.class)) {
} else if (in.equals(Byte.TYPE) && out.equals(Byte.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
}

if (in.equals(Character.class) && out.equals(Character.TYPE)) {
} else if (in.equals(Character.class) && out.equals(Character.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
}

if (in.equals(Character.TYPE) && out.equals(Character.class)) {
} else if (in.equals(Character.TYPE) && out.equals(Character.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
}

if (in.equals(Double.class) && out.equals(Double.TYPE)) {
} else if (in.equals(Double.class) && out.equals(Double.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
}

if (in.equals(Double.TYPE) && out.equals(Double.class)) {
} else if (in.equals(Double.TYPE) && out.equals(Double.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
}

if (in.equals(Float.class) && out.equals(Float.TYPE)) {
} else if (in.equals(Float.class) && out.equals(Float.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
}

if (in.equals(Float.TYPE) && out.equals(Float.class)) {
} else if (in.equals(Float.TYPE) && out.equals(Float.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
}

if (in.equals(Integer.class) && out.equals(Integer.TYPE)) {
} else if (in.equals(Integer.class) && out.equals(Integer.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
}

if (in.equals(Integer.TYPE) && out.equals(Integer.class)) {
} else if (in.equals(Integer.TYPE) && out.equals(Integer.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
}

if (in.equals(Long.class) && out.equals(Long.TYPE)) {
} else if (in.equals(Long.class) && out.equals(Long.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
}

if (in.equals(Long.TYPE) && out.equals(Long.class)) {
} else if (in.equals(Long.TYPE) && out.equals(Long.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
}

if (in.equals(Short.class) && out.equals(Short.TYPE)) {
} else if (in.equals(Short.class) && out.equals(Short.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
}

if (in.equals(Short.TYPE) && out.equals(Short.class)) {
} else if (in.equals(Short.TYPE) && out.equals(Short.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
}
}
@@ -265,21 +235,13 @@ static void visitDefaultValue(Class<?> parameterType, MethodVisitor mv) {

if (parameterType == Integer.TYPE || parameterType == Short.TYPE || parameterType == Boolean.TYPE) {
mv.visitInsn(Opcodes.ICONST_0);
}

if (parameterType == Long.TYPE) {
} else if (parameterType == Long.TYPE) {
mv.visitInsn(Opcodes.LCONST_0);
}

if (parameterType == Double.TYPE) {
} else if (parameterType == Double.TYPE) {
mv.visitInsn(Opcodes.DCONST_0);
}

if (parameterType == Float.TYPE) {
} else if (parameterType == Float.TYPE) {
mv.visitInsn(Opcodes.FCONST_0);
}

if (parameterType == Character.TYPE || parameterType == Byte.TYPE) {
} else if (parameterType == Character.TYPE || parameterType == Byte.TYPE) {
mv.visitIntInsn(Opcodes.BIPUSH, 0);
}
} else {
Original file line number Diff line number Diff line change
@@ -541,6 +541,10 @@ private static Method findMethod(InvokedMethod invokedMethod, MethodLookup looku
return null;
}

List<RepositoryFragment<?>> getFragments() {
return fragments;
}

/**
* Returns the number of {@link RepositoryFragment fragments} available.
*
@@ -585,5 +589,6 @@ public int hashCode() {
result = (31 * result) + ObjectUtils.nullSafeHashCode(fragments);
return result;
}

}
}
Original file line number Diff line number Diff line change
@@ -107,7 +107,7 @@ public abstract class RepositoryFactorySupport
CONVERSION_SERVICE.removeConvertible(Object.class, Object.class);
}

private final Map<RepositoryInformationCacheKey, RepositoryInformation> repositoryInformationCache;
private final Map<RepositoryInformationCacheKey, RepositoryStub> repositoryInformationCache;
private final List<RepositoryProxyPostProcessor> postProcessors;

private @Nullable Class<?> repositoryBaseClass;
@@ -127,7 +127,7 @@ public abstract class RepositoryFactorySupport
@SuppressWarnings("null")
public RepositoryFactorySupport() {

this.repositoryInformationCache = new HashMap<>(16);
this.repositoryInformationCache = new HashMap<>(8);
this.postProcessors = new ArrayList<>();

this.namedQueries = PropertiesBasedNamedQueries.EMPTY;
@@ -291,16 +291,6 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
return RepositoryFragments.empty();
}

/**
* Creates {@link RepositoryComposition} based on {@link RepositoryMetadata} for repository-specific method handling.
*
* @param metadata the repository metadata to use.
* @return repository composition.
*/
private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata) {
return RepositoryComposition.fromMetadata(metadata);
}

/**
* Returns a repository instance for the given interface.
*
@@ -359,8 +349,9 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
repositoryInterface);
repositoryCompositionStep.tag("fragment.count", String.valueOf(fragments.size()));

RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
RepositoryInformation information = getRepositoryInformation(metadata, composition);
RepositoryStub stub = getRepositoryStub(metadata, fragments);
RepositoryComposition composition = stub.composition();
RepositoryInformation information = stub.information();

repositoryCompositionStep.tag("fragments", () -> {

@@ -490,47 +481,35 @@ protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface)
* @return will never be {@literal null}.
*/
protected RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata, RepositoryFragments fragments) {
return getRepositoryInformation(metadata, getRepositoryComposition(metadata, fragments));
return getRepositoryStub(metadata, fragments).information();
}

/**
* Returns the {@link RepositoryComposition} for the given {@link RepositoryMetadata} and extra
* {@link RepositoryFragments}.
*
* @param metadata must not be {@literal null}.
* @param fragments must not be {@literal null}.
* @return will never be {@literal null}.
*/
private RepositoryComposition getRepositoryComposition(RepositoryMetadata metadata, RepositoryFragments fragments) {

Assert.notNull(metadata, "RepositoryMetadata must not be null");
Assert.notNull(fragments, "RepositoryFragments must not be null");

RepositoryComposition composition = getRepositoryComposition(metadata);
RepositoryFragments repositoryAspects = getRepositoryFragments(metadata);

return composition.append(fragments).append(repositoryAspects);
}

/**
* Returns the {@link RepositoryInformation} for the given repository interface.
* Returns the cached {@link RepositoryStub} for the given repository and composition. {@link RepositoryMetadata} is a
* strong cache key while {@link RepositoryFragments} contributes a light-weight caching component by using only the
* fragments hash code. In a typical Spring scenario, that shouldn't impose issues as one repository factory produces
* only a single repository instance for one repository interface. Things might be different when using various
* fragments for the same repository interface.
*
* @param metadata
* @param composition
* @param fragments
* @return
*/
private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,
RepositoryComposition composition) {
private RepositoryStub getRepositoryStub(RepositoryMetadata metadata, RepositoryFragments fragments) {

RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition);
RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, fragments);

synchronized (repositoryInformationCache) {

return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {

Class<?> baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata);
RepositoryComposition composition = RepositoryComposition.fromMetadata(metadata);
RepositoryFragments repositoryAspects = getRepositoryFragments(metadata);
composition = composition.append(fragments).append(repositoryAspects);

return new DefaultRepositoryInformation(metadata, baseClass, composition);
Class<?> baseClass = repositoryBaseClass != null ? repositoryBaseClass : getRepositoryBaseClass(metadata);

return new RepositoryStub(new DefaultRepositoryInformation(metadata, baseClass, composition), composition);
});
}
}
@@ -811,6 +790,18 @@ public List<QueryMethod> getQueryMethods() {
}
}

/**
* Repository stub holding {@link RepositoryInformation} and {@link RepositoryComposition}.
*
* @param information
* @param composition
* @author Mark Paluch
* @since 3.4.4
*/
record RepositoryStub(RepositoryInformation information, RepositoryComposition composition) {

}

/**
* Simple value object to build up keys to cache {@link RepositoryInformation} instances.
*
@@ -820,31 +811,26 @@ public List<QueryMethod> getQueryMethods() {
private static final class RepositoryInformationCacheKey {

private final String repositoryInterfaceName;
private final long compositionHash;
private final long fragmentsHash;

/**
* Creates a new {@link RepositoryInformationCacheKey} for the given {@link RepositoryMetadata} and composition.
* Creates a new {@link RepositoryInformationCacheKey} for the given {@link RepositoryMetadata} and fragments.
*
* @param metadata must not be {@literal null}.
* @param composition must not be {@literal null}.
* @param fragments must not be {@literal null}.
*/
public RepositoryInformationCacheKey(RepositoryMetadata metadata, RepositoryComposition composition) {
public RepositoryInformationCacheKey(RepositoryMetadata metadata, RepositoryFragments fragments) {

this.repositoryInterfaceName = metadata.getRepositoryInterface().getName();
this.compositionHash = composition.hashCode();
}

public RepositoryInformationCacheKey(String repositoryInterfaceName, long compositionHash) {
this.repositoryInterfaceName = repositoryInterfaceName;
this.compositionHash = compositionHash;
this.fragmentsHash = fragments.getFragments().hashCode();
}

public String getRepositoryInterfaceName() {
return this.repositoryInterfaceName;
}

public long getCompositionHash() {
return this.compositionHash;
public long getFragmentsHash() {
return this.fragmentsHash;
}

@Override
@@ -855,7 +841,7 @@ public boolean equals(Object o) {
if (!(o instanceof RepositoryInformationCacheKey that)) {
return false;
}
if (compositionHash != that.compositionHash) {
if (fragmentsHash != that.fragmentsHash) {
return false;
}
return ObjectUtils.nullSafeEquals(repositoryInterfaceName, that.repositoryInterfaceName);
@@ -864,14 +850,14 @@ public boolean equals(Object o) {
@Override
public int hashCode() {
int result = ObjectUtils.nullSafeHashCode(repositoryInterfaceName);
result = 31 * result + (int) (compositionHash ^ (compositionHash >>> 32));
result = 31 * result + Long.hashCode(fragmentsHash);
return result;
}

@Override
public String toString() {
return "RepositoryFactorySupport.RepositoryInformationCacheKey(repositoryInterfaceName="
+ this.getRepositoryInterfaceName() + ", compositionHash=" + this.getCompositionHash() + ")";
+ this.getRepositoryInterfaceName() + ", fragmentsHash=" + this.getFragmentsHash() + ")";
}
}

Original file line number Diff line number Diff line change
@@ -94,18 +94,9 @@ private static void collectKotlinProperties(Class<?> beanClass, Collection<KCall

if (member instanceof KProperty<?> property) {

Method getter = ReflectJvmMapping.getJavaGetter(property);
Method setter = property instanceof KMutableProperty<?> kmp ? ReflectJvmMapping.getJavaSetter(kmp) : null;

if (getter == null) {
Type javaType = ReflectJvmMapping.getJavaType(property.getReturnType());
getter = ReflectionUtils.findMethod(beanClass,
javaType == Boolean.TYPE ? "is" : "get" + StringUtils.capitalize(property.getName()));
}

if (getter != null) {
getter = ClassUtils.getMostSpecificMethod(getter, beanClass);
}
Type javaType = ReflectJvmMapping.getJavaType(property.getReturnType());
Method getter = findGetter(beanClass, property, javaType);

if (getter != null && (Modifier.isStatic(getter.getModifiers()) || getter.getParameterCount() != 0)) {
continue;
@@ -123,6 +114,21 @@ private static void collectKotlinProperties(Class<?> beanClass, Collection<KCall
}
}

private static @Nullable Method findGetter(Class<?> beanClass, KProperty<?> property, Type javaType) {

Method getter = ReflectJvmMapping.getJavaGetter(property);

if (getter == null && javaType == Boolean.TYPE) {
getter = ReflectionUtils.findMethod(beanClass, "is" + StringUtils.capitalize(property.getName()));
}

if (getter == null) {
getter = ReflectionUtils.findMethod(beanClass, "get" + StringUtils.capitalize(property.getName()));
}

return getter != null ? ClassUtils.getMostSpecificMethod(getter, beanClass) : null;
}

private static void collectBasicJavaProperties(Class<?> beanClass, Map<String, PropertyDescriptor> descriptors)
throws IntrospectionException {

@@ -204,5 +210,7 @@ public void setWriteMethod(@Nullable Method writeMethod) {
public Method getWriteMethod() {
return this.writeMethod;
}

}

}
Loading