Как использовать данные весны с couchbase без атрибута _class
Есть ли простой способ использовать весеннюю базу данных с документами, которые не имеют _class
атрибут? В диване у меня что-то подобное в моем sampledata
ковш:
{
"username" : "alice",
"created" : 1473292800000,
"data" : { "a": 1, "b" : "2"},
"type" : "mydata"
}
Теперь, есть ли способ определить отображение из этой структуры документа в объект Java (обратите внимание, что _class
атрибут отсутствует и не может быть добавлен) и наоборот, чтобы я мог получить все (или большинство) автоматических функций из данных рессорной базы?
Что-то вроде: если type
Поле имеет значение "mydata", используйте класс MyData.java. Поэтому, когда поиск выполняется вместо автоматического добавления AND _class = "mydata"
к сгенерированному запросу добавить AND type = "mydata"
,
2 ответа
Spring Data в целом нуждается в _class
поле, чтобы узнать, что нужно создать обратно при десериализации.
В Spring Data Couchbase довольно легко использовать другое имя поля, чем _class
переопределив typeKey()
метод в AbsctractCouchbaseDataConfiguration
,
Но он все равно будет ожидать полного имени класса по умолчанию
Чтобы обойти это, потребуется немного больше работы:
- Вам нужно будет реализовать свой собственный
CouchbaseTypeMapper
, следуя моделиDefaultCouchbaseTypeMapper
, вsuper(...)
конструктор, вам нужно будет предоставить дополнительный аргумент: списокTypeInformationMapper
, Реализация по умолчанию явно не предоставляет, поэтомуSimpleTypeInformationMapper
используется, который является тот, который ставит FQN. - Существует альтернативная реализация, которая настраивается так, что вы можете псевдоним определенных классов с более коротким именем через
Map
:ConfigurableTypeInformationMapper
... 3. Таким образом, поставивConfigurableTypeInformationMapper
с псевдонимом, который вы хотите для определенных классов +SimpleTypeInformationMapper
после этого в списке (если вы сериализовали класс, для которого вы не указали псевдоним), вы можете достичь своей цели. typeMapper
используется вMappingCouchbaseConverter
, который вы также должны будете расширить, к сожалению (просто дляtypeMapper
вместо по умолчанию.- Как только вы это сделаете, снова переопределите конфигурацию, чтобы вернуть экземпляр вашего пользовательского
MappingCouchbaseConverter
который использует ваш обычайCouchbaseTypeMapper
(mappingCouchbaseConverter()
метод).
Вы можете добиться этого, например, создав собственную аннотацию @DocumentType
@DocumentType("billing")
@Document
public class BillingRecordDocument {
String name;
// ...
}
Документ будет выглядеть так:
{
"type" : "billing"
"name" : "..."
}
Просто создайте следующие классы: Создайте собственный AbstractReactiveCouchbaseConfiguration
или AbstractCouchbaseConfiguration
(зависит от того, какой вариант вы используете)
@Configuration
@EnableReactiveCouchbaseRepositories
public class CustomReactiveCouchbaseConfiguration extends AbstractReactiveCouchbaseConfiguration {
// implement abstract methods
// and configure custom mapping convereter
@Bean(name = BeanNames.COUCHBASE_MAPPING_CONVERTER)
public MappingCouchbaseConverter mappingCouchbaseConverter() throws Exception {
MappingCouchbaseConverter converter = new CustomMappingCouchbaseConverter(couchbaseMappingContext(), typeKey());
converter.setCustomConversions(customConversions());
return converter;
}
@Override
public String typeKey() {
return "type"; // this will owerride '_class'
}
}
Создать индивидуальный MappingCouchbaseConverter
public class CustomMappingCouchbaseConverter extends MappingCouchbaseConverter {
public CustomMappingCouchbaseConverter(final MappingContext<? extends CouchbasePersistentEntity<?>,
CouchbasePersistentProperty> mappingContext, final String typeKey) {
super(mappingContext, typeKey);
this.typeMapper = new TypeBasedCouchbaseTypeMapper(typeKey);
}
}
и пользовательские аннотации @DocumentType
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DocumentType {
String value();
}
Затем создайте TypeAwareTypeInformationMapper
который просто проверит, аннотирован ли объект @DocumentType
если да, используйте значение из этой аннотации, сделайте значение по умолчанию, если нет (полное имя класса)
public class TypeAwareTypeInformationMapper extends SimpleTypeInformationMapper {
@Override
public Alias createAliasFor(TypeInformation<?> type) {
DocumentType[] documentType = type.getType().getAnnotationsByType(DocumentType.class);
if (documentType.length == 1) {
return Alias.of(documentType[0].value());
}
return super.createAliasFor(type);
}
}
Затем зарегистрируйте его следующим образом
public class TypeBasedCouchbaseTypeMapper extends DefaultTypeMapper<CouchbaseDocument> implements CouchbaseTypeMapper {
private final String typeKey;
public TypeBasedCouchbaseTypeMapper(final String typeKey) {
super(new DefaultCouchbaseTypeMapper.CouchbaseDocumentTypeAliasAccessor(typeKey),
Collections.singletonList(new TypeAwareTypeInformationMapper()));
this.typeKey = typeKey;
}
@Override
public String getTypeKey() {
return typeKey;
}
}
В вашем классе конфигурации couchbase вам просто нужно иметь:
@Override
public String typeKey() {
return "type";
}
К сожалению, для вывода запроса (n1ql) класс или тип по-прежнему используют имя класса. Пробовал пружинный диван 2.2.6, и здесь минус. @ Симон, знаете ли вы, что что-то изменилось, и поддержка, чтобы иметь возможность иметь пользовательское значение _class/type в следующих выпусках?
@SimonBasle Внутри класса N1qlUtils и метода createWhereFilterForEntity у нас есть доступ к CouchbaseConverter. В сети:
String typeValue = entityInformation.getJavaType().getName();
Почему бы не использовать typeMapper из преобразователя для получения имени объекта, если мы не хотим использовать имя класса? В противном случае вам необходимо аннотировать каждый метод в вашем репозитории следующим образом:
@Query("#{#n1ql.selectEntity} WHERE `type`='airport' AND airportname = $1")
List<Airport> findAirportByAirportname(String airportName);
Если createWhereFilterForEntity использовал CouchbaseConverter, мы могли бы избежать аннотирования с помощью @Query.