Initial stab at changing the way IDs are handled in the mapping converter.

This commit is contained in:
J. Brisbin
2011-05-31 13:21:03 -05:00
parent aeea1bc5d5
commit 43d0f74a3e
10 changed files with 139 additions and 148 deletions

View File

@@ -20,9 +20,9 @@ package org.springframework.data.document.mongodb;
/**
* Helper class featuring helper methods for working with MongoDb collections.
* <p/>
* <p>
* <p/>
* Mainly intended for internal use within the framework.
*
*
* @author Thomas Risberg
* @since 1.0
*/
@@ -37,13 +37,14 @@ public abstract class MongoCollectionUtils {
/**
* Obtains the collection name to use for the provided class
*
* @param entityClass
* The class to determine the preferred collection name for
*
* @param entityClass The class to determine the preferred collection name for
* @return The preferred collection name
*/
public static String getPreferredCollectionName(Class<?> entityClass) {
return entityClass.getSimpleName();
String name = entityClass.getSimpleName();
char firstChar = name.charAt(0);
return (String.valueOf(firstChar).toLowerCase() + name.substring(1));
}
}

View File

@@ -20,8 +20,10 @@ import static org.springframework.data.document.mongodb.query.Criteria.*;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -88,6 +90,11 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
private static final Log LOGGER = LogFactory.getLog(MongoTemplate.class);
private static final String ID = "_id";
private static final List<String> ITERABLE_CLASSES = new ArrayList<String>() {{
add(List.class.getName());
add(Collection.class.getName());
add(Iterator.class.getName());
}};
/*
* WriteConcern to be used for write operations if it has been specified. Otherwise
@@ -373,7 +380,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
collection.drop();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Dropped collection ["+ collection.getFullName() + "]");
LOGGER.debug("Dropped collection [" + collection.getFullName() + "]");
}
return null;
}
@@ -475,6 +482,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.Object)
*/
public void insert(Object objectToSave) {
ensureNotIterable(objectToSave);
insert(determineEntityCollectionName(objectToSave), objectToSave);
}
@@ -482,9 +490,19 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @see org.springframework.data.document.mongodb.MongoOperations#insert(java.lang.String, java.lang.Object)
*/
public void insert(String collectionName, Object objectToSave) {
ensureNotIterable(objectToSave);
doInsert(collectionName, objectToSave, this.mongoConverter);
}
protected void ensureNotIterable(Object o) {
if (null != o) {
if (o.getClass().isArray() ||
ITERABLE_CLASSES.contains(o.getClass().getName())) {
throw new IllegalArgumentException("Cannot use a collection here.");
}
}
}
/**
* Prepare the collection before any processing is done using it. This allows a convenient way to apply
* settings like slaveOk() etc. Can be overridden in sub-classes.
@@ -613,21 +631,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected Object insertDBObject(String collectionName, final DBObject dbDoc) {
// DATADOC-95: This will prevent null objects from being saved.
// if (dbDoc.keySet().isEmpty()) {
// return null;
// }
// TODO: Need to move this to more central place
if (dbDoc.containsField("_id")) {
if (dbDoc.get("_id") instanceof String) {
ObjectId oid = convertIdValue(this.mongoConverter, dbDoc.get("_id"));
if (oid != null) {
dbDoc.put("_id", oid);
}
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collection: " + collectionName);
}
@@ -645,22 +648,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected List<ObjectId> insertDBObjectList(String collectionName, final List<DBObject> dbDocList) {
if (dbDocList.isEmpty()) {
return Collections.emptyList();
}
// TODO: Need to move this to more central place
for (DBObject dbDoc : dbDocList) {
if (dbDoc.containsField("_id")) {
if (dbDoc.get("_id") instanceof String) {
ObjectId oid = convertIdValue(this.mongoConverter, dbDoc.get("_id"));
if (oid != null) {
dbDoc.put("_id", oid);
}
}
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("insert list of DBObjects containing " + dbDocList.size() + " items");
}
@@ -690,20 +681,6 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
protected Object saveDBObject(String collectionName, final DBObject dbDoc) {
if (dbDoc.keySet().isEmpty()) {
return null;
}
// TODO: Need to move this to more central place
if (dbDoc.containsField("_id")) {
if (dbDoc.get("_id") instanceof String) {
ObjectId oid = convertIdValue(this.mongoConverter, dbDoc.get("_id"));
if (oid != null) {
dbDoc.put("_id", oid);
}
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("save DBObject containing fields: " + dbDoc.keySet());
}
@@ -824,9 +801,9 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DBObject dboq = mapper.getMappedObject(queryObject, entity);
WriteResult wr = null;
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName());
}
if (writeConcernToUse == null) {
wr = collection.remove(dboq);
} else {
@@ -889,7 +866,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
DBCollection coll = db.createCollection(collectionName, collectionOptions);
// TODO: Emit a collection created event
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created collection [" + coll.getFullName() + "]");
LOGGER.debug("Created collection [" + coll.getFullName() + "]");
}
return coll;
}
@@ -1014,7 +991,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
if (idProp == null) {
throw new MappingException("No id property found for object of type " + entity.getType().getName());
}
ConversionService service = mongoConverter.getConversionService();
try {

View File

@@ -29,6 +29,11 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bson.types.ObjectId;
@@ -36,6 +41,7 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.data.document.mongodb.MongoDbFactory;
import org.springframework.data.document.mongodb.mapping.MongoPersistentEntity;
@@ -58,12 +64,6 @@ import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBObject;
import com.mongodb.DBRef;
/**
* {@link MongoConverter} that uses a {@link MappingContext} to do sophisticated mapping of domain objects to
* {@link DBObject}.
@@ -74,11 +74,11 @@ import com.mongodb.DBRef;
public class MappingMongoConverter extends AbstractMongoConverter implements ApplicationContextAware {
public static final String CUSTOM_TYPE_KEY = "_class";
private static final List<Class<?>> VALID_ID_TYPES = Arrays.asList(new Class<?>[]{ObjectId.class, String.class,
BigInteger.class, byte[].class});
protected static final Log log = LogFactory.getLog(MappingMongoConverter.class);
protected final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
protected final SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
protected ApplicationContext applicationContext;
@@ -87,7 +87,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
/**
* Creates a new {@link MappingMongoConverter} given the new {@link MongoDbFactory} and {@link MappingContext}.
*
*
* @param mongoDbFactory
* @param mappingContext
*/
@@ -188,41 +188,41 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
final List<String> ctorParamNames = new ArrayList<String>();
final MongoPersistentProperty idProperty = entity.getIdProperty();
ParameterValueProvider provider = new SpELAwareParameterValueProvider(spelExpressionParser, spelCtx) {
@Override
@SuppressWarnings("unchecked")
public <T> T getParameterValue(PreferredConstructor.Parameter<T> parameter) {
if (parameter.getKey() != null) {
return super.getParameterValue(parameter);
}
String name = parameter.getName();
TypeInformation<T> type = parameter.getType();
Class<T> rawType = parameter.getRawType();
String key = idProperty == null ? name : idProperty.getName().equals(name) ? idProperty.getFieldName() : name;
Object obj = dbo.get(key);
@Override
@SuppressWarnings("unchecked")
public <T> T getParameterValue(PreferredConstructor.Parameter<T> parameter) {
ctorParamNames.add(name);
if (obj instanceof DBRef) {
return read(type, ((DBRef) obj).fetch());
} else if (obj instanceof BasicDBList) {
BasicDBList objAsDbList = (BasicDBList) obj;
List<?> l = unwrapList(objAsDbList, type);
return conversionService.convert(l, rawType);
} else if (obj instanceof DBObject) {
return read(type, ((DBObject) obj));
} else if (null != obj && obj.getClass().isAssignableFrom(rawType)) {
return (T) obj;
} else if (null != obj) {
return conversionService.convert(obj, rawType);
}
if (parameter.getKey() != null) {
return super.getParameterValue(parameter);
}
String name = parameter.getName();
TypeInformation<T> type = parameter.getType();
Class<T> rawType = parameter.getRawType();
String key = idProperty == null ? name : idProperty.getName().equals(name) ? idProperty.getFieldName() : name;
Object obj = dbo.get(key);
ctorParamNames.add(name);
if (obj instanceof DBRef) {
return read(type, ((DBRef) obj).fetch());
} else if (obj instanceof BasicDBList) {
BasicDBList objAsDbList = (BasicDBList) obj;
List<?> l = unwrapList(objAsDbList, type);
return conversionService.convert(l, rawType);
} else if (obj instanceof DBObject) {
return read(type, ((DBObject) obj));
} else if (null != obj && obj.getClass().isAssignableFrom(rawType)) {
return (T) obj;
} else if (null != obj) {
return conversionService.convert(obj, rawType);
}
return null;
}
};
return null;
}
};
final BeanWrapper<MongoPersistentEntity<S>, S> wrapper = BeanWrapper.create(entity, provider, conversionService);
// Set properties not already set in the constructor
@@ -325,19 +325,26 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
if (null == entity) {
throw new MappingException("No mapping metadata found for entity of type " + obj.getClass().getName());
}
final BeanWrapper<MongoPersistentEntity<Object>, Object> wrapper = BeanWrapper.create(obj, conversionService);
// Write the ID
final MongoPersistentProperty idProperty = entity.getIdProperty();
if (!dbo.containsField("_id") && null != idProperty) {
Object idObj;
try {
idObj = wrapper.getProperty(idProperty, Object.class, useFieldAccessOnly);
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new MappingException(e.getMessage(), e);
Object idObj = null;
Class<?>[] targetClasses = new Class<?>[]{ObjectId.class, Object.class};
for (int i = 0; i < targetClasses.length; i++) {
try {
idObj = wrapper.getProperty(idProperty, targetClasses[i], useFieldAccessOnly);
if (null != idObj) {
break;
}
} catch (ConversionException ignored) {
} catch (IllegalAccessException e) {
throw new MappingException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new MappingException(e.getMessage(), e);
}
}
if (null != idObj) {
@@ -396,13 +403,13 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
@SuppressWarnings({"unchecked"})
protected void writePropertyInternal(MongoPersistentProperty prop, Object obj, DBObject dbo) {
if (obj == null) {
return;
}
String name = prop.getFieldName();
if (prop.isCollection()) {
DBObject collectionInternal = writeCollectionInternal(prop, obj);
dbo.put(name, collectionInternal);
@@ -437,23 +444,23 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
writeInternal(obj, propDbObj, mappingContext.getPersistentEntity(prop.getTypeInformation()));
dbo.put(name, propDbObj);
}
@SuppressWarnings("unchecked")
protected DBObject writeCollectionInternal(MongoPersistentProperty property, Object obj) {
BasicDBList dbList = new BasicDBList();
Class<?> type = property.getType();
Collection<Object> coll = type.isArray() ? CollectionUtils.arrayToList(obj) : (Collection<Object>) obj;
TypeInformation<?> componentType = property.getTypeInformation().getComponentType();
for (Object element : coll) {
if (element == null) {
continue;
}
TypeInformation<?> valueType = ClassTypeInformation.from(element.getClass());
if (property.isDbReference()) {
DBRef dbRef = createDBRef(element, property.getDBRef());
dbList.add(dbRef);
@@ -484,7 +491,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
dbList.add(propDbObj);
}
}
return dbList;
}
@@ -513,19 +520,19 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
/**
* Adds custom type information to the given {@link DBObject} if necessary. That is if the value is not the same as
* the one given. This is usually the case if you store a subtype of the actual declared type of the property.
*
*
* @param type
* @param value
* @param dbObject
*/
public void addCustomTypeKeyIfNecessary(TypeInformation<?> type, Object value, DBObject dbObject) {
if (type == null) {
return;
}
Class<?> reference = getValueType(type).getType();
boolean notTheSameClass = !value.getClass().equals(reference);
if (notTheSameClass) {
dbObject.put(CUSTOM_TYPE_KEY, value.getClass().getName());
@@ -536,7 +543,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* Returns the type type information of the actual value to be stored. That is, for maps it will return the map value
* type, for collections it will return the component type as well as the given type if it is a non-collection or
* non-map one.
*
*
* @param type
* @return
*/
@@ -560,7 +567,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
private void writeSimpleInternal(String key, Object value, DBObject dbObject) {
Class<?> customTarget = getCustomTarget(value.getClass(), null);
Object valueToSet = null;
if (customTarget != null) {
valueToSet = conversionService.convert(value, customTarget);
@@ -691,7 +698,7 @@ public class MappingMongoConverter extends AbstractMongoConverter implements App
* Returns the type to be used to convert the DBObject given to. Will return {@literal null} if there's not type hint
* found in the {@link DBObject} or the type hint found can't be converted into a {@link Class} as the type might not
* be available.
*
*
* @param dbObject
* @return the type to be used for converting the given {@link DBObject} into or {@literal null} if there's no type
* found.

View File

@@ -23,7 +23,7 @@ import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import org.bson.types.BasicBSONList;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionException;
import org.springframework.data.document.mongodb.convert.MongoConverter;
import org.springframework.data.document.mongodb.mapping.MongoPersistentEntity;
import org.springframework.data.mapping.model.PersistentEntity;
@@ -31,7 +31,7 @@ import org.springframework.util.Assert;
/**
* A helper class to encapsulate any modifications of a Query object before it gets submitted to the database.
*
*
* @author Jon Brisbin <jbrisbin@vmware.com>
* @author Oliver Gierke
*/
@@ -41,7 +41,7 @@ public class QueryMapper {
/**
* Creates a new {@link QueryMapper} with the given {@link MongoConverter}.
*
*
* @param converter
*/
public QueryMapper(MongoConverter converter) {
@@ -52,7 +52,7 @@ public class QueryMapper {
/**
* Replaces the property keys used in the given {@link DBObject} with the appropriate keys by using the
* {@link PersistentEntity} metadata.
*
*
* @param query
* @param entity
* @return
@@ -82,7 +82,7 @@ public class QueryMapper {
try {
ObjectId oid = converter.convertObjectId(id);
ids.add(oid);
} catch (ConversionFailedException ignored) {
} catch (ConversionException ignored) {
ids.add(id);
}
} else {
@@ -96,7 +96,7 @@ public class QueryMapper {
} else if (null != converter) {
try {
value = converter.convertObjectId(value);
} catch (Exception ignored) {
} catch (ConversionException ignored) {
}
}
newKey = "_id";

View File

@@ -465,16 +465,7 @@ public class MongoTemplateTests {
}
@Test
public void testAddingToListWithSimpleConverter() throws Exception {
testAddingToList(this.template);
}
@Test
public void testAddingToListWithMappingConverter() throws Exception {
testAddingToList(this.mappingTemplate);
}
private void testAddingToList(MongoTemplate template) {
public void testAddingToList() {
PersonWithAList p = new PersonWithAList();
p.setFirstName("Sven");
p.setAge(22);

View File

@@ -18,9 +18,11 @@ package org.springframework.data.document.mongodb;
import java.util.ArrayList;
import java.util.List;
import org.bson.types.ObjectId;
public class PersonWithAList {
private String id;
private ObjectId id;
private String firstName;
@@ -30,11 +32,11 @@ public class PersonWithAList {
private List<Friend> friends = new ArrayList<Friend>();
public String getId() {
public ObjectId getId() {
return id;
}
public void setId(String id) {
public void setId(ObjectId id) {
this.id = id;
}

View File

@@ -174,14 +174,14 @@ public class MappingTests {
Person p = new Person(123456789, "John", "Doe", 37, addr);
p.setAccounts(accounts);
template.insert("Person", p);
template.insert("person", p);
Account newAcct = new Account();
newAcct.setBalance(10000.00f);
template.insert("account", newAcct);
accounts.add(newAcct);
template.save("Person", p);
template.save("person", p);
assertNotNull(p.getId());
@@ -203,8 +203,10 @@ public class MappingTests {
Person p1 = new Person(1234567890, "John", "Doe", 37, addr);
Person p2 = new Person(1234567890, "Jane", "Doe", 38, addr);
template.insert(p2);
template.insert(p1);
List<Person> persons = new ArrayList<Person>();
persons.add(p1);
persons.add(p2);
template.insertList(MongoCollectionUtils.getPreferredCollectionName(Person.class), persons);
List<Person> result = template.find(new Query(Criteria.where("ssn").is(1234567890)), Person.class);
assertThat(result.size(), is(1));
@@ -380,4 +382,15 @@ public class MappingTests {
assertEquals("New Text", p2.getText());
}
// @Test
// public void testThroughput() {
// long start = System.currentTimeMillis();
// for (int i = 0; i < 10000; i++) {
// PersonPojo p = new PersonPojo(i, "throughput test", "");
// template.insert(p);
// }
// double elapsed = System.currentTimeMillis() - start;
// System.out.println("time: " + (elapsed / 1000) + "s");
// }
}

View File

@@ -58,7 +58,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test
public void findsPersonById() throws Exception {
assertThat(repository.findOne(dave.getId()), is(dave));
assertThat(repository.findOne(dave.getId().toString()), is(dave));
}
@Test
@@ -82,7 +82,7 @@ public abstract class AbstractPersonRepositoryIntegrationTests {
@Test
public void deletesPersonByIdCorrectly() {
repository.delete(dave.getId());
repository.delete(dave.getId().toString());
List<Person> result = repository.findAll();

View File

@@ -21,20 +21,20 @@ import org.springframework.data.document.mongodb.mapping.Document;
/**
* Sample contactt domain class.
*
*
* @author Oliver Gierke
*/
@Document
abstract class Contact {
@Id
protected final String id;
protected final ObjectId id;
public Contact() {
this.id = new ObjectId().toString();
this.id = new ObjectId();
}
public String getId() {
public ObjectId getId() {
return id;
}
}

View File

@@ -41,6 +41,6 @@ public class ContactRepositoryIntegrationTests {
Person person = new Person("Oliver", "Gierke");
Contact result = repository.save(person);
assertTrue(repository.findOne(result.getId()) instanceof Person);
assertTrue(repository.findOne(result.getId().toString()) instanceof Person);
}
}