spring-data-mongodb icon indicating copy to clipboard operation
spring-data-mongodb copied to clipboard

Indexed affects not only root collection [DATAMONGO-1965]

Open spring-projects-issues opened this issue 7 years ago • 8 comments

Vadzim Parafianiuk opened DATAMONGO-1965 and commented

If I use @Indexed in one class, I want spring to create Index only in collection for this class, but it also creates index in another classes that use this @Indexed class as embedded field. As a result, if I use "unique = true" it also affects embedded fields in another classes. See my question on stackoverflow with more rich description and example.

 

I've found a solution: I create ApplicationListener, and after context loading I use MongoTemplate to create indexes manually. But it is not convenient


Reference URL: https://stackoverflow.com/questions/49930576/spring-data-mongodb-indexedunique-true-in-inner-field

spring-projects-issues avatar Apr 20 '18 15:04 spring-projects-issues

Mark Paluch commented

Workflow fixed (was broken previously by the Jira upgrade)

spring-projects-issues avatar May 07 '18 19:05 spring-projects-issues

Vadzim Parafianiuk commented

I've added link to stackoverflow with more detail description. I've only found the way to solve my problem is to create indexes manually

spring-projects-issues avatar May 07 '18 19:05 spring-projects-issues

I have just been tripped up with this - it certainly feels like a bug for my use-case. Extra index(es) are created when a class contains references to itself:

Example:

@Document("People")
class Person {
    @Indexed private String name;
    private Map<String, Person> friends = new HashMap<>();
}

The following indexes are created. "name" : "friends.name" is not wanted/desired.

> db.People.getIndexes()
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",
		"ns" : "test.People"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : "name",
		"ns" : "test.People"
	},
	{
		"v" : 2,
		"key" : {
			"friends.name.name" : 1
		},
		"name" : "friends.name",
		"ns" : "test.People"
	}
]

This can quickly get out of hand creating many unwanted indexes if the class contains several @Indexed fields.

@odrotbohm - please let me know if you would like me to report a new issue specifically for this case.

jasonkingatconversocial avatar Sep 16 '22 14:09 jasonkingatconversocial

Hello, I just submit a PR with a simple proposal: an annotation @IgnoreIndexes to put on fields mapped as embedded document. Adding this annotation, the indexes defined in classes of embedded fields will not be created on root collection.

rolag-it avatar Dec 28 '23 14:12 rolag-it

Annotation-based indices cover only a certain ground and aren't an all-purpose utility to express all sorts of combinations. We already require double-opt in by requiring the entity type to be annotated with @Document. These types of hierarchies can be represented with a slightly different type hierarchy. Alternatively, please resort to using programmatic index creation.

mp911de avatar Dec 29 '23 09:12 mp911de

Hello @mp911de, I understand the scope of use you described for annotation-based indexes and I agree with you, but the current resolution of the indexes defined on embedded objects is quite odd resulting counter-intuitive and bug prone.

I also reach out the same workarounds you proposed, finding some significant cons:

  • use different type hierarchy: easily lead to code duplication;
  • programmatic index creation: add unwanted complexity.

Annotation-based indexes are an easy and powerful tool; an annotation @IgnoreIndexesto exclude unwanted indexes definitions when mapping embedded document is a solution that enables great flexibility, it has a very low footprint, no side-effects and moreover is totally backward compatible.

Also it facilitates the embedded document pattern, a key approach for mapping relation in MongoDB.

Please take into account the considerations listed above evaluating my PR.

rolag-it avatar Jan 02 '24 11:01 rolag-it

Adding another annotation doesn't solve the underlying issue, it instead creates another utility that can be used in an unintended way resulting in more complexity for the framework and frustration for users.

Generally speaking, we semi-deprecated auto-index creation exactly for these reasons because it isn't a one-fits-all approach. Instead, the intention of auto-index creation is to get to speed quickly with application development and then, move index creation into some ops process as suggested by MongoDB (not mentioning the many incidents created by auto-index creation on huge production clusters).

In any case, let me take this ticket to the team to discuss it.

mp911de avatar Jan 03 '24 10:01 mp911de

I'm also facing this problem, but since I'm already creating the indexes programmatically, I just directly update my code to deal with it - using the code snippet from the doc as a base, I adapted it like this:

[!WARNING]
The following code likely doesn't work for List/Set/Collection/Map/deeply-nested-scenario because I don't have that need - if you need to support those you'd need to enhance the code on your own.

@EventListener(ContextRefreshedEvent.class)
void initIndexes() {
    MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate.getConverter().getMappingContext();
    IndexResolver resolver = IndexResolver.create(mappingContext);

    mappingContext.getPersistentEntities()
            .stream()
            .filter(it -> it.isAnnotationPresent(Document.class))
            .forEach(it -> {
                Set<String> embeddedDocumentFieldNames = new HashSet<>();
                it.doWithProperties((PropertyHandler<MongoPersistentProperty>) persistentProperty -> {
                    if (persistentProperty.hasActualTypeAnnotation(Document.class)) {
                        embeddedDocumentFieldNames.add(persistentProperty.getFieldName());
                    }
                });

                IndexOperations indexOps = mongoTemplate.indexOps(it.getType());
                StreamSupport.stream(resolver.resolveIndexFor(it.getType()).spliterator(), false)
                        .filter(indexDefinition -> {
                            String indexName = indexDefinition.getIndexOptions().getString("name");
                            if (indexName == null) { // @CompoundIndex.name not configured
                                return true;
                            }

                            // Because index name created for an embedded document starts with `<field/property name>.<the field/compound index name>`
                            return embeddedDocumentFieldNames.stream()
                                    .noneMatch(embeddedDocumentFieldName -> indexName.startsWith(embeddedDocumentFieldName + "."));
                        })
                        .forEach(indexOps::ensureIndex);
            });
}

yihtserns avatar Jan 13 '24 17:01 yihtserns