DATACOUCH-620 - Fix nested object id/@Id issue.
Only treat the id (or @Id) field in top-level objects as the id.
This commit is contained in:
@@ -402,7 +402,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
typeMapper.writeType(type, target);
|
||||
}
|
||||
|
||||
writeInternal(source, target, type);
|
||||
writeInternal(source, target, type, true);
|
||||
if (target.getId() == null) {
|
||||
throw new MappingException("An ID property is needed, but not found/could not be generated on this entity.");
|
||||
}
|
||||
@@ -416,7 +416,8 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
* @param typeHint the type information for the source.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation<?> typeHint) {
|
||||
protected void writeInternal(final Object source, CouchbaseDocument target, final TypeInformation<?> typeHint,
|
||||
boolean withId) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -437,7 +438,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(source.getClass());
|
||||
writeInternal(source, target, entity);
|
||||
writeInternal(source, target, entity, withId);
|
||||
addCustomTypeKeyIfNecessary(typeHint, source, target);
|
||||
}
|
||||
|
||||
@@ -471,9 +472,10 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
* @param source the source object.
|
||||
* @param target the target document.
|
||||
* @param entity the persistent entity to convert from.
|
||||
* @param withId one of the top-level properties is the id for the document
|
||||
*/
|
||||
protected void writeInternal(final Object source, final CouchbaseDocument target,
|
||||
final CouchbasePersistentEntity<?> entity) {
|
||||
final CouchbasePersistentEntity<?> entity, boolean withId) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -483,7 +485,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
final ConvertingPropertyAccessor<Object> accessor = getPropertyAccessor(source);
|
||||
final CouchbasePersistentProperty idProperty = entity.getIdProperty();
|
||||
final CouchbasePersistentProperty idProperty = withId ? entity.getIdProperty() : null;
|
||||
final CouchbasePersistentProperty versionProperty = entity.getVersionProperty();
|
||||
|
||||
GeneratedValue generatedValueInfo = null;
|
||||
@@ -525,7 +527,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
}
|
||||
|
||||
if (!conversions.isSimpleType(propertyObj.getClass())) {
|
||||
writePropertyInternal(propertyObj, target, prop);
|
||||
writePropertyInternal(propertyObj, target, prop, false);
|
||||
} else {
|
||||
writeSimpleInternal(propertyObj, target, prop.getFieldName());
|
||||
}
|
||||
@@ -552,7 +554,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
Class<?> type = inverseProp.getType();
|
||||
Object propertyObj = accessor.getProperty(inverseProp, type);
|
||||
if (null != propertyObj) {
|
||||
writePropertyInternal(propertyObj, target, inverseProp);
|
||||
writePropertyInternal(propertyObj, target, inverseProp, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -568,7 +570,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writePropertyInternal(final Object source, final CouchbaseDocument target,
|
||||
final CouchbasePersistentProperty prop) {
|
||||
final CouchbasePersistentProperty prop, boolean withId) {
|
||||
if (source == null) {
|
||||
return;
|
||||
}
|
||||
@@ -617,7 +619,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
CouchbasePersistentEntity<?> entity = isSubtype(prop.getType(), source.getClass())
|
||||
? mappingContext.getRequiredPersistentEntity(source.getClass())
|
||||
: mappingContext.getRequiredPersistentEntity(type);
|
||||
writeInternal(source, propertyDoc, entity);
|
||||
writeInternal(source, propertyDoc, entity, false);
|
||||
target.put(name, propertyDoc);
|
||||
}
|
||||
|
||||
@@ -660,7 +662,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
} else {
|
||||
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
|
||||
TypeInformation<?> valueTypeInfo = type.isMap() ? type.getMapValueType() : ClassTypeInformation.OBJECT;
|
||||
writeInternal(val, embeddedDoc, valueTypeInfo);
|
||||
writeInternal(val, embeddedDoc, valueTypeInfo, false);
|
||||
target.put(simpleKey, embeddedDoc);
|
||||
}
|
||||
} else {
|
||||
@@ -706,7 +708,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
} else {
|
||||
|
||||
CouchbaseDocument embeddedDoc = new CouchbaseDocument();
|
||||
writeInternal(element, embeddedDoc, componentType);
|
||||
writeInternal(element, embeddedDoc, componentType, false);
|
||||
target.put(embeddedDoc);
|
||||
}
|
||||
|
||||
@@ -937,7 +939,7 @@ public class MappingCouchbaseConverter extends AbstractCouchbaseConverter implem
|
||||
String expression = property.getSpelExpression();
|
||||
Object value = expression != null ? evaluator.evaluate(expression) : source.get(property.getFieldName());
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
if (property.isIdProperty() && parent == null) {
|
||||
return (R) source.getId();
|
||||
}
|
||||
if (value == null) {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2020 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.annotation.Id;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Course entity for tests
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class Course {
|
||||
@Id private final String id;
|
||||
private final String userId;
|
||||
private final String room;
|
||||
|
||||
public Course(String id, String userId, String room) {
|
||||
this.id = id;
|
||||
this.userId = userId;
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer("Course(");
|
||||
sb.append("id=");
|
||||
sb.append(id);
|
||||
sb.append(", userId=");
|
||||
sb.append(userId);
|
||||
sb.append(", room=");
|
||||
sb.append(room);
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) throws RuntimeException {
|
||||
if (this == that)
|
||||
return true;
|
||||
if (that == null || getClass() != that.getClass())
|
||||
return false;
|
||||
for (Field f : getClass().getFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
for (Field f : getClass().getDeclaredFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean same(Field f, Object a, Object b) {
|
||||
Object thisField = null;
|
||||
Object thatField = null;
|
||||
|
||||
try {
|
||||
f.get(a);
|
||||
f.get(b);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (thisField == null && thatField == null) {
|
||||
return true;
|
||||
}
|
||||
if (thisField == null && thatField != null) {
|
||||
return false;
|
||||
}
|
||||
if (!thisField.equals(thatField)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2020 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.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Submission entity for tests
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class Submission {
|
||||
private final String id;
|
||||
private final String userId;
|
||||
private final String talkId;
|
||||
private final String status;
|
||||
private final long number;
|
||||
|
||||
public Submission(String id, String userId, String talkId, String status, long number) {
|
||||
this.id = id;
|
||||
this.userId = userId;
|
||||
this.talkId = talkId;
|
||||
this.status = status;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer("Submission(");
|
||||
sb.append("id=");
|
||||
sb.append(id);
|
||||
sb.append(", userId=");
|
||||
sb.append(userId);
|
||||
sb.append(", talkId=");
|
||||
sb.append(talkId);
|
||||
sb.append(", status=");
|
||||
sb.append(status);
|
||||
sb.append(", number=");
|
||||
sb.append(number);
|
||||
sb.append(")");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) throws RuntimeException {
|
||||
if (this == that)
|
||||
return true;
|
||||
if (that == null || getClass() != that.getClass())
|
||||
return false;
|
||||
for (Field f : getClass().getFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
for (Field f : getClass().getDeclaredFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean same(Field f, Object a, Object b) {
|
||||
Object thisField = null;
|
||||
Object thatField = null;
|
||||
|
||||
try {
|
||||
f.get(a);
|
||||
f.get(b);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (thisField == null && thatField == null) {
|
||||
return true;
|
||||
}
|
||||
if (thisField == null && thatField != null) {
|
||||
return false;
|
||||
}
|
||||
if (!thisField.equals(thatField)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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 lombok.Data;
|
||||
import org.springframework.data.annotation.TypeAlias;
|
||||
import org.springframework.data.couchbase.core.index.CompositeQueryIndex;
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* UserSubmission entity for tests
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Data
|
||||
@Document
|
||||
@TypeAlias("user")
|
||||
@CompositeQueryIndex(fields = { "id", "username", "email" })
|
||||
public class UserSubmission {
|
||||
private String id;
|
||||
private String username;
|
||||
private String email;
|
||||
private String password;
|
||||
private List<String> roles;
|
||||
private Address address;
|
||||
private int credits;
|
||||
private List<Submission> submissions;
|
||||
private List<Course> courses;
|
||||
|
||||
public void setSubmissions(List<Submission> submissions) {
|
||||
this.submissions = submissions;
|
||||
}
|
||||
|
||||
public void setCourses(List<Course> courses) {
|
||||
this.courses = courses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) throws RuntimeException {
|
||||
if (this == that)
|
||||
return true;
|
||||
if (that == null || getClass() != that.getClass())
|
||||
return false;
|
||||
for (Field f : getClass().getFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
for (Field f : getClass().getDeclaredFields()) {
|
||||
if (!same(f, this, that))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean same(Field f, Object a, Object b) {
|
||||
Object thisField = null;
|
||||
Object thatField = null;
|
||||
|
||||
try {
|
||||
f.get(a);
|
||||
f.get(b);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (thisField == null && thatField == null) {
|
||||
return true;
|
||||
}
|
||||
if (thisField == null && thatField != null) {
|
||||
return false;
|
||||
}
|
||||
if (!thisField.equals(thatField)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.repository.PagingAndSortingRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
/**
|
||||
* UserSubmission Repository for tests
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
public interface UserSubmissionRepository extends PagingAndSortingRepository<UserSubmission, String> {
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@ package org.springframework.data.couchbase.repository;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -27,10 +28,14 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.domain.Course;
|
||||
import org.springframework.data.couchbase.domain.Library;
|
||||
import org.springframework.data.couchbase.domain.LibraryRepository;
|
||||
import org.springframework.data.couchbase.domain.Submission;
|
||||
import org.springframework.data.couchbase.domain.User;
|
||||
import org.springframework.data.couchbase.domain.UserRepository;
|
||||
import org.springframework.data.couchbase.domain.UserSubmission;
|
||||
import org.springframework.data.couchbase.domain.UserSubmissionRepository;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
@@ -49,6 +54,7 @@ public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareInt
|
||||
|
||||
@Autowired UserRepository userRepository;
|
||||
@Autowired LibraryRepository libraryRepository;
|
||||
@Autowired UserSubmissionRepository userSubmissionRepository;
|
||||
|
||||
@Test
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
@@ -89,6 +95,31 @@ public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareInt
|
||||
assertFalse(userRepository.existsById(library.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
|
||||
void saveAndFindByWithNestedId() {
|
||||
UserSubmission user = new UserSubmission();
|
||||
user.setId(UUID.randomUUID().toString());
|
||||
user.setSubmissions(
|
||||
Arrays.asList(new Submission(UUID.randomUUID().toString(), user.getId(), "tid", "status", 123)));
|
||||
user.setCourses(Arrays.asList(new Course(UUID.randomUUID().toString(), user.getId(), "581")));
|
||||
|
||||
// this currently fails when using mocked in integration.properties with status "UNKNOWN"
|
||||
assertFalse(userRepository.existsById(user.getId()));
|
||||
|
||||
userSubmissionRepository.save(user);
|
||||
|
||||
Optional<UserSubmission> found = userSubmissionRepository.findById(user.getId());
|
||||
assertTrue(found.isPresent());
|
||||
found.ifPresent(u -> assertEquals(user, u));
|
||||
|
||||
assertTrue(userRepository.existsById(user.getId()));
|
||||
assertEquals(user.getSubmissions().get(0).getId(), found.get().getSubmissions().get(0).getId());
|
||||
assertEquals(user.getCourses().get(0).getId(), found.get().getCourses().get(0).getId());
|
||||
assertEquals(user, found.get());
|
||||
userSubmissionRepository.delete(user);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
|
||||
static class Config extends AbstractCouchbaseConfiguration {
|
||||
|
||||
Reference in New Issue
Block a user