简体   繁体   中英

@Indexed annotation ignored when using named collections

I have a pojo annotated like this:

@Document
class Car {

  @Id
  String id ;

  @Indexed
  String manufacturer ;

}

And I am using MongoTemplate to insert into mongo. If I insert without specifying a collection name, everything works fine. However, if I specify a collection name, none of the indexes are created except for the _id one.

I really need to be able to specify the collection name manually because:

  • I need to ensure different subclasses of Car end up in the same collection
  • I would like to store each year's worth of Cars in a separate collection

Do I have to call ensureIndex() myself manually? If so, is there a way to do it that uses my @Indexed annotations? The actual objects I am trying to save are a lot more complex than 'Car'

Unfortunately MongoTemplate function

public void insert(Object objectToSave, String collectionName)

using collectionName only for saving object and not for creating any annotated indexes. If object passed to save operation then application event listener MongoPersistentEntityIndexCreator scan saved entity class for @Indexed annotations and create indexes. But it detect collection name based on next formula (from sources):

String collection = StringUtils.hasText(index.collection()) ? index.collection() : entity.getCollection();

where index.collection() is collection from @Indexed annotation and entity.getCollection() from @Document annotation.

So, you need to call ensureIndex() manually. It's strange behaviour. I think you can open bug here: DATAMONGO

EDIT: I think you can create function that return all classes annotated with @Document and also get all collections from mongodb started from cars.<year> . Then you can analyse all fields annotated with @Indexed and, as result, call ensureIndex for this fields using collections list.

I really need to be able to specify the collection name manually because:

I need to ensure different subclasses of Car end up in the same collection
I would like to store each year's worth of Cars in a separate collection

As for your first statement, you can enforce a single collection using @Document metadata annotation:

@Document(collection="cars")
class Car {

    @Id
    String id ;

    @Indexed
    String manufacturer ;

}

@Document(collection="cars")
class Honda extends Car {

}

@Document(collection="cars")
class Volvo extends Car {

}

The collection field of @Document will take care that every subclass of car gets into the cars collection, plus the index is automatically created with the help of the @Indexed annotation.

You maybe better off stooping to the DB level and checking if the collection has indexes you need using getIndexes and calling the ensureIndex as part of your application start up.

Tip: Implement InitializingBean on the class that configures mongo for your application and do the magic there.

No preaching, IMHO Spring is good at certain things and so-so at others. I try to avoid the bloat other than for DI & TXN management in non XA environments.

After a long search, I think that for now is not posible using annotations, so I created a programmatically way to manage this scenario.

creating normal index and compound index

First I defined an object to map the index structure.

public class CollectionDefinition {

private String id;
private String collectionName;
private LinkedHashMap<String, Sort.Direction> normalIndexMap;
private ArrayList<String> compoundIndexMap;

public String getId() {
    return id;
}

public void setId(String id) {
    this.id = id;
}

public String getCollectionName() {
    return collectionName;
}

public void setCollectionName(String collectionName) {
    this.collectionName = collectionName;
}

public LinkedHashMap<String, Sort.Direction> getNormalIndexMap() {
    return normalIndexMap;
}

public void setNormalIndexMap(LinkedHashMap<String, Sort.Direction> normalIndexMap) {
    this.normalIndexMap = normalIndexMap;
}

public ArrayList<String> getCompoundIndexMap() {
    return compoundIndexMap;
}

public void setCompoundIndexMap(ArrayList<String> compoundIndexMap) {
    this.compoundIndexMap = compoundIndexMap;
}

The Json collection will look like this

{
"collectionName" : "example", 
"normalIndexMap" : {
    "sessionId" : "ASC", 
    "hash" : "DESC"
}, 
"compoundIndexMap" : [
    "{\"hash\":1,\"sessionId\":1,\"serverDate\":-1}", 
    "{\"sessionId\":1,\"serverDate\":-1}", 
    "{\"sessionId\":1,\"loginWasSuccessful\":1,\"loginDate\":-1}"
]}

Then We can create the collection and the indexes

private List<IndexDefinition> createIndexList(Map<String, Sort.Direction> normalIndexMap) {
    Set<String> keySet = normalIndexMap.keySet();
    List<IndexDefinition> indexArray = new ArrayList<>();
    for (String key : keySet) {
        indexArray.add(new Index(key, normalIndexMap.get(key)).background());
    }
    return indexArray;
}

The resulting single index list can be used to create single field index

private void createIndexes(List<IndexDefinition> indexDefinitionList, MongoOperations mongoOperations, String collectionName) {
    indexDefinitionList.forEach(indexDefinition -> mongoOperations.indexOps(collectionName).ensureIndex(indexDefinition));
}

The compound index or multifield index is tricky, We need to create DBObjects to define the index

private List<DBObject> createCompountIndexList(List<String> compoundIndexStringMapList) {//NOSONAR IS IN USE
    if (compoundIndexStringMapList == null || compoundIndexStringMapList.isEmpty())
        return new ArrayList<>(); //NOSONAR
    ObjectMapper mapper = new ObjectMapper();
    List<DBObject> basicDBObjectList = new ArrayList<>();
    for (String stringMapList : compoundIndexStringMapList) {
        LinkedHashMap<String, Integer> parsedMap;
        try {
            parsedMap = mapper.readValue(stringMapList, new TypeReference<Map<String, Integer>>() {
            });
            BasicDBObjectBuilder dbObjectBuilder = BasicDBObjectBuilder.start();
            Iterator it = parsedMap.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry indexElement = (Map.Entry) it.next();
                dbObjectBuilder.add((String) indexElement.getKey(), indexElement.getValue());
                it.remove(); // avoids a ConcurrentModificationException
            }
            basicDBObjectList.add(dbObjectBuilder.get());
        } catch (IOException e) {//NOSONAR I´m logging it, we can not do anything more here cause it is part of the db configuration settings that need to be correct
            Logger.getLogger(JsonCollectionCreation.class).error("The compound index definition " + stringMapList + " is not correct it can not be mapped to LinkHashMap");
        }

    }
    return basicDBObjectList;
}

And the result can be used to create the index in the collection

private void createCompoundIndex(List<DBObject> compoundIndexList, MongoOperations mongoOperations, String collectionName) {
    if (compoundIndexList.isEmpty()) return;
    for (DBObject compoundIndexDefinition : compoundIndexList) {
        mongoOperations.indexOps(collectionName).ensureIndex(new CompoundIndexDefinition(compoundIndexDefinition).background());
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM