1. Przegląd
Ten samouczek będzie kontynuował odkrywanie niektórych podstawowych funkcji Spring Data MongoDB — @DBRef adnotacje i wydarzenia z cyklu życia.
2. @DBRef
Struktura mapowania nie obsługuje przechowywania relacji rodzic-dziecko i osadzone dokumenty w innych dokumentach. Możemy jednak zrobić – możemy przechowywać je osobno i używać DBRef aby odnieść się do dokumentów.
Gdy obiekt zostanie załadowany z MongoDB, te odniesienia zostaną szybko rozwiązane i otrzymamy zmapowany obiekt, który wygląda tak samo, jak gdyby był przechowywany osadzony w naszym głównym dokumencie.
Spójrzmy na kod:
@DBRef
private EmailAddress emailAddress;
Adres e-mail wygląda tak:
@Document
public class EmailAddress {
@Id
private String id;
private String value;
// standard getters and setters
}
Pamiętaj, że platforma mapowania nie obsługuje operacji kaskadowych . A więc – na przykład – jeśli uruchomimy zapis na rodzicu, dziecko nie zostanie zapisane automatycznie – jeśli chcemy je również zapisać, będziemy musieli jawnie wywołać zapisywanie na dziecku.
Właśnie tutaj przydają się zdarzenia cyklu życia .
3. Wydarzenia cyklu życia
Spring Data MongoDB publikuje kilka bardzo przydatnych zdarzeń cyklu życia — takich jak onBeforeConvert, onBeforeSave, onAfterSave, onAfterLoad i po konwersji.
Aby przechwycić jedno ze zdarzeń, musimy zarejestrować podklasę AbstractMappingEventListener i zastąp jedną z metod tutaj. Po wywołaniu zdarzenia nasz odbiornik zostanie wywołany, a obiekt domeny zostanie przekazany.
3.1. Podstawowy zapis kaskadowy
Spójrzmy na przykład, który mieliśmy wcześniej – zapisywanie użytkownika z adresem e-mail . Możemy teraz posłuchać onBeforeConvert zdarzenie, które zostanie wywołane zanim obiekt domeny trafi do konwertera:
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());
}
}
}
Teraz wystarczy zarejestrować słuchacza w MongoConfig :
@Bean
public UserCascadeSaveMongoEventListener userCascadingMongoEventListener() {
return new UserCascadeSaveMongoEventListener();
}
Lub jako XML:
<bean class="org.baeldung.event.UserCascadeSaveMongoEventListener" />
I mamy całą semantykę kaskadową – aczkolwiek tylko dla użytkownika.
3.2. Ogólna implementacja kaskadowa
Udoskonalmy teraz poprzednie rozwiązanie, nadając funkcję kaskadową ogólną. Zacznijmy od zdefiniowania niestandardowej adnotacji:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CascadeSave {
//
}
Zajmijmy się teraz pracą nad naszym niestandardowym odbiornikiem aby obsłużyć te pola ogólnie i nie musieć rzutować na żadną konkretną jednostkę:
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));
}
}
Używamy więc narzędzia refleksji z Springa i uruchamiamy nasze wywołanie zwrotne na wszystkich polach, które spełniają nasze kryteria:
@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);
}
}
}
Jak widać, szukamy pól, które mają zarówno DBRef adnotacja oraz CascadeSave . Po znalezieniu tych pól zapisujemy encję podrzędną.
Spójrzmy na FieldCallback klasa, której używamy do sprawdzenia, czy dziecko ma @Id adnotacja:
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;
}
}
Wreszcie, aby wszystko działało razem, oczywiście musimy adres e-mail pole, które ma być teraz poprawnie opisane:
@DBRef
@CascadeSave
private EmailAddress emailAddress;
3.3. Test kaskadowy
Przyjrzyjmy się teraz scenariuszowi – zapisujemy użytkownika z adresem e-mail , a operacja zapisywania jest automatycznie kaskadowana do tej osadzonej jednostki:
User user = new User();
user.setName("Brendan");
EmailAddress emailAddress = new EmailAddress();
emailAddress.setValue("[email protected]");
user.setEmailAddress(emailAddress);
mongoTemplate.insert(user);
Sprawdźmy naszą bazę danych:
{
"_id" : ObjectId("55cee9cc0badb9271768c8b9"),
"name" : "Brendan",
"age" : null,
"email" : {
"value" : "[email protected]"
}
}