Custom Cascading in Spring Data MongoDB – Spring Data MongoDB中的自定义级联

最后修改: 2015年 8月 24日

1. Overview

1.概述

This tutorial will continue to explore some of the core features of Spring Data MongoDB – the @DBRef annotation and life-cycle events.

本教程将继续探讨Spring Data MongoDB的一些核心功能–@DBRef注解和生命周期事件。

2. @DBRef

2.@DBRef

The mapping framework doesn’t support storing parent-child relations and embedded documents within other documents. What we can do though is – we can store them separately and use a DBRef to refer to the documents.

映射框架不支持存储父子关系和其他文档中的嵌入式文档。但我们可以做的是–我们可以将它们分开存储,并使用DBRef来引用这些文档。

When the object is loaded from MongoDB, those references will be eagerly resolved, and we’ll get back a mapped object that looks the same as if it had been stored embedded within our master document.

当对象从MongoDB加载时,这些引用将被急切地解决,我们将得到一个映射的对象,看起来就像它被嵌入到我们的主文档中存储一样。

Let’s look at some code:

让我们看看一些代码。

@DBRef
private EmailAddress emailAddress;

EmailAddress looks like:

EmailAddress看起来像。

@Document
public class EmailAddress {
    @Id
    private String id;
    
    private String value;
    
    // standard getters and setters
}

Note that the mapping framework doesn’t handle cascading operations. So – for instance – if we trigger a save on a parent, the child won’t be saved automatically – we’ll need to explicitly trigger the save on the child if we want to save it as well.

请注意,映射框架并不处理级联操作。所以–例如–如果我们在父节点上触发了save,子节点不会自动被保存–如果我们想把子节点也保存起来,我们就需要明确地在子节点上触发保存操作。

This is precisely where life cycle events come in handy.

这正是生命周期事件派上用场的地方

3. Lifecycle Events

3.生命周期事件

Spring Data MongoDB publishes some very useful life cycle events – such as onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad and onAfterConvert.

Spring Data MongoDB发布了一些非常有用的生命周期事件–例如onBeforeConvert、onBeforeSave、onAfterSave、onAfterLoadonAfterConvert。

To intercept one of the events, we need to register a subclass of AbstractMappingEventListener and override one of the methods here. When the event is dispatched, our listener will be called and domain object passed in.

为了拦截其中一个事件,我们需要注册一个AbstractMappingEventListener的子类,并重写这里的一个方法。当事件被派发时,我们的监听器将被调用,域对象被传入。

3.1. Basic Cascade Save

3.1.基本级联保存

Let’s look at the example we had earlier – saving the user with the emailAddress. We can now listen to the onBeforeConvert event which will be called before a domain object goes into the converter:

让我们看看我们之前的例子–用emailAddress保存user。我们现在可以监听onBeforeConvert事件,它将在域对象进入转换器之前被调用。

public class UserCascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {
    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(BeforeConvertEvent<Object> event) { 
        Object source = event.getSource(); 
        if ((source instanceof User) && (((User) source).getEmailAddress() != null)) { 
            mongoOperations.save(((User) source).getEmailAddress());
        }
    }
}

Now we just need to register the listener into MongoConfig:

现在我们只需要将监听器注册到MongoConfig

@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
    return new UserCascadeSaveMongoEventListener();
}

Or as XML:

或者作为XML。

<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />

And we have cascading semantics all done – albeit only for the user.

而我们已经完成了级联语义–尽管只是对用户而言。

3.2. A Generic Cascade Implementation

3.2.一个通用的级联实现

Let’s now improve the previous solution by making the cascade functionality generic. Let’s start by defining a custom annotation:

现在让我们通过使级联功能通用来改进之前的解决方案。让我们从定义一个自定义注解开始。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
    //
}

Let’s now work on our custom listener to handle these fields generically and not have to cast to any particular entity:

现在让我们在我们的自定义监听器上工作,以通用地处理这些字段,而不必投向任何特定的实体。

public class CascadeSaveMongoEventListener extends AbstractMongoEventListener<Object> {

    @Autowired
    private MongoOperations mongoOperations;

    @Override
    public void onBeforeConvert(BeforeConvertEvent<Object> event) { 
        Object source = event.getSource(); 
        ReflectionUtils.doWithFields(source.getClass(), 
          new CascadeCallback(source, mongoOperations));
    }
}

So we’re using the reflection utility out of Spring, and we’re running our callback on all fields that meet our criteria:

所以我们使用Spring的反射工具,在所有符合我们标准的字段上运行我们的回调。

@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
    ReflectionUtils.makeAccessible(field);

    if (field.isAnnotationPresent(DBRef.class) && 
      field.isAnnotationPresent(CascadeSave.class)) {
    
        Object fieldValue = field.get(getSource());
        if (fieldValue != null) {
            FieldCallback callback = new FieldCallback();
            ReflectionUtils.doWithFields(fieldValue.getClass(), callback);

            getMongoOperations().save(fieldValue);
        }
    }
}

As you can see, we’re looking for fields that have both the DBRef annotation as well as CascadeSave. Once we find these fields, we save the child entity.

正如你所看到的,我们正在寻找同时具有DBRef注释和CascadeSave的字段。一旦我们找到这些字段,我们就会保存这个子实体。

Let’s look at the FieldCallback class which we’re using to check if the child has a @Id annotation:

让我们看看FieldCallback类,我们用它来检查孩子是否有@Id注释。

public class FieldCallback 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;
    }
}

Finally, to make it all work together, we, of course, need to emailAddress field to now be correctly annotated:

最后,为了使这一切顺利进行,我们当然需要对emailAddress字段进行正确注释。

@DBRef
@CascadeSave
private EmailAddress emailAddress;

3.3. The Cascade Test

3.3.级联测试

Let’s now have a look at a scenario – we save a User with emailAddress, and the save operation cascades to this embedded entity automatically:

现在让我们来看看一个场景–我们保存一个带有emailAddressUser,并且保存操作会自动级联到这个嵌入式实体。

User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("b@gmail.com");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);

Let’s check our database:

让我们检查一下我们的数据库。

{
    "_id" : ObjectId("55cee9cc0badb9271768c8b9"),
    "name" : "Brendan",
    "age" : null,
    "email" : {
        "value" : "b@gmail.com"
    }
}

4. Conclusion

4.结论

In this article, we illustrated some cool features of Spring Data MongoDB – the @DBRef annotation, life cycle events and how we can handle cascading intelligently.

在这篇文章中,我们说明了Spring Data MongoDB的一些很酷的特性–@DBRef注解、生命周期事件以及我们如何智能地处理级联。

The implementation of all these examples and code snippets can be found over on GitHub – this is a Maven based project, so it should be easy to import and run as it is.

所有这些例子和代码片段的实现都可以在GitHub上找到–这是一个基于Maven的项目,所以应该很容易导入并按原样运行。