Browse Source

Unwrap `LazyLoadingProxy` before checking `isNew`.

Closes #5031
Original pull request: #5033
pull/5039/head
Christoph Strobl 5 months ago committed by Mark Paluch
parent
commit
bcb40b1113
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 8
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/support/MappingMongoEntityInformation.java
  2. 9
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java
  3. 50
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java
  4. 23
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/UserRepository.java
  5. 5
      spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml
  6. 9
      src/main/antora/modules/ROOT/pages/mongodb/mapping/document-references.adoc

8
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.bson.types.ObjectId;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.data.mapping.PersistentPropertyAccessor; 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.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.core.query.Collation;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation; import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
@ -107,6 +108,13 @@ public class MappingMongoEntityInformation<T, ID> extends PersistentEntityInform
return fallbackIdType; return fallbackIdType;
} }
@Override
public boolean isNew(T entity) {
T unwrapped = entity instanceof LazyLoadingProxy proxy ? (T) proxy.getTarget() : entity;
return super.isNew(unwrapped);
}
@Override @Override
public boolean isVersioned() { public boolean isVersioned() {
return this.entityMetadata.hasVersionProperty(); return this.entityMetadata.hasVersionProperty();

9
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/Person.java

@ -77,6 +77,7 @@ public class Person extends Contact {
User unwrappedUser; User unwrappedUser;
@DocumentReference User spiritAnimal; @DocumentReference User spiritAnimal;
@DocumentReference(lazy = true) User lazySpiritAnimal;
int visits; int visits;
@ -325,6 +326,14 @@ public class Person extends Contact {
this.spiritAnimal = spiritAnimal; this.spiritAnimal = spiritAnimal;
} }
public User getLazySpiritAnimal() {
return lazySpiritAnimal;
}
public void setLazySpiritAnimal(User lazySpiritAnimal) {
this.lazySpiritAnimal = lazySpiritAnimal;
}
@Override @Override
public int hashCode() { public int hashCode() {

50
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/PersonRepositoryLazyLoadingIntegrationTests.java

@ -15,8 +15,9 @@
*/ */
package org.springframework.data.mongodb.repository; package org.springframework.data.mongodb.repository;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.mongodb.core.convert.LazyLoadingTestUtils.*; 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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -37,12 +38,14 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
* *
* @author Thomas Darimont * @author Thomas Darimont
* @author Oliver Gierke * @author Oliver Gierke
* @author Christoph Strobl
*/ */
@ContextConfiguration(locations = "PersonRepositoryIntegrationTests-context.xml") @ContextConfiguration(locations = "PersonRepositoryIntegrationTests-context.xml")
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
public class PersonRepositoryLazyLoadingIntegrationTests { public class PersonRepositoryLazyLoadingIntegrationTests {
@Autowired PersonRepository repository; @Autowired PersonRepository repository;
@Autowired UserRepository userRepository;
@Autowired MongoOperations operations; @Autowired MongoOperations operations;
@BeforeEach @BeforeEach
@ -123,4 +126,47 @@ public class PersonRepositoryLazyLoadingIntegrationTests {
assertProxyIsResolved(coworker, true); assertProxyIsResolved(coworker, true);
assertThat(coworker.getUsername()).isEqualTo(thomas.getUsername()); 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));
}
} }

23
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<User, String> {}

5
spring-data-mongodb/src/test/resources/org/springframework/data/mongodb/repository/PersonRepositoryIntegrationTests-context.xml

@ -19,4 +19,9 @@
</property> </property>
</bean> </bean>
<bean class="org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean">
<constructor-arg value="org.springframework.data.mongodb.repository.UserRepository"/>
<property name="mongoOperations" ref="mongoTemplate"/>
</bean>
</beans> </beans>

9
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. 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. 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]. 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]] [[mapping-usage.document-references]]
== Using Document References == Using Document References
@ -500,6 +502,7 @@ A few more general remarks:
* Do you use cyclic references? * Do you use cyclic references?
Ask your self if you need them. Ask your self if you need them.
* Lazy document references are hard to debug. * 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. * There is no support for reading document references using reactive infrastructure.
==== ====

Loading…
Cancel
Save