网站建设的一般要素,做网站网页的专业,电子商务营销的概念,白城网页制作默认情况下#xff0c; Spring Data MongoDB不支持对带有DBRef注释的引用对象的级联操作#xff0c;如引用所述 #xff1a; 映射框架不处理级联保存 。 如果更改了Person对象引用的Account对象#xff0c;则必须单独 保存 Account对象。 在Person对象上调用save 不会自动… 默认情况下 Spring Data MongoDB不支持对带有DBRef注释的引用对象的级联操作如引用所述 映射框架不处理级联保存 。 如果更改了Person对象引用的Account对象则必须单独 保存 Account对象。 在Person对象上调用save 不会自动将Account对象保存在属性帐户中。 这很成问题因为要实现保存子对象您需要覆盖父存储库中的save方法或创建其他“服务” 这里介绍了类似的方法。 在本文中我将向您展示如何使用AbstractMongoEventListener的通用实现针对所有文档实现此目标。 CascadeSave批注 由于我们无法通过添加级联属性来更改DBRef批注因此可以创建新的批注CascadeSave该批注将用于标记保存父对象时应保存哪些字段。 package pl.maciejwalkowiak.springdata.mongodb;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;Retention(RetentionPolicy.RUNTIME)
Target({ ElementType.FIELD })
public interface CascadeSave {}CascadingMongoEventListener 下一部分是实现此批注的处理程序。 我们将使用强大的Spring Application Event机制 。 特别是我们将扩展AbstractMongoEventListener以捕获已保存的对象然后再将其转换为Mongo的DBObject 。 它是如何工作的 调用对象MongoTemplate #save方法时在实际保存对象之前会将其从MongoDB api转换为DBObject。 下面实现的CascadingMongoEventListener提供了在对象转换之前捕获对象的钩子并且 仔细检查其所有字段以检查是否同时有DBRef和CascadeSave注释的字段。 找到字段时它将检查是否存在Id批注 子对象正在保存 package pl.maciejwalkowiak.springdata.mongodb;import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.model.MappingException;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.DBRef;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;import java.lang.reflect.Field;public class CascadingMongoEventListener extends AbstractMongoEventListener {Autowiredprivate MongoOperations mongoOperations;Overridepublic void onBeforeConvert(final Object source) {ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {ReflectionUtils.makeAccessible(field);if (field.isAnnotationPresent(DBRef.class) field.isAnnotationPresent(CascadeSave.class)) {final Object fieldValue field.get(source);DbRefFieldCallback callback new DbRefFieldCallback();ReflectionUtils.doWithFields(fieldValue.getClass(), callback);if (!callback.isIdFound()) {throw new MappingException(Cannot perform cascade save on child object without id set);}mongoOperations.save(fieldValue);}}});}private static class DbRefFieldCallback implements ReflectionUtils.FieldCallback {private boolean idFound;public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {ReflectionUtils.makeAccessible(field);if (field.isAnnotationPresent(Id.class)) {idFound true;}}public boolean isIdFound() {return idFound;}}
}映射要求 如您所见为了使工作正常您需要遵循一些规则 父类的子级属性必须使用DBRef和CascadeSave进行映射 子类需要具有以Id注释的属性如果应该自动生成该ID则应按ObjectId的类型 用法 为了在项目中使用级联保存您只需要在Spring Context中注册CascadingMongoEventListener bean classpl.maciejwalkowiak.springdata.mongodb.CascadingMongoEventListener /让我们测试一下 为了显示一个示例我制作了两个文档类 Document
public class User {Idprivate ObjectId id;private String name;DBRefCascadeSaveprivate Address address;public User(String name) {this.name name;}// ... getters, setters, equals hashcode
}Document
public class Address {Idprivate ObjectId id;private String city;public Address(String city) {this.city city;}// ... getters, setters, equals hashcode
} 在测试中有一个创建了地址的用户然后保存了该用户。 测试将仅涵盖积极的情况并且仅用于表明它确实有效 applcationContext-tests.xml仅包含默认的Spring Data MongoDB Bean和已注册的CascadingMongoEventListener RunWith(SpringJUnit4ClassRunner.class)
ContextConfiguration(locations {classpath:applcationContext-tests.xml})
public class CascadingMongoEventListenerTest {Autowiredprivate MongoOperations mongoOperations;/*** Clean collections before tests are executed*/Beforepublic void cleanCollections() {mongoOperations.dropCollection(User.class);mongoOperations.dropCollection(Address.class);}Testpublic void testCascadeSave() {// givenUser user new User(John Smith);user.setAddress(new Address(London));// whenmongoOperations.save(user);// thenListUser users mongoOperations.findAll(User.class);assertThat(users).hasSize(1).containsOnly(user);User savedUser users.get(0);assertThat(savedUser.getAddress()).isNotNull().isEqualTo(user.getAddress());ListAddress addresses mongoOperations.findAll(Address.class);assertThat(addresses).hasSize(1).containsOnly(user.getAddress());}
} 我们也可以在Mongo控制台中进行检查 db.user.find()
{ _id : ObjectId(4f9d1bab1a8854250a5bf13e), _class : pl.maciejwalkowiak.springdata.mongodb.domain.User, name : John Smith, address : { $ref : address, $id : ObjectId(4f9d1ba41a8854250a5bf13d) } }db.address.find()
{ _id : ObjectId(4f9d1ba41a8854250a5bf13d), _class : pl.maciejwalkowiak.springdata.mongodb.domain.Address, city : London }摘要 通过这种简单的解决方案我们最终可以通过一个方法调用保存子对象而无需为每个文档类实现任何特殊的功能。 我相信将来作为Spring Data MongoDB版本的一部分我们会在级联删除中找到该功能。 这里介绍的解决方案有效但 它需要使用其他注释 使用反射API遍历字段这不是最快的方法但可以根据需要随意实现缓存 如果这可以是Spring Data MongoDB的一部分而不是附加的注释则DBRef可以具有附加的cascade属性。 除了反射之外我们可以将MongoMappingContext和MongoPersistentEntity一起使用。 我已经开始准备带有这些更改的请求请求。 我们将看看它是否将被Spring Source团队接受。 参考 Spring Data MongoDB级联保存在我们的JCG合作伙伴 Maciej Walkowiak的“ 软件开发之旅”博客上的DBRef对象上。 翻译自: https://www.javacodegeeks.com/2013/11/spring-data-mongodb-cascade-save-on-dbref-objects.html