Reputation: 275
Is there a simple way to use spring data couchbase with documents that do not have _class
attribute?
In the couchbase I have something like this in my sampledata
bucket:
{
"username" : "alice",
"created" : 1473292800000,
"data" : { "a": 1, "b" : "2"},
"type" : "mydata"
}
Now, is there any way to define mapping from this structure of document to Java object (note that _class
attribute is missing and cannot be added) and vice versa so that I get all (or most) automagical features from spring couchbase data?
Something like:
If type
field has value "mydata" use class MyData.java.
So when find is performed instead of automatically adding AND _class = "mydata"
to generated query add AND type = "mydata"
.
Upvotes: 8
Views: 5515
Reputation: 11
+1 on Peter Jurkovics answer org.springframework.data.couchbase.core.query.Query watch the typeValue from alias on
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues
...
Alias alias = converter.getTypeAlias(typeInfo);
typeValue = alias.toString();
...
this should show the DocumentType value and the filter should be shown on the StringBasedN1qlQueryParser
StringBasedN1qlQueryParser sbnqp = new StringBasedN1qlQueryParser(bucketName, scopeName, collectionName, converter, domainClass, returnClass, typeKey, typeValue, isCount, distinctFields, fields);
on TypeBasedCouchbaseTypeMapper class I had to add
@Override
public Alias getTypeAlias(TypeInformation<?> typeInformation) {
if (this.typeKey != null) {
return new TypeAwareTypeInformationMapper().createAliasFor(typeInformation);
} else {
return Alias.of(typeInformation.getType());
}
}
Upvotes: 1
Reputation: 11
This can be done by doing the following:
typeKey()
in AbstractCouchbaseConfiguration or AbstractReactiveCouchbaseConfiguration, this is "_class" by defaultpublic class CustomCouchbaseConfiguration extends AbstractCouchbaseConfiguration {
@Override
public String typeKey() {
return "type";
}
}
@TypeAlias("mydata")
@Document
@TypeAlias("mydata")
public class MyDataEntity {}
This will also automatically be added when persisting your object.
Upvotes: 0
Reputation: 2896
You can achive this e.g. by creating custom annotation @DocumentType
@DocumentType("billing")
@Document
public class BillingRecordDocument {
String name;
// ...
}
Document will look like:
{
"type" : "billing"
"name" : "..."
}
Just create following classes:
Create custom AbstractReactiveCouchbaseConfiguration
or AbstractCouchbaseConfiguration
(depends which varian you use)
@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'
}
}
Create custom 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);
}
}
and custom annotation @DocumentType
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface DocumentType {
String value();
}
Then create TypeAwareTypeInformationMapper
which will just check if an entity is annoatated by @DocumentType
if so, use value from that annotation, do the default if not (fully qualified class name)
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);
}
}
Then register it as following
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;
}
}
Upvotes: 5
Reputation: 28301
Spring Data in general needs the _class
field to know what to instantiate back when deserializing.
It's fairly easy in Spring Data Couchbase to use a different field name than _class
, by overriding the typeKey()
method in the AbsctractCouchbaseDataConfiguration
.
But it'll still expect a fully qualified classname in there by default
Getting around that will require quite a bit more work:
CouchbaseTypeMapper
, following the model of DefaultCouchbaseTypeMapper
. In the super(...)
constructor, you'll need to provide an additional argument: a list of TypeInformationMapper
. The default implementation doesn't explicitly provide one, so a SimpleTypeInformationMapper
is used, which is the one that puts FQNs.Map
: ConfigurableTypeInformationMapper
...ConfigurableTypeInformationMapper
with the alias you want for specific classes + a SimpleTypeInformationMapper
after it in the list (for the case were you serialize a class that you didn't provide an alias for), you can achieve your goal.typeMapper
is used within the MappingCouchbaseConverter
, which you'll also need to extend unfortunately (just to instantiate your typeMapper
instead of the default.MappingCouchbaseConverter
that uses your custom CouchbaseTypeMapper
(the mappingCouchbaseConverter()
method).Upvotes: 8
Reputation: 76
@SimonBasle Inside of class N1qlUtils and method createWhereFilterForEntity we have access to the CouchbaseConverter. On line:
String typeValue = entityInformation.getJavaType().getName();
Why not use the typeMapper from the converter to get the name of the entity when we want to avoid using the class name? Otherwise you have to annotate each method in your repository as follows:
@Query("#{#n1ql.selectEntity} WHERE `type`='airport' AND airportname = $1")
List<Airport> findAirportByAirportname(String airportName);
If createWhereFilterForEntity used the CouchbaseConverter we could avoid annotating with the @Query.
Upvotes: 2
Reputation: 31
In your couchbase configuration class you just need to have :
@Override
public String typeKey() {
return "type";
}
Unfortunately for query derivation (n1ql) the _class or type are still using the class name.Tried spring couch 2.2.6 and it's minus point here. @Simon, are you aware that something has changed and the support to have the possibility to have custom _class/type value in next release(s)?
Upvotes: 3