spring-projects / spring-projects/spring-data-mongodb

Repository query into embedded simple type structure throws MappingException

Open
#3,983 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

for: team-attention type: bug type: enhancement
Dominant language
Java
Stars
1.7k
Forks
1.1k
PR merge metrics
No merged PRs in 30d

Description

Attempting to use a Spring Data repository to query the structure of a simple type (i.e., a type having a write converter) embedded in another type fails to a MappingException. This problem is present in the latest Spring Data MongoDB version 3.3.2 and seems to have been introduced in this Spring Data Commons commit originally included in Spring Data Commons 2.5.0-M3 (see the issue #2294 and the PR #2293). A similar problem seems to have been reported in #3659, but the associated PR #3661 appears to have fixed the issue only for executions originating from QueryMapper.

Example MappingException stack trace.

org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for property private org.springframework.data.mongodb.core.ReproTest$SomeSimpleType org.springframework.data.mongodb.core.ReproTest$Container.simpleTypedValue!
	at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:152)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.getPair(PersistentPropertyPathFactory.java:225)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.createPersistentPropertyPath(PersistentPropertyPathFactory.java:199)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.lambda$getPersistentPropertyPath$1(PersistentPropertyPathFactory.java:172)
	at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.getPersistentPropertyPath(PersistentPropertyPathFactory.java:171)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:86)
	at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:99)
	at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:293)
	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.create(MongoQueryCreator.java:120)
	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.create(MongoQueryCreator.java:67)
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:119)
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:95)
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:81)
	at org.springframework.data.mongodb.repository.query.PartTreeMongoQuery.createQuery(PartTreeMongoQuery.java:89)
	at org.springframework.data.mongodb.repository.query.AbstractMongoQuery.doExecute(AbstractMongoQuery.java:142)
	at org.springframework.data.mongodb.repository.query.AbstractMongoQuery.execute(AbstractMongoQuery.java:127)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:159)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:98)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
	at jdk.proxy2/jdk.proxy2.$Proxy51.findFirstBySimpleTypedValue_Something(Unknown Source)

Here's a test that reproduces the problem.

package org.springframework.data.mongodb.core;

import com.mongodb.client.MongoClient;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Id;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.data.mongodb.test.util.Client;
import org.springframework.data.mongodb.test.util.MongoClientExtension;
import org.springframework.data.repository.CrudRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.Collections;
import java.util.Optional;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThatNoException;

@ExtendWith({ MongoClientExtension.class, SpringExtension.class })
@ContextConfiguration
public class ReproTest {

    static @Client MongoClient mongoClient;

    @Configuration
    @EnableMongoRepositories(
            considerNestedRepositories = true,
            includeFilters=@ComponentScan.Filter(
                    type = FilterType.ASSIGNABLE_TYPE,
                    classes = ReproTest.ContainerRepository.class))
    static class Config extends AbstractMongoClientConfiguration {

        @Override
        protected String getDatabaseName() {
            return "test";
        }

        @Override
        public MongoClient mongoClient() {
            return mongoClient;
        }

        @Override
        protected boolean autoIndexCreation() {
            return false;
        }

        @Override
        protected Set<Class<?>> getInitialEntitySet() throws ClassNotFoundException {
            return Collections.emptySet();
        }

        @Override
        protected void configureConverters(MongoCustomConversions.MongoConverterConfigurationAdapter converterConfigurationAdapter) {
            converterConfigurationAdapter.registerConverter(new SimpleTypeConverter());
        }
    }

    @Autowired
    MongoOperations mongoOps;
    @Autowired
    ContainerRepository repository;


    @Test
    public void repositoryQueryIntoSimpleTypeStructureShouldNotThrowAnException() {
        assertThatNoException()
                .isThrownBy(() -> repository.findFirstBySimpleTypedValue_Something("example"));
    }

    @Document
    public static class Container {
        @Id
        private String id;
        private SomeSimpleType simpleTypedValue;
    }

    public static class SomeSimpleType {
        private String something;

        public String getSomething() {
            return something;
        }
    }

    @WritingConverter
    public static class SimpleTypeConverter implements Converter<SomeSimpleType, org.bson.Document> {
        @Override
        public org.bson.Document convert(SomeSimpleType actual) {
            return new org.bson.Document("something", actual.getSomething());
        }
    }

    public interface ContainerRepository extends CrudRepository<Container, String> {
        Optional<Container> findFirstBySimpleTypedValue_Something(String something);
    }
}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with ReproTest.repositoryQueryIntoSimpleTypeStructureShouldNotThrowAnException and run the supplied reproduction. Trace the failure from MongoQueryCreator.java through PersistentPropertyPathFactory and AbstractMappingContext; done means the repository method completes without a MappingException for the embedded converted type.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, mongodb, spring
Domain
backend, database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.