DATAMONGO-213 - Add WriteConcern to arguments of MongoOperations.update*() methods

DATAMONGO-320 - Remove use of slaveOk boolean option in MongoTemplate as it is deprecated. Replace with ReadPreference
This commit is contained in:
Mark Pollack
2011-11-16 13:28:13 -05:00
parent 64921ddad1
commit cfefe46cd4
5 changed files with 271 additions and 29 deletions

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2011 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
*
* http://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;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;
/**
* Represents an action taken against the collection. Used by {@link WriteConcernResolver} to determine a custom
* WriteConcern based on this information.
*
* Properties that will always be not-null are collectionName and defaultWriteConcern.
* The EntityClass is null only for the MongoActionOperaton.INSERT_LIST.
*
* INSERT, SAVE have null query,
* REMOVE has null document
* INSERT_LIST has null entityClass, document, and query.
*
* @author Mark Pollack
*
*/
public class MongoAction {
private String collectionName;
private WriteConcern defaultWriteConcern;
private Class<?> entityClass;
private MongoActionOperation mongoActionOperation;
private DBObject query;
private DBObject document;
/**
* Create an instance of a MongoAction
* @param defaultWriteConcern the default write concern
* @param mongoActionOperation action being taken against the collection
* @param collectionName the collection name
* @param entityClass the POJO that is being operated against
* @param document the converted DBObject from the POJO or Spring Update object
* @param query the converted DBOjbect from the Spring Query object
*/
public MongoAction(WriteConcern defaultWriteConcern, MongoActionOperation mongoActionOperation,
String collectionName, Class<?> entityClass, DBObject document, DBObject query) {
super();
this.defaultWriteConcern = defaultWriteConcern;
this.mongoActionOperation = mongoActionOperation;
this.collectionName = collectionName;
this.entityClass = entityClass;
this.query = query;
this.document = document;
}
public String getCollectionName() {
return collectionName;
}
public WriteConcern getDefaultWriteConcern() {
return defaultWriteConcern;
}
public Class<?> getEntityClass() {
return entityClass;
}
public MongoActionOperation getMongoActionOperation() {
return mongoActionOperation;
}
public DBObject getQuery() {
return query;
}
public DBObject getDocument() {
return document;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2010-2011 the original author or authors.
*
* Licensed under t
import org.springframework.data.convert.EntityReader;
he 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
*
* http://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;
/**
* Enumeration for operations on a collection. Used with {@link MongoAction} to help determine the
* WriteConcern to use for a given mutating operation
*
* @author Mark Pollack
* @see MongoAction
*
*/
public enum MongoActionOperation {
REMOVE,
UPDATE,
INSERT,
INSERT_LIST,
SAVE
}

View File

@@ -41,6 +41,7 @@ import com.mongodb.MapReduceCommand;
import com.mongodb.MapReduceOutput;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;
import com.mongodb.util.JSON;
@@ -124,6 +125,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* the DB or Collection.
*/
private WriteConcern writeConcern = null;
private WriteConcernResolver writeConcernResolver = new DefaultWriteConcernResolver();
/*
* WriteResultChecking to be used for write operations if it has been
@@ -131,10 +134,10 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
*/
private WriteResultChecking writeResultChecking = WriteResultChecking.NONE;
/*
* Flag used to indicate use of slaveOk() for any operations on collections.
/**
* Set the ReadPreference when operating on a collection. See {@link #prepareCollection(DBCollection)}
*/
private boolean slaveOk = false;
private ReadPreference readPreference = null;
private final MongoConverter mongoConverter;
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
@@ -223,12 +226,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
/**
* TODO: document properly
* Configures the {@link WriteConcernResolver} to be used with the template.
*
* @param slaveOk
* @param writeConcernResolver
*/
public void setSlaveOk(boolean slaveOk) {
this.slaveOk = slaveOk;
public void setWriteConcernResolver(WriteConcernResolver writeConcernResolver) {
this.writeConcernResolver = writeConcernResolver;
}
/**
* Used by @{link {@link #prepareCollection(DBCollection)} to set the {@link ReadPreference} before any operations are performed.
* @param readPreference
*/
public void setReadPreference(ReadPreference readPreference) {
this.readPreference = readPreference;
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
@@ -578,8 +589,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @param collection
*/
protected void prepareCollection(DBCollection collection) {
if (this.slaveOk) {
collection.slaveOk();
if (this.readPreference != null) {
collection.setReadPreference(readPreference);
}
}
@@ -590,8 +601,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
* @param writeConcern any WriteConcern already configured or null
* @return The prepared WriteConcern or null
*/
protected WriteConcern prepareWriteConcern(WriteConcern writeConcern) {
return writeConcern;
protected WriteConcern prepareWriteConcern(MongoAction mongoAction) {
return writeConcernResolver.resolve(mongoAction);
}
protected <T> void doInsert(String collectionName, T objectToSave, MongoWriter<T> writer) {
@@ -601,7 +612,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
writer.write(objectToSave, dbDoc);
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc));
Object id = insertDBObject(collectionName, dbDoc);
Object id = insertDBObject(collectionName, dbDoc, objectToSave.getClass());
populateIdIfNecessary(objectToSave, id);
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbDoc));
@@ -685,19 +696,20 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
writer.write(objectToSave, dbDoc);
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbDoc));
Object id = saveDBObject(collectionName, dbDoc);
Object id = saveDBObject(collectionName, dbDoc, objectToSave.getClass());
populateIdIfNecessary(objectToSave, id);
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbDoc));
}
protected Object insertDBObject(String collectionName, final DBObject dbDoc) {
protected Object insertDBObject(final String collectionName, final DBObject dbDoc, final Class<?> entityClass) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("insert DBObject containing fields: " + dbDoc.keySet() + " in collection: " + collectionName);
}
return execute(collectionName, new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT, collectionName, entityClass, dbDoc, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (writeConcernToUse == null) {
collection.insert(dbDoc);
} else {
@@ -708,7 +720,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
});
}
protected List<ObjectId> insertDBObjectList(String collectionName, final List<DBObject> dbDocList) {
protected List<ObjectId> insertDBObjectList(final String collectionName, final List<DBObject> dbDocList) {
if (dbDocList.isEmpty()) {
return Collections.emptyList();
}
@@ -718,7 +730,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
execute(collectionName, new CollectionCallback<Void>() {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.INSERT_LIST, collectionName, null, null, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (writeConcernToUse == null) {
collection.insert(dbDocList);
} else {
@@ -741,13 +754,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return ids;
}
protected Object saveDBObject(String collectionName, final DBObject dbDoc) {
protected Object saveDBObject(final String collectionName, final DBObject dbDoc, final Class<?> entityClass) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("save DBObject containing fields: " + dbDoc.keySet());
}
return execute(collectionName, new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.SAVE, collectionName, entityClass, dbDoc, null);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (writeConcernToUse == null) {
collection.save(dbDoc);
} else {
@@ -796,7 +810,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
}
WriteResult wr;
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName, entityClass, updateObj, queryObj);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (writeConcernToUse == null) {
if (multi) {
wr = collection.updateMulti(queryObj, updateObj);
@@ -869,7 +884,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
doRemove(determineCollectionName(entityClass), query, entityClass);
}
protected <T> void doRemove(String collectionName, final Query query, Class<T> entityClass) {
protected <T> void doRemove(final String collectionName, final Query query, final Class<T> entityClass) {
if (query == null) {
throw new InvalidDataAccessApiUsageException("Query passed in to remove can't be null");
}
@@ -879,7 +894,8 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
public Void doInCollection(DBCollection collection) throws MongoException, DataAccessException {
DBObject dboq = mapper.getMappedObject(queryObject, entity);
WriteResult wr = null;
WriteConcern writeConcernToUse = prepareWriteConcern(writeConcern);
MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.REMOVE, collectionName, entityClass, null, queryObject);
WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("remove using query: " + queryObject + " in collection: " + collection.getName());
}
@@ -1412,8 +1428,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
/**
* Checks and handles any errors.
* <p/>
* TODO: current implementation logs errors - will be configurable to log warning, errors or throw exception in later
* versions
* Current implementation logs errors. Future version may make this configurable to log warning, errors or throw exception.
*/
private void handleAnyWriteResultErrors(WriteResult wr, DBObject query, String operation) {
if (WriteResultChecking.NONE == this.writeResultChecking) {
@@ -1587,6 +1602,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
return source;
}
}
private class DefaultWriteConcernResolver implements WriteConcernResolver {
public WriteConcern resolve(MongoAction action) {
return action.getDefaultWriteConcern();
}
}
/**
* {@link DbObjectCallback} that assumes a {@link GeoResult} to be created, delegates actual content unmarshalling to

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2011 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
*
* http://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;
import com.mongodb.WriteConcern;
/**
* A strategy interface to determine the WriteConcern to use for a given MongoDbAction.
*
* Return the passed in default WriteConcern (a property on MongoAction) if no determination can be made.
*
* @author Mark Pollack
*
*/
public interface WriteConcernResolver {
/**
* Resolve the WriteConcern given the MongoAction
* @param action describes the context of the Mongo action. Contains a default WriteConcern to use if one should not be resolved.
* @return a WriteConcern based on the passed in MongoAction value, maybe null
*/
WriteConcern resolve(MongoAction action);
}

View File

@@ -61,6 +61,8 @@ import com.mongodb.DBObject;
import com.mongodb.DBRef;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.ReadPreference;
import com.mongodb.WriteConcern;
import com.mongodb.WriteResult;
/**
@@ -774,8 +776,8 @@ public class MongoTemplateTests {
@Test
public void testUsingSlaveOk() throws Exception {
this.template.execute("slaveOkTest", new CollectionCallback<Object>() {
public void testUsingReadPreference() throws Exception {
this.template.execute("readPref", new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
assertThat(collection.getOptions(), is(0));
assertThat(collection.getDB().getOptions(), is(0));
@@ -783,10 +785,10 @@ public class MongoTemplateTests {
}
});
MongoTemplate slaveTemplate = new MongoTemplate(factory);
slaveTemplate.setSlaveOk(true);
slaveTemplate.execute("slaveOkTest", new CollectionCallback<Object>() {
slaveTemplate.setReadPreference(ReadPreference.SECONDARY);
slaveTemplate.execute("readPref", new CollectionCallback<Object>() {
public Object doInCollection(DBCollection collection) throws MongoException, DataAccessException {
assertThat(collection.getOptions(), is(4));
assertThat(collection.getReadPreference(), is(ReadPreference.SECONDARY));
assertThat(collection.getDB().getOptions(), is(0));
return null;
}
@@ -821,6 +823,55 @@ public class MongoTemplateTests {
assertThat(result.getId(), is(person.getId()));
assertThat(result.getFirstName(), is("Carter"));
}
@Test
public void testWriteConcernResolver() {
PersonWithIdPropertyOfTypeObjectId person = new PersonWithIdPropertyOfTypeObjectId();
person.setId(new ObjectId());
person.setFirstName("Dave");
template.setWriteConcern(WriteConcern.NONE);
template.save(person);
WriteResult result = template.updateFirst(query(where("id").is(person.getId())), update("firstName", "Carter"),
PersonWithIdPropertyOfTypeObjectId.class);
WriteConcern lastWriteConcern = result.getLastConcern();
assertThat(lastWriteConcern, equalTo(WriteConcern.NONE));
FsyncSafeWriteConcernResolver resolver = new FsyncSafeWriteConcernResolver();
template.setWriteConcernResolver(resolver);
Query q = query(where("_id").is(person.getId()));
Update u = update("firstName", "Carter");
result = template.updateFirst(q, u, PersonWithIdPropertyOfTypeObjectId.class);
lastWriteConcern = result.getLastConcern();
assertThat(lastWriteConcern, equalTo(WriteConcern.FSYNC_SAFE));
MongoAction lastMongoAction = resolver.getMongoAction();
assertThat(lastMongoAction.getCollectionName(), is("personWithIdPropertyOfTypeObjectId"));
assertThat(lastMongoAction.getDefaultWriteConcern(), equalTo(WriteConcern.NONE));
assertThat(lastMongoAction.getDocument(), notNullValue());
assertThat(lastMongoAction.getEntityClass().toString(), is(PersonWithIdPropertyOfTypeObjectId.class.toString()));
assertThat(lastMongoAction.getMongoActionOperation(), is(MongoActionOperation.UPDATE));
assertThat(lastMongoAction.getQuery(), equalTo(q.getQueryObject()));
assertThat(lastMongoAction.getDocument(), equalTo(u.getUpdateObject()));
}
private class FsyncSafeWriteConcernResolver implements WriteConcernResolver {
private MongoAction mongoAction;
public WriteConcern resolve(MongoAction action) {
this.mongoAction = action;
return WriteConcern.FSYNC_SAFE;
}
public MongoAction getMongoAction() {
return mongoAction;
}
}
/**
* @see DATADOC-246