MongoDB
 sql >> Baza danych >  >> NoSQL >> MongoDB

Niestandardowe kaskadowanie w Spring Data MongoDB

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]"
    }
}

  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. Problem w zwracaniu danych pobranych z zapytań DB wywoływanych w pętli

  2. Uruchomienie PHP Nie można załadować biblioteki dynamicznej php_mongo.dll

  3. $pull . MongoDB

  4. Jak ściągnąć jedną instancję elementu w tablicy w MongoDB?

  5. Napraw „długość/szerokość geograficzna jest poza granicami” w MongoDB podczas tworzenia indeksu 2dsphere