Как использовать данные Spring с couchbase без атрибута _class

Есть ли простой способ использовать базу данных Spring с документами, у которых нет атрибута _class? На диване у меня в sampledata ведре что-то вроде этого:

{
  "username" : "alice", 
  "created" : 1473292800000,
  "data" : { "a": 1, "b" : "2"},
  "type" : "mydata"
}

Теперь, есть ли способ определить отображение из этой структуры документа в объект Java (обратите внимание, что атрибут _class отсутствует и не может быть добавлен) и наоборот, чтобы я получил все (или большинство) автоматических функций из данных Spring couchbase?

Что-то вроде: Если поле type имеет значение "mydata", используйте класс MyData.java. Поэтому, когда поиск выполняется вместо автоматического добавления AND _class = "mydata" к сгенерированному запросу, добавьте AND type = "mydata".


person Milan    schedule 09.08.2016    source источник


Ответы (4)


Spring Data в целом нуждается в поле _class, чтобы знать, что нужно создать обратно при десериализации.

В Spring Data Couchbase довольно просто использовать имя поля, отличное от _class, переопределив метод typeKey() в AbsctractCouchbaseDataConfiguration.

Но по умолчанию он все равно будет ожидать наличие полного имени класса

Чтобы обойти это, потребуется немного больше работы:

  1. Вам нужно будет реализовать свой собственный CouchbaseTypeMapper, следуя модели DefaultCouchbaseTypeMapper. В конструкторе super(...) вам необходимо указать дополнительный аргумент: список TypeInformationMapper. Реализация по умолчанию не предоставляет его явно, поэтому используется SimpleTypeInformationMapper, который помещает FQN.
  2. Существует альтернативная реализация, которая настраивается так, что вы можете присваивать конкретным классам более короткое имя с помощью Map: _11 _...
  3. Итак, поместив ConfigurableTypeInformationMapper с псевдонимом, который вы хотите для определенных классов + SimpleTypeInformationMapper после него в списке (в случае, если вы сериализуете класс, для которого вы не указали псевдоним), вы можете достичь своей цели.
  4. typeMapper используется в MappingCouchbaseConverter, который, к сожалению, вам также потребуется расширить (просто чтобы создать экземпляр typeMapper вместо значения по умолчанию.
  5. Как только у вас есть это, снова переопределите конфигурацию, чтобы вернуть экземпляр вашего пользовательского MappingCouchbaseConverter, который использует ваш собственный CouchbaseTypeMapper (метод mappingCouchbaseConverter()).
person Simon Baslé    schedule 19.08.2016
comment
Саймон, когда вы сказали, что FQN ожидается по умолчанию. Значит ли это, что его можно переопределить? Я спрашиваю об этом, потому что я пробовал то, что вы рекомендовали, он работал, сохраняя в базе данных, но при извлечении (например, findByField (...)) фильтр typeKey использует FQN целевого класса, т.е. игнорирует кастомный конвертер. - person Zava; 05.02.2017
comment
Можете ли вы подтвердить, что невозможно использовать псевдоним вместо полного имени класса при использовании методов запроса Spring? Я делаю такой вывод, глядя на N1qlUtils # createWhereFilterForEntity - person Happy; 10.05.2017
comment
Правильно, AFAIK прямо сейчас это невозможно или, по крайней мере, официально не поддерживается - person Simon Baslé; 10.05.2017
comment
@ SimonBaslé, возможно, ответ можно изменить, чтобы указать ограничения. Об этом подходе и о том, что он в конечном итоге не сработает. - person Jade; 05.03.2018

Вы можете добиться этого, например, путем создания пользовательской аннотации @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;
    }
}
person Peter Jurkovic    schedule 12.08.2019
comment
это было очень полезно, теперь я могу создавать документы с настраиваемым ключом типа и значением типа, НО мои методы репозитория spring-data-couchbase, такие как findById, больше не работают. Из того, что я понял из следующих ответов, возможно, это связано с тем, что предложение where все еще использует полностью определенное имя класса Java в качестве значения типа, несмотря ни на что ... Вы нашли способ обойти это? - person SnoopDougg; 26.03.2020

В классе конфигурации дивана вам просто необходимо иметь:

@Override
public String typeKey() {
    return "type";
}

К сожалению, для вывода запроса (n1ql) _class или тип все еще используют имя класса. Пробовал spring couch 2.2.6, и здесь это минус. @Simon, знаете ли вы, что что-то изменилось и появилась поддержка, чтобы иметь возможность иметь настраиваемое значение _class / type в следующих выпусках?

person Gabriel    schedule 22.08.2017
comment
Я делаю исправление для запросов n1ql и findBy *, чтобы использовать настраиваемый тип. - person mn_test347; 22.04.2020

@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.

person Peter Martin    schedule 04.03.2019