diff --git a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java index 07087759c..b86806abb 100644 --- a/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java +++ b/src/main/java/org/springframework/data/couchbase/core/CouchbaseTemplateSupport.java @@ -87,6 +87,18 @@ public CouchbaseDocument encodeEntity(final Object entityToEncode) { public T decodeEntity(String id, String source, long cas, Class entityClass, String scope, String collection) { final CouchbaseDocument converted = new CouchbaseDocument(id); converted.setId(id); + + // this is the entity class defined for the repository. It may not be the class of the document that was read + // we will reset it after reading the document + // + // This will fail for the case where: + // 1) The version is defined in the concrete class, but not in the abstract class; and + // 2) The constructor takes a "long version" argument resulting in an exception would be thrown if version in + // the source is null. + // We could expose from the MappingCouchbaseConverter determining the persistent entity from the source, + // but that is a lot of work to do every time just for this very rare and avoidable case. + // TypeInformation typeToUse = typeMapper.readType(source, type); + CouchbasePersistentEntity persistentEntity = couldBePersistentEntity(entityClass); if (persistentEntity == null) { // method could return a Long, Boolean, String etc. @@ -99,14 +111,24 @@ public T decodeEntity(String id, String source, long cas, Class entityCla return (T) set.iterator().next().getValue(); } + // if possible, set the version property in the source so that if the constructor has a long version argument, + // it will have a value an not fail (as null is not a valid argument for a long argument). This possible failure + // can be avoid by defining the argument as Long instead of long. + // persistentEntity is still the (possibly abstract) class specified in the repository definition + // it's possible that the abstract class does not have a version property, and this won't be able to set the version if (cas != 0 && persistentEntity.getVersionProperty() != null) { converted.put(persistentEntity.getVersionProperty().getName(), cas); } + // if the constructor has an argument that is long version, then construction will fail if the 'version' + // is not available as 'null' is not a legal value for a long. Changing the arg to "Long version" would solve this. + // (Version doesn't come from 'source', it comes from the cas argument to decodeEntity) T readEntity = converter.read(entityClass, (CouchbaseDocument) translationService.decode(source, converted)); final ConvertingPropertyAccessor accessor = getPropertyAccessor(readEntity); - if (persistentEntity.getVersionProperty() != null) { + persistentEntity = couldBePersistentEntity(readEntity.getClass()); + + if (cas != 0 && persistentEntity.getVersionProperty() != null) { accessor.setProperty(persistentEntity.getVersionProperty(), cas); } N1qlJoinResolver.handleProperties(persistentEntity, accessor, template.reactive(), id, scope, collection); diff --git a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java index 9d1d0b686..53c2561ef 100644 --- a/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java +++ b/src/main/java/org/springframework/data/couchbase/core/mapping/BasicCouchbasePersistentProperty.java @@ -77,6 +77,10 @@ public String getFieldName() { if (fieldName != null) { return fieldName; } + if (getField() == null) { // use the name of the property - instead of getting an NPE trying to use field + return fieldName = getName(); + } + Field annotationField = getField().getAnnotation(Field.class); if (annotationField != null) { diff --git a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java index e9790c77b..d8140e89a 100644 --- a/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java +++ b/src/main/java/org/springframework/data/couchbase/repository/query/StringBasedN1qlQueryParser.java @@ -20,6 +20,7 @@ import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_CAS; import static org.springframework.data.couchbase.core.support.TemplateUtils.SELECT_ID; +import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -192,8 +193,8 @@ private String getProjectedOrDistinctFields(String b, Class resultClass, String if (distinctFields != null && distinctFields.length != 0) { return i(distinctFields).toString(); } - String projectedFields = i(b) + ".*"; - if (resultClass != null) { + String projectedFields = i(b) + ".*"; // if we can't get further information of the fields needed project everything + if (resultClass != null && !Modifier.isAbstract(resultClass.getModifiers())) { PersistentEntity persistentEntity = couchbaseConverter.getMappingContext().getPersistentEntity(resultClass); StringBuilder sb = new StringBuilder(); getProjectedFieldsInternal(b, null, sb, persistentEntity, typeField, fields, distinctFields != null); @@ -528,9 +529,8 @@ public N1qlSpelValues(String selectClause, String entityFields, String bucket, S } // copied from StringN1qlBasedQuery - private N1QLExpression getExpression(ParameterAccessor accessor, - ReturnedType returnedType, SpelExpressionParser parser, - QueryMethodEvaluationContextProvider evaluationContextProvider) { + private N1QLExpression getExpression(ParameterAccessor accessor, ReturnedType returnedType, + SpelExpressionParser parser, QueryMethodEvaluationContextProvider evaluationContextProvider) { boolean isCountQuery = queryMethod.isCountQuery(); Object[] runtimeParameters = getParameters(accessor); EvaluationContext evaluationContext = evaluationContextProvider.getEvaluationContext(queryMethod.getParameters(), diff --git a/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java index 77a55c3c5..2ca6bb982 100644 --- a/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/core/CustomTypeKeyIntegrationTests.java @@ -62,8 +62,7 @@ void saveSimpleEntityCorrectlyWithDifferentTypeKey() { assertEquals(user, modified); GetResult getResult = clientFactory.getCollection(null).get(user.getId()); - assertEquals("org.springframework.data.couchbase.domain.User", - getResult.contentAsObject().getString(CUSTOM_TYPE_KEY)); + assertEquals("abstractuser", getResult.contentAsObject().getString(CUSTOM_TYPE_KEY)); assertFalse(getResult.contentAsObject().containsKey(DefaultCouchbaseTypeMapper.DEFAULT_TYPE_KEY)); operations.removeById(User.class).one(user.getId()); } diff --git a/src/test/java/org/springframework/data/couchbase/domain/AbstractUser.java b/src/test/java/org/springframework/data/couchbase/domain/AbstractUser.java index 00d106d15..94e41aa82 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AbstractUser.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AbstractUser.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,35 +16,21 @@ package org.springframework.data.couchbase.domain; -import java.util.Objects; - -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.annotation.PersistenceConstructor; -import org.springframework.data.annotation.Transient; -import org.springframework.data.annotation.Version; -import org.springframework.data.couchbase.core.mapping.Document; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.couchbase.core.mapping.Field; /** * User entity for tests * * @author Michael Reiche */ - +@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER) public abstract class AbstractUser extends ComparableEntity { - - @Version protected long version; @Id protected String id; protected String firstname; protected String lastname; - @Transient protected String transientInfo; - @CreatedBy protected String createdBy; - @CreatedDate protected long createdDate; - @LastModifiedBy protected String lastModifiedBy; - @LastModifiedDate protected long lastModifiedDate; + @Field(AbstractingTypeMapper.SUBTYPE) protected String subtype; public String getId() { return id; @@ -53,53 +39,4 @@ public String getId() { public String getFirstname() { return firstname; } - - public String getLastname() { - return lastname; - } - - public long getCreatedDate() { - return createdDate; - } - - public void setCreatedDate(long createdDate) { - this.createdDate = createdDate; - } - - public String getCreatedBy() { - return createdBy; - } - - public void setCreatedBy(String createdBy) { - this.createdBy = createdBy; - } - - public long getLastModifiedDate() { - return lastModifiedDate; - } - - public String getLastModifiedBy() { - return lastModifiedBy; - } - - public long getVersion() { - return version; - } - - public void setVersion(long version) { - this.version = version; - } - - @Override - public int hashCode() { - return Objects.hash(getId(), firstname, lastname); - } - - public String getTransientInfo() { - return transientInfo; - } - - public void setTransientInfo(String something) { - transientInfo = something; - } } diff --git a/src/test/java/org/springframework/data/couchbase/domain/AbstractUserRepository.java b/src/test/java/org/springframework/data/couchbase/domain/AbstractUserRepository.java index 5b6f3f85b..1fa94d7e3 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/AbstractUserRepository.java +++ b/src/test/java/org/springframework/data/couchbase/domain/AbstractUserRepository.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2021 the original author or authors + * Copyright 2012-2022 the original author or authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.util.List; import java.util.stream.Stream; -import com.couchbase.client.java.query.QueryScanConsistency; import org.springframework.data.couchbase.repository.CouchbaseRepository; import org.springframework.data.couchbase.repository.Query; import org.springframework.data.couchbase.repository.ScanConsistency; @@ -27,6 +26,7 @@ import org.springframework.stereotype.Repository; import com.couchbase.client.java.json.JsonArray; +import com.couchbase.client.java.query.QueryScanConsistency; /** * AbstractUser Repository for tests @@ -34,13 +34,13 @@ * @author Michael Reiche */ @Repository -@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS) +@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS) public interface AbstractUserRepository extends CouchbaseRepository { @Query("#{#n1ql.selectEntity} where (meta().id = $1)") AbstractUser myFindById(String id); - List findByFirstname(String firstname); + List findByFirstname(String firstname); Stream findByLastname(String lastname); diff --git a/src/test/java/org/springframework/data/couchbase/domain/AbstractingMappingCouchbaseConverter.java b/src/test/java/org/springframework/data/couchbase/domain/AbstractingMappingCouchbaseConverter.java new file mode 100644 index 000000000..bde87cf29 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/AbstractingMappingCouchbaseConverter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.couchbase.domain; + +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity; +import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty; +import org.springframework.data.mapping.context.MappingContext; + +/** + * MappingConverter that uses AbstractTypeMapper + * + * @author Michael Reiche + */ +public class AbstractingMappingCouchbaseConverter extends MappingCouchbaseConverter { + + /** + * this constructer creates a TypeBasedCouchbaseTypeMapper with the specified typeKey while MappingCouchbaseConverter + * uses a DefaultCouchbaseTypeMapper typeMapper = new DefaultCouchbaseTypeMapper(typeKey != null ? typeKey : + * TYPEKEY_DEFAULT); + * + * @param mappingContext + * @param typeKey - the typeKey to be used (normally "_class") + */ + public AbstractingMappingCouchbaseConverter( + final MappingContext, CouchbasePersistentProperty> mappingContext, + final String typeKey) { + super(mappingContext, typeKey); + this.typeMapper = new AbstractingTypeMapper(typeKey); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/AbstractingTypeMapper.java b/src/test/java/org/springframework/data/couchbase/domain/AbstractingTypeMapper.java new file mode 100644 index 000000000..ea9314490 --- /dev/null +++ b/src/test/java/org/springframework/data/couchbase/domain/AbstractingTypeMapper.java @@ -0,0 +1,97 @@ +/* + * Copyright 2022 the original author or authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.couchbase.domain; + +import java.util.Collections; + +import org.springframework.data.convert.DefaultTypeMapper; +import org.springframework.data.convert.TypeAliasAccessor; +import org.springframework.data.couchbase.core.convert.CouchbaseTypeMapper; +import org.springframework.data.couchbase.core.mapping.CouchbaseDocument; +import org.springframework.data.mapping.Alias; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.util.TypeInformation; + +/** + * TypeMapper that leverages subtype + * + * @author Michael Reiche + */ +public class AbstractingTypeMapper extends DefaultTypeMapper implements CouchbaseTypeMapper { + + public static final String SUBTYPE = "subtype"; + private final String typeKey; + + public static class Type { + public static final String ABSTRACTUSER = "abstractuser", USER = "user", OTHERUSER = "otheruser"; + } + + /** + * Create a new type mapper with the type key. + * + * @param typeKey the typeKey to use. + */ + public AbstractingTypeMapper(final String typeKey) { + super(new CouchbaseDocumentTypeAliasAccessor(typeKey), (MappingContext) null, Collections + .singletonList(new org.springframework.data.couchbase.core.convert.TypeAwareTypeInformationMapper())); + this.typeKey = typeKey; + } + + @Override + public String getTypeKey() { + return this.typeKey; + } + + public static final class CouchbaseDocumentTypeAliasAccessor implements TypeAliasAccessor { + + private final String typeKey; + + public CouchbaseDocumentTypeAliasAccessor(final String typeKey) { + this.typeKey = typeKey; + } + + @Override + public Alias readAliasFrom(final CouchbaseDocument source) { + String alias = (String) source.get(typeKey); + if (Type.ABSTRACTUSER.equals(alias)) { + String subtype = (String) source.get(AbstractingTypeMapper.SUBTYPE); + if (Type.OTHERUSER.equals(subtype)) { + alias = OtherUser.class.getName(); + } else if (Type.USER.equals(subtype)) { + alias = User.class.getName(); + } else { + throw new RuntimeException( + "no mapping for type " + SUBTYPE + "=" + subtype + " in type " + alias + " source=" + source); + } + } + return Alias.ofNullable(alias); + } + + @Override + public void writeTypeTo(final CouchbaseDocument sink, final Object alias) { + if (typeKey != null) { + sink.put(typeKey, alias); + } + } + } + + @Override + public Alias getTypeAlias(TypeInformation info) { + return getAliasFor(info); + } + +} diff --git a/src/test/java/org/springframework/data/couchbase/domain/OtherUser.java b/src/test/java/org/springframework/data/couchbase/domain/OtherUser.java index 4d5468654..4829ed451 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/OtherUser.java +++ b/src/test/java/org/springframework/data/couchbase/domain/OtherUser.java @@ -17,6 +17,7 @@ package org.springframework.data.couchbase.domain; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.TypeAlias; import org.springframework.data.couchbase.core.mapping.Document; /** @@ -26,6 +27,7 @@ */ @Document +@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER) public class OtherUser extends AbstractUser { @PersistenceConstructor @@ -33,6 +35,7 @@ public OtherUser(final String id, final String firstname, final String lastname) this.id = id; this.firstname = firstname; this.lastname = lastname; + this.subtype = AbstractingTypeMapper.Type.OTHERUSER; } } diff --git a/src/test/java/org/springframework/data/couchbase/domain/User.java b/src/test/java/org/springframework/data/couchbase/domain/User.java index c228e6bf4..5a841edb8 100644 --- a/src/test/java/org/springframework/data/couchbase/domain/User.java +++ b/src/test/java/org/springframework/data/couchbase/domain/User.java @@ -16,11 +16,19 @@ package org.springframework.data.couchbase.domain; +import java.io.Serializable; +import java.util.Objects; + +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.annotation.PersistenceConstructor; +import org.springframework.data.annotation.Transient; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.annotation.Version; import org.springframework.data.couchbase.core.mapping.Document; -import java.io.Serializable; - /** * User entity for tests * @@ -29,13 +37,71 @@ */ @Document -public class User extends AbstractUser implements Serializable { +@TypeAlias(AbstractingTypeMapper.Type.ABSTRACTUSER) +public class User extends AbstractUser implements Serializable { @PersistenceConstructor public User(final String id, final String firstname, final String lastname) { this.id = id; this.firstname = firstname; this.lastname = lastname; + this.subtype = AbstractingTypeMapper.Type.USER; + } + + @Version protected long version; + @Transient protected String transientInfo; + @CreatedBy protected String createdBy; + @CreatedDate protected long createdDate; + @LastModifiedBy protected String lastModifiedBy; + @LastModifiedDate protected long lastModifiedDate; + + public String getLastname() { + return lastname; + } + + public long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(long createdDate) { + this.createdDate = createdDate; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public long getLastModifiedDate() { + return lastModifiedDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + @Override + public int hashCode() { + return Objects.hash(getId(), firstname, lastname); + } + + public String getTransientInfo() { + return transientInfo; + } + + public void setTransientInfo(String something) { + transientInfo = something; } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseAbstractRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseAbstractRepositoryIntegrationTests.java index 3eb99d838..e1e4c764d 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/CouchbaseAbstractRepositoryIntegrationTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/CouchbaseAbstractRepositoryIntegrationTests.java @@ -18,15 +18,21 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.List; import java.util.Optional; import java.util.UUID; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration; +import org.springframework.data.couchbase.core.convert.CouchbaseCustomConversions; +import org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter; +import org.springframework.data.couchbase.core.mapping.CouchbaseMappingContext; import org.springframework.data.couchbase.domain.AbstractUser; import org.springframework.data.couchbase.domain.AbstractUserRepository; +import org.springframework.data.couchbase.domain.AbstractingMappingCouchbaseConverter; import org.springframework.data.couchbase.domain.OtherUser; import org.springframework.data.couchbase.domain.User; import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories; @@ -50,38 +56,50 @@ public class CouchbaseAbstractRepositoryIntegrationTests extends ClusterAwareInt void saveAndFindAbstract() { // User extends AbstractUser // OtherUser extends Abstractuser - AbstractUser user = null; + { - user = new User(UUID.randomUUID().toString(), "userFirstname", "userLastname"); - assertEquals(User.class, user.getClass()); - abstractUserRepository.save(user); + User concreteUser = null; { + concreteUser = new User(UUID.randomUUID().toString(), "userFirstname", "userLastname"); + assertEquals(User.class, concreteUser.getClass()); + concreteUser = abstractUserRepository.save(concreteUser); // this will now have version set // Queries on repositories for abstract entities must be @Query and not include // #{#n1ql.filter} (i.e. _class = ) as the classname will not match any document - AbstractUser found = abstractUserRepository.myFindById(user.getId()); - assertEquals(user, found); - assertEquals(user.getClass(), found.getClass()); + AbstractUser found = abstractUserRepository.myFindById(concreteUser.getId()); + assertEquals(concreteUser, found); + assertEquals(concreteUser.getClass(), found.getClass()); + } + { + Optional found = abstractUserRepository.findById(concreteUser.getId()); + assertEquals(concreteUser, found.get()); } { - Optional found = abstractUserRepository.findById(user.getId()); - assertEquals(user, found.get()); + List found = abstractUserRepository.findByFirstname(concreteUser.getFirstname()); + assertEquals(1, found.size(), "should have found one user"); + assertEquals(concreteUser, found.get(0)); } - abstractUserRepository.delete(user); + abstractUserRepository.delete(concreteUser); } { - user = new OtherUser(UUID.randomUUID().toString(), "userFirstname", "userLastname"); - assertEquals(OtherUser.class, user.getClass()); - abstractUserRepository.save(user); + AbstractUser abstractUser = new OtherUser(UUID.randomUUID().toString(), "userFirstname", "userLastname"); + assertEquals(OtherUser.class, abstractUser.getClass()); + abstractUserRepository.save(abstractUser); + { + // not going to find this one as using the type _class = AbstractUser ??? + AbstractUser found = abstractUserRepository.myFindById(abstractUser.getId()); + assertEquals(abstractUser, found); + assertEquals(abstractUser.getClass(), found.getClass()); + } { - AbstractUser found = abstractUserRepository.myFindById(user.getId()); - assertEquals(user, found); - assertEquals(user.getClass(), found.getClass()); + Optional found = abstractUserRepository.findById(abstractUser.getId()); + assertEquals(abstractUser, found.get()); } { - Optional found = abstractUserRepository.findById(user.getId()); - assertEquals(user, found.get()); + List found = abstractUserRepository.findByFirstname(abstractUser.getFirstname()); + assertEquals(1, found.size(), "should have found one user"); + assertEquals(abstractUser, found.get(0)); } - abstractUserRepository.delete(user); + abstractUserRepository.delete(abstractUser); } } @@ -110,6 +128,24 @@ public String getBucketName() { return bucketName(); } + /** + * This uses a CustomMappingCouchbaseConverter instead of MappingCouchbaseConverter, which in turn uses + * AbstractTypeMapper which has special mapping for AbstractUser + */ + @Override + @Bean(name = "mappingCouchbaseConverter") + public MappingCouchbaseConverter mappingCouchbaseConverter(CouchbaseMappingContext couchbaseMappingContext, + CouchbaseCustomConversions couchbaseCustomConversions /* there is a customConversions() method bean */) { + // MappingCouchbaseConverter relies on a SimpleInformationMapper + // that has an getAliasFor(info) that just returns getType().getName(). + // Our CustomMappingCouchbaseConverter uses a TypeBasedCouchbaseTypeMapper that will + // use the DocumentType annotation + MappingCouchbaseConverter converter = new AbstractingMappingCouchbaseConverter(couchbaseMappingContext, + typeKey()); + converter.setCustomConversions(couchbaseCustomConversions); + return converter; + } + } } diff --git a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java index c4594f22a..516f4c9ca 100644 --- a/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java +++ b/src/test/java/org/springframework/data/couchbase/repository/query/StringN1qlQueryCreatorMockedTests.java @@ -85,7 +85,7 @@ void createsQueryCorrectly() throws Exception { Query query = creator.createQuery(); assertEquals( - "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2", + "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and firstname = $1 and lastname = $2", query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); } @@ -104,7 +104,7 @@ void createsQueryCorrectly2() throws Exception { Query query = creator.createQuery(); assertEquals( - "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)", + "SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate`, `firstname`, `lastname`, `subtype` FROM `travel-sample` where `_class` = \"abstractuser\" and (firstname = $first or lastname = $last)", query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false)); }