Skip to content

Unwrap LazyLoadingProxy before checking isNew. #5033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.0.x-GH-5031-SNAPSHOT</version>
<packaging>pom</packaging>

<name>Spring Data MongoDB</name>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb-distribution/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.0.x-GH-5031-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion spring-data-mongodb/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb-parent</artifactId>
<version>5.0.0-SNAPSHOT</version>
<version>5.0.x-GH-5031-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@

import org.bson.Document;
import org.jspecify.annotations.Nullable;

import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.model.ValueExpressionEvaluator;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.Wrapped;

import com.mongodb.DBRef;

Expand All @@ -32,23 +34,9 @@
* @author Christoph Strobl
* @author Mark Paluch
*/
class DefaultDbRefProxyHandler implements DbRefProxyHandler {

private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
private final ValueResolver resolver;
private final Function<Object, ValueExpressionEvaluator> evaluatorFactory;

/**
* @param mappingContext must not be {@literal null}.
* @param resolver must not be {@literal null}.
*/
public DefaultDbRefProxyHandler(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext,
ValueResolver resolver, Function<Object, ValueExpressionEvaluator> evaluatorFactory) {

this.mappingContext = mappingContext;
this.resolver = resolver;
this.evaluatorFactory = evaluatorFactory;
}
record DefaultDbRefProxyHandler(
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext, ValueResolver resolver,
Function<Object, ValueExpressionEvaluator> evaluatorFactory) implements DbRefProxyHandler {

@Override
public Object populateId(MongoPersistentProperty property, @Nullable DBRef source, Object proxy) {
Expand All @@ -65,11 +53,12 @@ public Object populateId(MongoPersistentProperty property, @Nullable DBRef sourc
}

ValueExpressionEvaluator evaluator = evaluatorFactory.apply(proxy);
PersistentPropertyAccessor accessor = entity.getPropertyAccessor(proxy);
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor((Wrapped) () -> proxy);

Document object = new Document(idProperty.getFieldName(), source.getId());
ObjectPath objectPath = ObjectPath.ROOT.push(proxy, entity, null);
accessor.setProperty(idProperty, resolver.getValueInternal(idProperty, object, evaluator, objectPath));
accessor.setProperty(idProperty,
resolver.getValueInternal(idProperty, object, evaluator, objectPath));

return proxy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

import org.jspecify.annotations.Nullable;

import org.springframework.data.mongodb.core.mapping.Wrapped;

import com.mongodb.DBRef;

/**
Expand All @@ -28,14 +30,15 @@
* @since 1.5
* @see LazyLoadingProxyFactory
*/
public interface LazyLoadingProxy {
public interface LazyLoadingProxy extends Wrapped {

/**
* Initializes the proxy and returns the wrapped value.
*
* @return
* @since 1.5
*/
@Override
Object getTarget();

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,15 @@
import java.util.Map;

import org.jspecify.annotations.Nullable;

import org.springframework.data.annotation.Id;
import org.springframework.data.expression.ValueEvaluationContext;
import org.springframework.data.expression.ValueExpression;
import org.springframework.data.expression.ValueExpressionParser;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.AssociationHandler;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PropertyHandler;
import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.mongodb.MongoCollectionUtils;
Expand Down Expand Up @@ -188,6 +190,16 @@ public void verify() {
verifyFieldTypes();
}

@Override
public boolean isNew(Object bean) {
return super.isNew(Wrapped.getTargetObject(bean));
}

@Override
public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
return super.getPropertyAccessor(Wrapped.getTargetObject(bean));
}

@Override
public EvaluationContext getEvaluationContext(@Nullable Object rootObject) {
return super.getEvaluationContext(rootObject);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ default boolean hasCollation() {
/**
* Get the entities shard key if defined.
*
* @return {@link ShardKey#none()} if not not set.
* @return {@link ShardKey#none()} if not set.
* @since 3.0
*/
ShardKey getShardKey();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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.core.mapping;

/**
* Marker interface for objects that wrap another object, providing a way to retrieve the wrapped value.
*
* @author Mark Paluch
* @since 5.0
*/
public interface Wrapped {

/**
* Returns the wrapped value.
*
* @return
*/
Object getTarget();

/**
* Unwraps the given object if it is an instance of {@link Wrapped}. If not, returns the object as-is.
*
* @param object the object to unwrap, must not be {@literal null}.
* @return the unwrapped object or the original object if it is not wrapped.
*/
static <T> T getTargetObject(T object) {
return object instanceof Wrapped w ? (T) w.getTarget() : object;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.bson.types.ObjectId;
import org.jspecify.annotations.Nullable;

import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.query.Collation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,41 @@

import java.util.Date;

import org.bson.types.ObjectId;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.mapping.callback.EntityCallbacks;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.DocumentReference;
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertCallback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;

/**
* Integration test for the auditing support.
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class AuditingIntegrationTests {
@ContextConfiguration(locations = "auditing.xml")
@ExtendWith(SpringExtension.class)
class AuditingIntegrationTests {

@Test // DATAMONGO-577, DATAMONGO-800, DATAMONGO-883, DATAMONGO-2261
public void enablesAuditingAndSetsPropertiesAccordingly() throws Exception {
@Autowired MongoMappingContext mappingContext;
@Autowired ApplicationContext context;
@Autowired MongoTemplate template;

AbstractApplicationContext context = new ClassPathXmlApplicationContext("auditing.xml", getClass());
@Test // DATAMONGO-577, DATAMONGO-800, DATAMONGO-883, DATAMONGO-2261
void enablesAuditingAndSetsPropertiesAccordingly() throws Exception {

MongoMappingContext mappingContext = context.getBean(MongoMappingContext.class);
mappingContext.getPersistentEntity(Entity.class);

EntityCallbacks callbacks = EntityCallbacks.create(context);
Expand All @@ -55,24 +65,100 @@ public void enablesAuditingAndSetsPropertiesAccordingly() throws Exception {
assertThat(entity.modified).isEqualTo(entity.created);

Thread.sleep(10);
entity.id = 1L;
entity.id = "foo";

entity = callbacks.callback(BeforeConvertCallback.class, entity, "collection-1");

assertThat(entity.created).isNotNull();
assertThat(entity.modified).isNotEqualTo(entity.created);
context.close();
}

class Entity {
@Test // GH-5031
void handlesDocumentReferenceAuditingCorrectly() throws InterruptedException {

template.remove(TopDocumentReferenceLevelEntity.class).all();

Entity entity = new Entity();
template.insert(entity);

TopDocumentReferenceLevelEntity tle = new TopDocumentReferenceLevelEntity();
tle.entity = entity;
template.insert(tle);

Thread.sleep(200);

TopDocumentReferenceLevelEntity loadAndModify = template.findById(tle.id, TopDocumentReferenceLevelEntity.class);
Date created = loadAndModify.entity.getCreated();
Date modified = loadAndModify.entity.getModified();
template.save(loadAndModify.entity);

TopDocumentReferenceLevelEntity loaded = template.findById(tle.id, TopDocumentReferenceLevelEntity.class);

assertThat(loaded.entity.getCreated()).isEqualTo(created);
assertThat(loaded.entity.getModified()).isNotEqualTo(modified);
}

@Test // GH-5031
void handlesDbRefAuditingCorrectly() throws InterruptedException {

template.remove(TopDbRefLevelEntity.class).all();

Entity entity = new Entity();
template.insert(entity);

TopDbRefLevelEntity tle = new TopDbRefLevelEntity();
tle.entity = entity;
template.insert(tle);

Thread.sleep(200);

TopDbRefLevelEntity loadAndModify = template.findById(tle.id, TopDbRefLevelEntity.class);
Date created = loadAndModify.entity.getCreated();
Date modified = loadAndModify.entity.getModified();
template.save(loadAndModify.entity);

TopDbRefLevelEntity loaded = template.findById(tle.id, TopDbRefLevelEntity.class);

@Id Long id;
assertThat(loaded.entity.getCreated()).isEqualTo(created);
assertThat(loaded.entity.getModified()).isNotEqualTo(modified);
}

static class Entity {

@Id String id;
@CreatedDate Date created;
Date modified;

@LastModifiedDate
public Date getModified() {
return modified;
}

public Date getCreated() {
return created;
}

public void setCreated(Date created) {
this.created = created;
}

public void setModified(Date modified) {
this.modified = modified;
}
}

static class TopDocumentReferenceLevelEntity {

@Id ObjectId id;
@DocumentReference(lazy = true) Entity entity;

}

static class TopDbRefLevelEntity {

@Id ObjectId id;
@DBRef(lazy = true) Entity entity;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import org.springframework.beans.DirectFieldAccessor;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.AccessType.Type;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceCreator;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoExceptionTranslator;
Expand Down Expand Up @@ -487,11 +487,11 @@ void shouldEagerlyResolveIdPropertyWithFieldAccess() {

ClassWithLazyDbRefs result = converter.read(ClassWithLazyDbRefs.class, object);

PersistentPropertyAccessor accessor = propertyEntity.getPropertyAccessor(result.dbRefToConcreteType);
DirectFieldAccessor accessor = new DirectFieldAccessor(result.dbRefToConcreteType);
MongoPersistentProperty idProperty = mappingContext.getRequiredPersistentEntity(LazyDbRefTarget.class)
.getIdProperty();

assertThat(accessor.getProperty(idProperty)).isNotNull();
assertThat(accessor.getPropertyValue(idProperty.getName())).isNotNull();
assertProxyIsResolved(result.dbRefToConcreteType, false);
}

Expand Down
Loading