diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java index 443108d2f..647f8531a 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java @@ -18,6 +18,7 @@ package org.springframework.data.mongodb.repository.support; import org.bson.types.ObjectId; import org.jspecify.annotations.Nullable; import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mongodb.core.convert.LazyLoadingProxy; import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.repository.query.MongoEntityInformation; @@ -107,6 +108,13 @@ public class MappingMongoEntityInformation extends PersistentEntityInform return fallbackIdType; } + @Override + public boolean isNew(T entity) { + + T unwrapped = entity instanceof LazyLoadingProxy proxy ? (T) proxy.getTarget() : entity; + return super.isNew(unwrapped); + } + @Override public boolean isVersioned() { return this.entityMetadata.hasVersionProperty(); diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java index eeca60bc3..090570590 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java @@ -77,6 +77,7 @@ public class Person extends Contact { User unwrappedUser; @DocumentReference User spiritAnimal; + @DocumentReference(lazy = true) User lazySpiritAnimal; int visits; @@ -325,6 +326,14 @@ public class Person extends Contact { this.spiritAnimal = spiritAnimal; } + public User getLazySpiritAnimal() { + return lazySpiritAnimal; + } + + public void setLazySpiritAnimal(User lazySpiritAnimal) { + this.lazySpiritAnimal = lazySpiritAnimal; + } + @Override public int hashCode() { diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java index f94a52e91..f985d8e37 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java @@ -15,8 +15,9 @@ */ package org.springframework.data.mongodb.repository; -import static org.assertj.core.api.Assertions.*; -import static org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils.assertProxyIsResolved; import java.util.ArrayList; import java.util.Arrays; @@ -37,12 +38,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; * * @author Thomas Darimont * @author Oliver Gierke + * @author Christoph Strobl */ @ContextConfiguration(locations = "PersonRepositoryIntegrationTests-context.xml") @ExtendWith(SpringExtension.class) public class PersonRepositoryLazyLoadingIntegrationTests { @Autowired PersonRepository repository; + @Autowired UserRepository userRepository; @Autowired MongoOperations operations; @BeforeEach @@ -123,4 +126,47 @@ public class PersonRepositoryLazyLoadingIntegrationTests { assertProxyIsResolved(coworker, true); assertThat(coworker.getUsername()).isEqualTo(thomas.getUsername()); } + + @Test // GH-5031 + void allowsSavingEntityLoadedViaLazyDBRef() { + + User thomas = new User(); + thomas.id = "tom"; + thomas.username = "Thomas"; + userRepository.save(thomas); + + Person oliver = new Person(); + oliver.id = "ollie"; + oliver.setFirstname("Oliver"); + oliver.coworker = thomas; + repository.save(oliver); + + Person loaded = repository.findById(oliver.id).get(); + User coworker = loaded.getCoworker(); + assertThat(coworker.getUsername()).isEqualTo(thomas.getUsername()); + + assertThatNoException().isThrownBy(() -> userRepository.save(coworker)); + } + + @Test // GH-5031 + void allowsSavingEntityLoadedViaLazyDocumentReference() { + + User thomas = new User(); + thomas.id = "tom"; + thomas.username = "Thomas"; + userRepository.save(thomas); + + Person oliver = new Person(); + oliver.id = "ollie"; + oliver.setFirstname("Oliver"); + oliver.lazySpiritAnimal = thomas; + repository.save(oliver); + + Person loaded = repository.findById(oliver.id).get(); + User spiritAnimal = loaded.getLazySpiritAnimal(); + assertThat(spiritAnimal.getUsername()).isEqualTo(thomas.getUsername()); + + assertThatNoException().isThrownBy(() -> userRepository.save(spiritAnimal)); + } + } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/UserRepository.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/UserRepository.java new file mode 100644 index 000000000..1226331c7 --- /dev/null +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/UserRepository.java @@ -0,0 +1,23 @@ +/* + * Copyright 2025 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.mongodb.repository; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Christoph Strobl + */ +public interface UserRepository extends CrudRepository {} diff --git a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml index 1fc7b9bea..8c6194f0a 100644 --- a/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml +++ b/spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml @@ -19,4 +19,9 @@ + + + + + diff --git a/src/main/antora/modules/ROOT/pages/mongodb/mapping/document-references.adoc b/src/main/antora/modules/ROOT/pages/mongodb/mapping/document-references.adoc index 1dec452dc..b5e618d00 100644 --- a/src/main/antora/modules/ROOT/pages/mongodb/mapping/document-references.adoc +++ b/src/main/antora/modules/ROOT/pages/mongodb/mapping/document-references.adoc @@ -47,10 +47,12 @@ Required properties that are also defined as lazy loading ``DBRef`` and used as TIP: Lazily loaded ``DBRef``s can be hard to debug. Make sure tooling does not accidentally trigger proxy resolution by e.g. calling `toString()` or some inline debug rendering invoking property getters. -Please consider to enable _trace_ logging for `org.springframework.data.mongodb.core.convert.DefaultDbRefResolver` to gain insight on `DBRef` resolution. +Please consider to enable _trace_ logging for `org.springframework.data.mongodb.core.convert.DefaultDbRefResolver` to gain insight on `DBRef` resolution. + +Though technically possible, avoid saving back individual lazily loaded entities obtained via properties of the referencing root. CAUTION: Lazy loading may require class proxies, that in turn, might need access to jdk internals, that are not open, starting with Java 16+, due to https://openjdk.java.net/jeps/396[JEP 396: Strongly Encapsulate JDK Internals by Default]. -For those cases please consider falling back to an interface type (eg. switch from `ArrayList` to `List`) or provide the required `--add-opens` argument. +For those cases please consider falling back to an interface type (eg. switch from `ArrayList` to `List`) or provide the required `--add-opens` argument. + + [[mapping-usage.document-references]] == Using Document References @@ -500,6 +502,7 @@ A few more general remarks: * Do you use cyclic references? Ask your self if you need them. * Lazy document references are hard to debug. -Make sure tooling does not accidentally trigger proxy resolution by e.g. calling `toString()`. +Make sure tooling does not accidentally trigger proxy resolution by e.g. calling `toString()`. + +Though technically possible, avoid saving back individual lazily loaded entities obtained via properties of the referencing root. * There is no support for reading document references using reactive infrastructure. ====