DATAMONGO-279 - Added support for optimistic locking.
Introduced @Version annotation to demarcate a version property on an entity. MongoTemplate will initialize this property on the first save of an instance if not set already. If it is already set, the template will bump the version number on subsequent saves and actually trigger an update backed by a query including the old version number. A failure to update the entity accordingly will then trigger an OptimisticLockingFailureException. General JavaDoc polish in mapping package.
This commit is contained in:
@@ -38,6 +38,7 @@ import org.springframework.context.ApplicationContext;
|
|||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
import org.springframework.context.ApplicationEventPublisherAware;
|
import org.springframework.context.ApplicationEventPublisherAware;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.core.convert.ConversionService;
|
import org.springframework.core.convert.ConversionService;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
@@ -45,6 +46,7 @@ import org.springframework.core.io.ResourceLoader;
|
|||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.data.authentication.UserCredentials;
|
import org.springframework.data.authentication.UserCredentials;
|
||||||
import org.springframework.data.convert.EntityReader;
|
import org.springframework.data.convert.EntityReader;
|
||||||
import org.springframework.data.mapping.PersistentEntity;
|
import org.springframework.data.mapping.PersistentEntity;
|
||||||
@@ -108,6 +110,7 @@ import com.mongodb.util.JSON;
|
|||||||
* @author Mark Pollack
|
* @author Mark Pollack
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Amol Nayak
|
* @author Amol Nayak
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
||||||
|
|
||||||
@@ -486,7 +489,7 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
} else {
|
} else {
|
||||||
query.limit(1);
|
query.limit(1);
|
||||||
List<T> results = find(query, entityClass, collectionName);
|
List<T> results = find(query, entityClass, collectionName);
|
||||||
return (results.isEmpty() ? null : results.get(0));
|
return results.isEmpty() ? null : results.get(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,7 +737,53 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void save(Object objectToSave, String collectionName) {
|
public void save(Object objectToSave, String collectionName) {
|
||||||
doSave(collectionName, objectToSave, this.mongoConverter);
|
|
||||||
|
MongoPersistentEntity<?> mongoPersistentEntity = getPersistentEntity(objectToSave.getClass());
|
||||||
|
|
||||||
|
// No optimistic locking -> simple save
|
||||||
|
if (!mongoPersistentEntity.hasVersionProperty()) {
|
||||||
|
doSave(collectionName, objectToSave, this.mongoConverter);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
doSaveVersioned(objectToSave, mongoPersistentEntity, collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> void doSaveVersioned(T objectToSave, MongoPersistentEntity<?> entity, String collectionName) {
|
||||||
|
|
||||||
|
BeanWrapper<PersistentEntity<T, ?>, T> beanWrapper = BeanWrapper.create(objectToSave,
|
||||||
|
this.mongoConverter.getConversionService());
|
||||||
|
MongoPersistentProperty idProperty = entity.getIdProperty();
|
||||||
|
MongoPersistentProperty versionProperty = entity.getVersionProperty();
|
||||||
|
Object id = beanWrapper.getProperty(idProperty);
|
||||||
|
|
||||||
|
// Fresh instance -> initialize version property
|
||||||
|
if (id == null) {
|
||||||
|
beanWrapper.setProperty(versionProperty, 0);
|
||||||
|
doSave(collectionName, objectToSave, this.mongoConverter);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
assertUpdateableIdIfNotSet(objectToSave);
|
||||||
|
|
||||||
|
// Create query for entity with the id and old version
|
||||||
|
Object version = beanWrapper.getProperty(versionProperty);
|
||||||
|
Query query = new Query(Criteria.where(idProperty.getName()).is(id).and(versionProperty.getName()).is(version));
|
||||||
|
|
||||||
|
// Bump version number
|
||||||
|
Number number = beanWrapper.getProperty(versionProperty, Number.class, false);
|
||||||
|
beanWrapper.setProperty(versionProperty, number.longValue() + 1);
|
||||||
|
|
||||||
|
BasicDBObject dbObject = new BasicDBObject();
|
||||||
|
|
||||||
|
maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave));
|
||||||
|
this.mongoConverter.write(objectToSave, dbObject);
|
||||||
|
|
||||||
|
maybeEmitEvent(new BeforeSaveEvent<T>(objectToSave, dbObject));
|
||||||
|
Update update = Update.fromDBObject(dbObject, ID);
|
||||||
|
|
||||||
|
updateFirst(query, update, objectToSave.getClass());
|
||||||
|
maybeEmitEvent(new AfterSaveEvent<T>(objectToSave, dbObject));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected <T> void doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
|
protected <T> void doSave(String collectionName, T objectToSave, MongoWriter<T> writer) {
|
||||||
@@ -883,6 +932,14 @@ public class MongoTemplate implements MongoOperations, ApplicationContextAware {
|
|||||||
} else {
|
} else {
|
||||||
wr = collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse);
|
wr = collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity != null && entity.hasVersionProperty() && !multi) {
|
||||||
|
if (wr.getN() == 0) {
|
||||||
|
throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: "
|
||||||
|
+ updateObj.toMap().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleAnyWriteResultErrors(wr, queryObj, "update with '" + updateObj + "'");
|
handleAnyWriteResultErrors(wr, queryObj, "update with '" + updateObj + "'");
|
||||||
return wr;
|
return wr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2011-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.data.mongodb.core.mapping;
|
package org.springframework.data.mongodb.core.mapping;
|
||||||
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -23,8 +22,8 @@ import org.springframework.context.ApplicationContext;
|
|||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.context.expression.BeanFactoryAccessor;
|
import org.springframework.context.expression.BeanFactoryAccessor;
|
||||||
import org.springframework.context.expression.BeanFactoryResolver;
|
import org.springframework.context.expression.BeanFactoryResolver;
|
||||||
import org.springframework.data.mapping.PersistentEntity;
|
|
||||||
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
import org.springframework.data.mapping.model.BasicPersistentEntity;
|
||||||
|
import org.springframework.data.mapping.model.MappingException;
|
||||||
import org.springframework.data.mongodb.MongoCollectionUtils;
|
import org.springframework.data.mongodb.MongoCollectionUtils;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
import org.springframework.expression.Expression;
|
import org.springframework.expression.Expression;
|
||||||
@@ -34,11 +33,12 @@ import org.springframework.expression.spel.support.StandardEvaluationContext;
|
|||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mongo specific {@link PersistentEntity} implementation that adds Mongo specific meta-data such as the collection name
|
* MongoDB specific {@link MongoPersistentEntity} implementation that adds Mongo specific meta-data such as the
|
||||||
* and the like.
|
* collection name and the like.
|
||||||
*
|
*
|
||||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
* @author Jon Brisbin
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
||||||
MongoPersistentEntity<T>, ApplicationContextAware {
|
MongoPersistentEntity<T>, ApplicationContextAware {
|
||||||
@@ -47,6 +47,8 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
|||||||
private final SpelExpressionParser parser;
|
private final SpelExpressionParser parser;
|
||||||
private final StandardEvaluationContext context;
|
private final StandardEvaluationContext context;
|
||||||
|
|
||||||
|
private MongoPersistentProperty versionProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
|
* Creates a new {@link BasicMongoPersistentEntity} with the given {@link TypeInformation}. Will default the
|
||||||
* collection name to the entities simple type name.
|
* collection name to the entities simple type name.
|
||||||
@@ -71,20 +73,41 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mapping.MutablePersistentEntity#addPersistentProperty(P)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addPersistentProperty(MongoPersistentProperty property) {
|
||||||
|
|
||||||
|
if (property.isVersionProperty()) {
|
||||||
|
|
||||||
|
if (this.versionProperty != null) {
|
||||||
|
throw new MappingException(String.format(
|
||||||
|
"Attempt to add version property %s but already have property %s registered "
|
||||||
|
+ "as version. Check your mapping configuration!", property.getField(), versionProperty.getField()));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.versionProperty = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.addPersistentProperty(property);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* (non-Javadoc)
|
* (non-Javadoc)
|
||||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||||
*/
|
*/
|
||||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||||
|
|
||||||
context.addPropertyAccessor(new BeanFactoryAccessor());
|
context.addPropertyAccessor(new BeanFactoryAccessor());
|
||||||
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
|
context.setBeanResolver(new BeanFactoryResolver(applicationContext));
|
||||||
context.setRootObject(applicationContext);
|
context.setRootObject(applicationContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
* Returns the collection the entity should be stored in.
|
* (non-Javadoc)
|
||||||
*
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getCollection()
|
||||||
* @return
|
|
||||||
*/
|
*/
|
||||||
public String getCollection() {
|
public String getCollection() {
|
||||||
|
|
||||||
@@ -92,6 +115,22 @@ public class BasicMongoPersistentEntity<T> extends BasicPersistentEntity<T, Mong
|
|||||||
return expression.getValue(context, String.class);
|
return expression.getValue(context, String.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#getVersionProperty()
|
||||||
|
*/
|
||||||
|
public MongoPersistentProperty getVersionProperty() {
|
||||||
|
return versionProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#hasVersionProperty()
|
||||||
|
*/
|
||||||
|
public boolean hasVersionProperty() {
|
||||||
|
return getVersionProperty() != null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
|
* {@link Comparator} implementation inspecting the {@link MongoPersistentProperty}'s order.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2011-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -32,9 +32,10 @@ import org.springframework.util.StringUtils;
|
|||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mongo specific {@link org.springframework.data.mapping.PersistentProperty} implementation.
|
* MongoDB specific {@link org.springframework.data.mapping.MongoPersistentProperty} implementation.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty> implements
|
public class BasicMongoPersistentProperty extends AnnotationBasedPersistentProperty<MongoPersistentProperty> implements
|
||||||
MongoPersistentProperty {
|
MongoPersistentProperty {
|
||||||
@@ -112,8 +113,9 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
|||||||
return annotation != null && StringUtils.hasText(annotation.value()) ? annotation.value() : field.getName();
|
return annotation != null && StringUtils.hasText(annotation.value()) ? annotation.value() : field.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mongodb.core.core.mapping.MongoPersistentProperty#getFieldOrder()
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getFieldOrder()
|
||||||
*/
|
*/
|
||||||
public int getFieldOrder() {
|
public int getFieldOrder() {
|
||||||
org.springframework.data.mongodb.core.mapping.Field annotation = getField().getAnnotation(
|
org.springframework.data.mongodb.core.mapping.Field annotation = getField().getAnnotation(
|
||||||
@@ -121,25 +123,36 @@ public class BasicMongoPersistentProperty extends AnnotationBasedPersistentPrope
|
|||||||
return annotation != null ? annotation.order() : Integer.MAX_VALUE;
|
return annotation != null ? annotation.order() : Integer.MAX_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mapping.AbstractPersistentProperty#createAssociation()
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Association<MongoPersistentProperty> createAssociation() {
|
protected Association<MongoPersistentProperty> createAssociation() {
|
||||||
return new Association<MongoPersistentProperty>(this, null);
|
return new Association<MongoPersistentProperty>(this, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mongodb.core.core.mapping.MongoPersistentProperty#isDbReference()
|
* (non-Javadoc)
|
||||||
*/
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isDbReference()
|
||||||
|
*/
|
||||||
public boolean isDbReference() {
|
public boolean isDbReference() {
|
||||||
return getField().isAnnotationPresent(DBRef.class);
|
return getField().isAnnotationPresent(DBRef.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mongodb.core.core.mapping.MongoPersistentProperty#getDBRef()
|
* (non-Javadoc)
|
||||||
*/
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#getDBRef()
|
||||||
|
*/
|
||||||
public DBRef getDBRef() {
|
public DBRef getDBRef() {
|
||||||
return getField().getAnnotation(DBRef.class);
|
return getField().getAnnotation(DBRef.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentProperty#isVersionProperty()
|
||||||
|
*/
|
||||||
|
public boolean isVersionProperty() {
|
||||||
|
return getField().isAnnotationPresent(Version.class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2011-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2011-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.data.mongodb.core.mapping;
|
package org.springframework.data.mongodb.core.mapping;
|
||||||
|
|
||||||
import java.beans.PropertyDescriptor;
|
import java.beans.PropertyDescriptor;
|
||||||
@@ -23,12 +22,16 @@ import org.springframework.beans.BeansException;
|
|||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.ApplicationContextAware;
|
import org.springframework.context.ApplicationContextAware;
|
||||||
import org.springframework.data.mapping.context.AbstractMappingContext;
|
import org.springframework.data.mapping.context.AbstractMappingContext;
|
||||||
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Jon Brisbin <jbrisbin@vmware.com>
|
* Default implementation of a {@link MappingContext} for MongoDB using {@link BasicMongoPersistentEntity} and
|
||||||
* @author Oliver Gierke ogierke@vmware.com
|
* {@link BasicMongoPersistentProperty} as primary abstractions.
|
||||||
|
*
|
||||||
|
* @author Jon Brisbin
|
||||||
|
* @author Oliver Gierke
|
||||||
*/
|
*/
|
||||||
public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersistentEntity<?>, MongoPersistentProperty>
|
public class MongoMappingContext extends AbstractMappingContext<BasicMongoPersistentEntity<?>, MongoPersistentProperty>
|
||||||
implements ApplicationContextAware {
|
implements ApplicationContextAware {
|
||||||
|
|||||||
@@ -1,12 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011-2012 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.mapping;
|
package org.springframework.data.mongodb.core.mapping;
|
||||||
|
|
||||||
import org.springframework.data.mapping.PersistentEntity;
|
import org.springframework.data.mapping.PersistentEntity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* MongoDB specific {@link PersistentEntity} abstraction.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersistentProperty> {
|
public interface MongoPersistentEntity<T> extends PersistentEntity<T, MongoPersistentProperty> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the collection the entity shall be persisted to.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
String getCollection();
|
String getCollection();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link MongoPersistentProperty} that represents the version attribute of an entity. Will not be
|
||||||
|
* {@literal null} if {@link #hasVersionProperty()}.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
MongoPersistentProperty getVersionProperty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the entity has a property representing the version of the entity.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean hasVersionProperty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2011-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -19,9 +19,10 @@ import org.springframework.core.convert.converter.Converter;
|
|||||||
import org.springframework.data.mapping.PersistentProperty;
|
import org.springframework.data.mapping.PersistentProperty;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mongo specific {@link org.springframework.data.mapping.PersistentProperty} implementation.
|
* MongoDB specific {@link org.springframework.data.mapping.PersistentProperty} extension.
|
||||||
*
|
*
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
public interface MongoPersistentProperty extends PersistentProperty<MongoPersistentProperty> {
|
public interface MongoPersistentProperty extends PersistentProperty<MongoPersistentProperty> {
|
||||||
|
|
||||||
@@ -55,6 +56,13 @@ public interface MongoPersistentProperty extends PersistentProperty<MongoPersist
|
|||||||
*/
|
*/
|
||||||
DBRef getDBRef();
|
DBRef getDBRef();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the property is representing the version attribute of an entity.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
boolean isVersionProperty();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple {@link Converter} implementation to transform a {@link MongoPersistentProperty} into its field name.
|
* Simple {@link Converter} implementation to transform a {@link MongoPersistentProperty} into its field name.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2011 by the original author(s).
|
* Copyright 2012-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@@ -29,22 +29,25 @@ import org.springframework.data.mongodb.MongoCollectionUtils;
|
|||||||
import org.springframework.data.util.TypeInformation;
|
import org.springframework.data.util.TypeInformation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* @deprecated use {@link MongoMappingContext} instead.
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public class SimpleMongoMappingContext extends
|
public class SimpleMongoMappingContext extends
|
||||||
AbstractMappingContext<SimpleMongoMappingContext.SimpleMongoPersistentEntity<?>, MongoPersistentProperty> {
|
AbstractMappingContext<SimpleMongoMappingContext.SimpleMongoPersistentEntity<?>, MongoPersistentProperty> {
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mapping.BasicMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected <T> SimpleMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
|
protected <T> SimpleMongoPersistentEntity<T> createPersistentEntity(TypeInformation<T> typeInformation) {
|
||||||
return new SimpleMongoPersistentEntity<T>(typeInformation);
|
return new SimpleMongoPersistentEntity<T>(typeInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
/*
|
||||||
* @see org.springframework.data.mapping.BasicMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.util.TypeInformation, org.springframework.data.mapping.BasicPersistentEntity)
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(java.lang.reflect.Field, java.beans.PropertyDescriptor, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected SimplePersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
|
protected SimplePersistentProperty createPersistentProperty(Field field, PropertyDescriptor descriptor,
|
||||||
@@ -112,6 +115,14 @@ public class SimpleMongoMappingContext extends
|
|||||||
public DBRef getDBRef() {
|
public DBRef getDBRef() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.core.mapping.MongoPersistentProperty#isVersion()
|
||||||
|
*/
|
||||||
|
public boolean isVersionProperty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static class SimpleMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
static class SimpleMongoPersistentEntity<T> extends BasicPersistentEntity<T, MongoPersistentProperty> implements
|
||||||
@@ -130,5 +141,20 @@ public class SimpleMongoMappingContext extends
|
|||||||
public String getCollection() {
|
public String getCollection() {
|
||||||
return MongoCollectionUtils.getPreferredCollectionName(getType());
|
return MongoCollectionUtils.getPreferredCollectionName(getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.core.mapping.MongoPersistentEntity#getVersionProperty()
|
||||||
|
*/
|
||||||
|
public MongoPersistentProperty getVersionProperty() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* (non-Javadoc)
|
||||||
|
* @see org.springframework.data.mongodb.core.mapping.MongoPersistentEntity#hasVersionProperty()
|
||||||
|
*/
|
||||||
|
public boolean hasVersionProperty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 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.mapping;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.*;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Demarcates a property to be used as version field to implement optimistic locking on entities.
|
||||||
|
*
|
||||||
|
* @since 1.4
|
||||||
|
* @author Patryk Wasik
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Target({ FIELD })
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
public @interface Version {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2011 the original author or authors.
|
* Copyright 2010-2012 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -15,13 +15,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.mongodb.core.query;
|
package org.springframework.data.mongodb.core.query;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
import com.mongodb.BasicDBObject;
|
||||||
import com.mongodb.DBObject;
|
import com.mongodb.DBObject;
|
||||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to easily construct MongoDB update clauses.
|
||||||
|
*
|
||||||
|
* @author Thomas Risberg
|
||||||
|
* @author Mark Pollack
|
||||||
|
* @author Oliver Gierke
|
||||||
|
*/
|
||||||
public class Update {
|
public class Update {
|
||||||
|
|
||||||
public enum Position {
|
public enum Position {
|
||||||
@@ -40,6 +50,31 @@ public class Update {
|
|||||||
return new Update().set(key, value);
|
return new Update().set(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an {@link Update} instance from the given {@link DBObject}. Allows to explicitly exlude fields from making
|
||||||
|
* it into the created {@link Update} object.
|
||||||
|
*
|
||||||
|
* @param object the source {@link DBObject} to create the update from.
|
||||||
|
* @param exclude the fields to exclude.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Update fromDBObject(DBObject object, String... exclude) {
|
||||||
|
|
||||||
|
Update update = new Update();
|
||||||
|
List<String> excludeList = Arrays.asList(exclude);
|
||||||
|
|
||||||
|
for (String key : object.keySet()) {
|
||||||
|
|
||||||
|
if (excludeList.contains(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
update.set(key, object.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update using the $set update modifier
|
* Update using the $set update modifier
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.core.convert.converter.Converter;
|
import org.springframework.core.convert.converter.Converter;
|
||||||
import org.springframework.dao.DataAccessException;
|
import org.springframework.dao.DataAccessException;
|
||||||
import org.springframework.dao.DataIntegrityViolationException;
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.dao.OptimisticLockingFailureException;
|
||||||
import org.springframework.data.annotation.Id;
|
import org.springframework.data.annotation.Id;
|
||||||
import org.springframework.data.annotation.PersistenceConstructor;
|
import org.springframework.data.annotation.PersistenceConstructor;
|
||||||
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
|
import org.springframework.data.mongodb.InvalidMongoDbApiUsageException;
|
||||||
@@ -75,20 +76,18 @@ import com.mongodb.WriteResult;
|
|||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Thomas Risberg
|
* @author Thomas Risberg
|
||||||
* @author Amol Nayak
|
* @author Amol Nayak
|
||||||
|
* @author Patryk Wasik
|
||||||
*/
|
*/
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@ContextConfiguration("classpath:infrastructure.xml")
|
@ContextConfiguration("classpath:infrastructure.xml")
|
||||||
public class MongoTemplateTests {
|
public class MongoTemplateTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired MongoTemplate template;
|
||||||
MongoTemplate template;
|
@Autowired MongoDbFactory factory;
|
||||||
@Autowired
|
|
||||||
MongoDbFactory factory;
|
|
||||||
|
|
||||||
MongoTemplate mappingTemplate;
|
MongoTemplate mappingTemplate;
|
||||||
|
|
||||||
@Rule
|
@Rule public ExpectedException thrown = ExpectedException.none();
|
||||||
public ExpectedException thrown = ExpectedException.none();
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -134,6 +133,7 @@ public class MongoTemplateTests {
|
|||||||
template.dropCollection(PersonWithIdPropertyOfPrimitiveInt.class);
|
template.dropCollection(PersonWithIdPropertyOfPrimitiveInt.class);
|
||||||
template.dropCollection(PersonWithIdPropertyOfTypeLong.class);
|
template.dropCollection(PersonWithIdPropertyOfTypeLong.class);
|
||||||
template.dropCollection(PersonWithIdPropertyOfPrimitiveLong.class);
|
template.dropCollection(PersonWithIdPropertyOfPrimitiveLong.class);
|
||||||
|
template.dropCollection(PersonWithVersionPropertyOfTypeInteger.class);
|
||||||
template.dropCollection(TestClass.class);
|
template.dropCollection(TestClass.class);
|
||||||
template.dropCollection(Sample.class);
|
template.dropCollection(Sample.class);
|
||||||
template.dropCollection(MyPerson.class);
|
template.dropCollection(MyPerson.class);
|
||||||
@@ -1263,6 +1263,44 @@ public class MongoTemplateTests {
|
|||||||
assertThat(result.get(0), hasProperty("name", is("Oleg")));
|
assertThat(result.get(0), hasProperty("name", is("Oleg")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see DATAMONGO-279
|
||||||
|
*/
|
||||||
|
@Test(expected = OptimisticLockingFailureException.class)
|
||||||
|
public void optimisticLockingHandling() {
|
||||||
|
|
||||||
|
// Init version
|
||||||
|
PersonWithVersionPropertyOfTypeInteger person = new PersonWithVersionPropertyOfTypeInteger();
|
||||||
|
person.age = 29;
|
||||||
|
person.firstName = "Patryk";
|
||||||
|
template.save(person);
|
||||||
|
|
||||||
|
List<PersonWithVersionPropertyOfTypeInteger> result = template
|
||||||
|
.findAll(PersonWithVersionPropertyOfTypeInteger.class);
|
||||||
|
|
||||||
|
assertThat(result, hasSize(1));
|
||||||
|
assertThat(result.get(0).version, is(0));
|
||||||
|
|
||||||
|
// Version change
|
||||||
|
person = result.get(0);
|
||||||
|
person.firstName = "Patryk2";
|
||||||
|
|
||||||
|
template.save(person);
|
||||||
|
|
||||||
|
assertThat(person.version, is(1));
|
||||||
|
|
||||||
|
result = mappingTemplate.findAll(PersonWithVersionPropertyOfTypeInteger.class);
|
||||||
|
|
||||||
|
assertThat(result, hasSize(1));
|
||||||
|
assertThat(result.get(0).version, is(1));
|
||||||
|
|
||||||
|
// Optimistic lock exception
|
||||||
|
person.version = 0;
|
||||||
|
person.firstName = "Patryk3";
|
||||||
|
|
||||||
|
template.save(person);
|
||||||
|
}
|
||||||
|
|
||||||
static class MyId {
|
static class MyId {
|
||||||
|
|
||||||
String first;
|
String first;
|
||||||
@@ -1271,14 +1309,12 @@ public class MongoTemplateTests {
|
|||||||
|
|
||||||
static class TypeWithMyId {
|
static class TypeWithMyId {
|
||||||
|
|
||||||
@Id
|
@Id MyId id;
|
||||||
MyId id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Sample {
|
public static class Sample {
|
||||||
|
|
||||||
@Id
|
@Id String id;
|
||||||
String id;
|
|
||||||
String field;
|
String field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2012 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 org.springframework.data.mongodb.core.mapping.Version;
|
||||||
|
|
||||||
|
public class PersonWithVersionPropertyOfTypeInteger {
|
||||||
|
|
||||||
|
String id;
|
||||||
|
String firstName;
|
||||||
|
int age;
|
||||||
|
|
||||||
|
@Version
|
||||||
|
Integer version;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PersonWithVersionPropertyOfTypeInteger [id=" + id + ", firstName=" + firstName + ", age=" + age
|
||||||
|
+ ", version=" + version + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user