В нашому проекті ми використовуємо звязку Mongo 3.x і обгортку Spring Data MongoDB використовуючи як кеш зі стрічковими ключами. Нещодавно в логах продакшина ми знайшли цікаву помилку, враховуючи, що код для роботи з кешом вже кілька років незмінний, то це було для нас несподіванкою. Отож що означає таке повідомлення та як це виправити
org.springframework.data.mapping.model.MappingException: Map key .......... contains dots but no replacement was configured! Make sure map keys don't contain dots in the first place or configure an appropriate replacement!
Щоб зрозуміти чому виникає подібна помилка вернемось до специфікації. Mongo це документо-орієнтована база даних в якій сутності зберігаються у вигляді BSON документів, що віддаються користувачам у JSON форматі. В самому документі ключем є дані рядкового типу на які накладені певні обмеження
[ecko_quote source=”https://docs.mongodb.org/manual/core/document/”]Field Names
Field names are strings.
Documents have the following restrictions on field names:
- The field name _id is reserved for use as a primary key; its value must be unique in the collection, is immutable, and may be of any type other than an array.
- The field names cannot start with the dollar sign ($) character.
- The field names cannot contain the dot (.) character.
- The field names cannot contain the null character.[/ecko_quote]
Обмеження на використання символу “.” в назві пов’язане з тим, що крапка використовується для доступу до полів вкладених обєктів, які можуть міститись в документі.
https://docs.mongodb.org/manual/reference/glossary/#term-dot-notation
Тому в спрінговому MongoTemplate ввели заміну можливого символу “.” в значеннях ключа.
Реалізує дану заміну метод potentiallyEscapeMapKey(String source) в класі MappingMongoConverter.java.
/** * Potentially replaces dots in the given map key with the configured map key replacement if configured or aborts * conversion if none is configured. * * @see #setMapKeyDotReplacement(String) * @param source * @return */ protected String potentiallyEscapeMapKey(String source) { if (!source.contains(".")) { return source; } if (mapKeyDotReplacement == null) { throw new MappingException(String.format( "Map key %s contains dots but no replacement was configured! Make " + "sure map keys don't contain dots in the first place or configure an appropriate replacement!", source)); } return source.replaceAll("\\.", mapKeyDotReplacement); }
Як ми бачимо йде заміна “.” на mapKeyDotReplacement
return source.replaceAll("\\.", mapKeyDotReplacement);
Відповідно для коректної роботи необхідно щоб було ініціалізовано поле mapKeyDotReplacement. У противному випадку ми отримаєм помилку.
Отож необхідно правильно сконфігурувати MappingMongoConverter інакше SpringMongoData візьме значення по замовчування де mapKeyDotReplacement є порожнім.
Для конфігурації *.xml “форматі”:
<bean id="mongoMoxydomainConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter"> <constructor-arg index="0" ref="mongoDbFactory" /> <constructor-arg index="1"> <bean class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" /> </constructor-arg> <property name="mapKeyDotReplacement" value="\\+" /> </bean>
де задаєм необхідне значення поля mapKeyDotReplacement
<property name="mapKeyDotReplacement" value="\\+" />
Якщо використовується java конфігурація то це можна зробити ось так
public @Bean MongoTemplate mongoTemplate() throws Exception { MappingMongoConverter converter = new MappingMongoConverter(new DefaultDbRefResolver(mongoDbFactory()), new MongoMappingContext()); converter.setMapKeyDotReplacement("\\+"); MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory(), converter); return mongoTemplate; }
Загалом доволі очевидна річ з крапкою у ключі може змусити похвилюватись, якщо ви недостатньо уважні і забули правильно ініціалізувати конвертер. На щастя крапка в ключах не є такою частою оказією і в нашому випадку ми прожили три роки до першої помилки в продакшині.
Тож будьте уважні і читайте специфікації 🙂