【外国技术文章翻译】Spring Data MongoDB级联保存在DBRef对象上

如果您想看看英语原文,可以直接跳到底部的“阅读原文”

默认情况下, 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 {
  @Autowired
  private MongoOperations mongoOperations;
 
  @Override
  public 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 class="pl.maciejwalkowiak.springdata.mongodb.CascadingMongoEventListener" />

让我们测试一下

为了显示一个示例,我制作了两个文档类:

@Document
public class User {
  @Id
  private ObjectId id;
  private String name;
 
  @DBRef
  @CascadeSave
  private Address address;
 
  public User(String name) {
      this.name = name;
  }
 
  // ... getters, setters, equals hashcode
}
@Document
public class Address {
  @Id
  private 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 {
 
  @Autowired
  private MongoOperations mongoOperations;
 
  /**
  * Clean collections before tests are executed
  */
  @Before
  public void cleanCollections() {
      mongoOperations.dropCollection(User.class);
      mongoOperations.dropCollection(Address.class);
  }
 
  @Test
  public void testCascadeSave() {
      // given
      User user = new User("John Smith");
      user.setAddress(new Address("London"));
 
      // when
      mongoOperations.save(user);
 
      // then
      List<User> users = mongoOperations.findAll(User.class);
      assertThat(users).hasSize(1).containsOnly(user);
 
      User savedUser = users.get(0);
      assertThat(savedUser.getAddress()).isNotNull().isEqualTo(user.getAddress());
 
      List<Address> 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 对象上。


翻译者:dnc8371

来源链接:

https://blog.csdn.net/dnc8371/article/details/106704995

英语原文链接:

https://www.javacodegeeks.com/2013/11/spring-data-mongodb-cascade-save-on-dbref-objects.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值