DATAMONGO-322 - MongoTemplates refuses to save entities with unset id if not auto-generateable.

If an entity is handed into the template to be saved or inserted we now check that the auto-generated ObjectId can actually be applied to the id property after saving the object.
This commit is contained in:
Oliver Gierke
2011-12-06 18:49:41 +01:00
parent 89de566893
commit 6616761f50
5 changed files with 110 additions and 41 deletions

View File

@@ -66,6 +66,7 @@ import org.springframework.data.mongodb.core.index.MongoPersistentEntityIndexCre
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.mongodb.core.mapping.event.AfterConvertEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterLoadEvent;
import org.springframework.data.mongodb.core.mapping.event.AfterSaveEvent;
@@ -609,6 +610,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected <T> void doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
assertUpdateableIdIfNotSet(objectToSave);
BasicDBObject dbDoc = new BasicDBObject();
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave));
@@ -693,6 +697,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected <T> void doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
assertUpdateableIdIfNotSet(objectToSave);
BasicDBObject dbDoc = new BasicDBObject();
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave));
@@ -882,6 +889,25 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return new Query(where(idProp.getFieldName()).is(idProperty));
}
private void assertUpdateableIdIfNotSet(Object entity) {
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entity.getClass());
MongoPersistentProperty idProperty = persistentEntity.getIdProperty();
if (idProperty == null) {
return;
}
ConversionService service = mongoConverter.getConversionService();
Object idValue = BeanWrapper.create(entity, service).getProperty(idProperty, Object.class, true);
if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
throw new InvalidDataAccessApiUsageException(String.format(
"Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(), entity.getClass()
.getName()));
}
}
public <T> void remove(Query query, Class<T> entityClass) {
Assert.notNull(query);
doRemove(determineCollectionName(entityClass), query, entityClass);

View File

@@ -35,14 +35,14 @@ import com.mongodb.DBRef;
*/
public abstract class MongoSimpleTypes {
public static final Set<Class<?>> SUPPORTED_ID_CLASSES;
public static final Set<Class<?>> AUTOGENERATED_ID_TYPES;
static {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(ObjectId.class);
classes.add(String.class);
classes.add(BigInteger.class);
SUPPORTED_ID_CLASSES = Collections.unmodifiableSet(classes);
AUTOGENERATED_ID_TYPES = Collections.unmodifiableSet(classes);
Set<Class<?>> simpleTypes = new HashSet<Class<?>>();
simpleTypes.add(DBRef.class);

View File

@@ -15,7 +15,7 @@
*/
package org.springframework.data.mongodb.repository.support;
import static org.springframework.data.querydsl.QueryDslUtils.QUERY_DSL_PRESENT;
import static org.springframework.data.querydsl.QueryDslUtils.*;
import java.io.Serializable;
import java.lang.reflect.Method;
@@ -23,7 +23,6 @@ import java.lang.reflect.Method;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.MongoSimpleTypes;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.EntityInformationCreator;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
@@ -38,7 +37,6 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Factory to create {@link MongoRepository} instances.
@@ -60,7 +58,8 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
Assert.notNull(mongoOperations);
this.mongoOperations = mongoOperations;
this.entityInformationCreator = new DefaultEntityInformationCreator(mongoOperations.getConverter().getMappingContext());
this.entityInformationCreator = new DefaultEntityInformationCreator(mongoOperations.getConverter()
.getMappingContext());
}
/*
@@ -111,13 +110,13 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
* @author Oliver Gierke
*/
private class MongoQueryLookupStrategy implements QueryLookupStrategy {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.repository.core.NamedQueries)
*/
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, NamedQueries namedQueries) {
MongoQueryMethod queryMethod = new MongoQueryMethod(method, metadata, entityInformationCreator);
String namedQueryName = queryMethod.getNamedQueryName();
@@ -132,20 +131,6 @@ public class MongoRepositoryFactory extends RepositoryFactorySupport {
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.support.RepositoryFactorySupport#validate(org.springframework.data.repository.support.RepositoryMetadata)
*/
@Override
protected void validate(RepositoryMetadata metadata) {
Class<?> idClass = metadata.getIdClass();
if (!MongoSimpleTypes.SUPPORTED_ID_CLASSES.contains(idClass)) {
throw new IllegalArgumentException(String.format("Unsupported id class! Only %s are supported!",
StringUtils.collectionToCommaDelimitedString(MongoSimpleTypes.SUPPORTED_ID_CLASSES)));
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getEntityInformation(java.lang.Class)

View File

@@ -15,23 +15,37 @@
*/
package org.springframework.data.mongodb.core;
import static org.junit.Assert.assertTrue;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import com.mongodb.DB;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import java.math.BigInteger;
import org.bson.types.ObjectId;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoWriter;
import org.springframework.test.util.ReflectionTestUtils;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.WriteResult;
/**
* Unit tests for {@link MongoTemplate}.
*
@@ -48,9 +62,15 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
@Mock
DB db;
@Mock
DBCollection collection;
@Before
public void setUp() {
this.template = new MongoTemplate(mongo, "database");
when(mongo.getDB("database")).thenReturn(db);
when(db.getCollection(Mockito.any(String.class))).thenReturn(collection);
}
@Test(expected = IllegalArgumentException.class)
@@ -82,7 +102,57 @@ public class MongoTemplateUnitTests extends MongoOperationsUnitTests {
template.setApplicationContext(new GenericApplicationContext());
template.mapReduce("foo", "classpath:doesNotExist.js", "function() {}", Person.class);
}
/**
* @see DATAMONGO-322
*/
@Test(expected = InvalidDataAccessApiUsageException.class)
public void rejectsEntityWithNullIdIfNotSupportedIdType() {
Object entity = new NotAutogenerateableId();
template.save(entity);
}
/**
* @see DATAMONGO-322
*/
@Test
public void storesEntityWithSetIdAlthoughNotAutogenerateable() {
NotAutogenerateableId entity = new NotAutogenerateableId();
entity.id = 1;
template.save(entity);
}
/**
* @see DATAMONGO-322
*/
@Test
public void autogeneratesIdForEntityWithAutogeneratableId() {
MongoTemplate template = spy(this.template);
doReturn(new ObjectId()).when(template).saveDBObject(Mockito.any(String.class), Mockito.any(DBObject.class),
Mockito.any(Class.class));
AutogenerateableId entity = new AutogenerateableId();
template.save(entity);
assertThat(entity.id, is(notNullValue()));
}
class AutogenerateableId {
@Id
BigInteger id;
}
class NotAutogenerateableId {
@Id
Integer id;
}
/**
* Mocks out the {@link MongoTemplate#getDb()} method to return the {@link DB} mock instead of executing the actual
* behaviour.

View File

@@ -30,10 +30,8 @@ import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Person;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation;
/**
* Unit test for {@link MongoRepositoryFactory}.
@@ -45,7 +43,7 @@ public class MongoRepositoryFactoryUnitTests {
@Mock
MongoTemplate template;
@Mock
MongoConverter converter;
@@ -55,7 +53,7 @@ public class MongoRepositoryFactoryUnitTests {
@Mock
@SuppressWarnings("rawtypes")
MongoPersistentEntity entity;
@Before
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setUp() {
@@ -63,12 +61,6 @@ public class MongoRepositoryFactoryUnitTests {
when(converter.getMappingContext()).thenReturn((MappingContext) mappingContext);
}
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidIdType() throws Exception {
MongoRepositoryFactory factory = new MongoRepositoryFactory(template);
factory.getRepository(SampleRepository.class);
}
@Test
@SuppressWarnings("unchecked")
public void usesMappingMongoEntityInformationIfMappingContextSet() {
@@ -80,8 +72,4 @@ public class MongoRepositoryFactoryUnitTests {
MongoEntityInformation<Person, Serializable> entityInformation = factory.getEntityInformation(Person.class);
assertTrue(entityInformation instanceof MappingMongoEntityInformation);
}
private interface SampleRepository extends MongoRepository<Person, Long> {
}
}