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、onAfterLoad和onAfterConvert。
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:
现在让我们来看看一个场景–我们保存一个带有emailAddress的User,并且保存操作会自动级联到这个嵌入式实体。
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.