1. Прегледа
Този урок ще продължи да изследва някои от основните функции на Spring Data MongoDB – @DBRef анотация и събития от жизнения цикъл.
2. @DBref
Рамката за картографиране не поддържа съхраняване на отношения родител-дете и вградени документи в други документи. Това, което можем да направим обаче е – можем да ги съхраняваме отделно и да използваме DBRef за справка с документите.
Когато обектът се зареди от MongoDB, тези препратки ще бъдат разрешени с нетърпение и ще получим обратно картографиран обект, който изглежда по същия начин, сякаш е бил съхранен вграден в нашия главен документ.
Нека разгледаме някакъв код:
@DBRef
private EmailAddress emailAddress;
Имейл адрес изглежда така:
@Document
public class EmailAddress {
@Id
private String id;
private String value;
// standard getters and setters
}
Имайте предвид, че рамката за картографиране не обработва каскадни операции . Така че – например – ако задействаме запазване на родител, детето няма да бъде запазено автоматично – ще трябва изрично да задействаме запазването на детето, ако искаме да го запазим също.
Точно тук събитията от жизнения цикъл са полезни .
3. Събития от жизнения цикъла
Spring Data MongoDB публикува някои много полезни събития от жизнения цикъл – като onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad и onAfterConvert.
За да прихванем едно от събитията, трябва да регистрираме подклас на AbstractMappingEventListener и отменете един от методите тук. Когато събитието бъде изпратено, нашият слушател ще бъде извикан и обектът на домейна ще бъде предаден.
3.1. Basic Cascade Save
Нека разгледаме примера, който имахме по-рано – запазване на потребител с имейл адрес . Вече можем да слушаме 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());
}
}
}
Сега просто трябва да регистрираме слушателя в MongoConfig :
@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
return new UserCascadeSaveMongoEventListener();
}
Или като XML:
<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />
И ние имаме каскадна семантика, цялата готова – макар и само за потребителя.
3.2. Общо каскадно изпълнение
Нека сега подобрим предишното решение, катонаправим каскадната функционалност обща. Нека започнем с дефиниране на персонализирана анотация:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
//
}
Нека сега работим върху нашия персонализиран слушател за да обработвате тези полета общо и да не се налага да прехвърляте към конкретен обект:
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));
}
}
Така че използваме помощната програма за отразяване от 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);
}
}
}
Както можете да видите, ние търсим полета, които имат и двата DBRef анотация, както и CascadeSave . След като намерим тези полета, запазваме дъщерния обект.
Нека разгледаме 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;
}
}
И накрая, за да накараме всичко да работи заедно, ние, разбира се, трябва да emailAddress поле, което сега да бъде правилно анотирано:
@DBRef
@CascadeSave
private EmailAddress emailAddress;
3.3. Каскадният тест
Нека сега да разгледаме сценария – запазваме Потребител с emailAddress , а операцията за запазване каскадно преминава към този вграден обект автоматично:
User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);
Нека проверим нашата база данни:
{
"_id" : ObjectId("55cee9cc0badb9271768c8b9"),
"name" : "Brendan",
"age" : null,
"email" : {
"value" : "[email protected]"
}
}