1. Panoramica
In questo tutorial capiremo come utilizzare Morphia, un Object Document Mapper (ODM) per MongoDB in Java.
Nel processo, capiremo anche cos'è un ODM e come facilita il lavoro con MongoDB.
2. Che cos'è un ODM ?
Per chi non lo sapesse in quest'area, MongoDB è un database orientato ai documenti creato per essere distribuito per natura . I database orientati ai documenti, in parole povere, gestiscono i documenti, che non sono altro che un modo senza schema di organizzare i dati semistrutturati . Rientrano in un ombrello più ampio e vagamente definito di database NoSQL, dal nome del loro apparente allontanamento dall'organizzazione tradizionale dei database SQL.
MongoDB fornisce driver per quasi tutti i linguaggi di programmazione più diffusi come Java . Questi driver offrono un livello di astrazione per lavorare con MongoDB in modo da non lavorare direttamente con Wire Protocol. Consideralo come Oracle che fornisce un'implementazione del driver JDBC per il proprio database relazionale.
Tuttavia, se ricordiamo i giorni in cui abbiamo lavorato direttamente con JDBC, possiamo apprezzare quanto possa diventare disordinato, specialmente in un paradigma orientato agli oggetti. Fortunatamente, abbiamo in nostro soccorso framework Object Relational Mapping (ORM) come Hibernate. Non è molto diverso per MongoDB.
Sebbene possiamo certamente lavorare con il driver di basso livello, richiede molto più standard per svolgere l'attività. Qui abbiamo un concetto simile a ORM chiamato Object Document Mapper (ODM) . Morphia riempie esattamente quello spazio per il linguaggio di programmazione Java e lavora sopra il driver Java per MongoDB.
3. Impostazione delle dipendenze
Abbiamo visto abbastanza teoria per farci entrare in qualche codice. Per i nostri esempi, modelleremo una libreria di libri e vedremo come gestirla in MongoDB usando Morphia.
Ma prima di iniziare, dovremo configurare alcune delle dipendenze.
3.1. MongoDB
Abbiamo bisogno di un'istanza in esecuzione di MongoDB con cui lavorare. Esistono diversi modi per ottenerlo e il più semplice è scaricare e installare l'edizione della community sul nostro computer locale.
Dovremmo lasciare tutte le configurazioni predefinite così come sono, inclusa la porta su cui viene eseguito MongoDB.
3.2. Morfia
Possiamo scaricare i JAR predefiniti per Morphia da Maven Central e usarli nel nostro progetto Java.
Tuttavia, il modo più semplice è utilizzare uno strumento di gestione delle dipendenze come Maven:
<dependency>
<groupId>dev.morphia.morphia</groupId>
<artifactId>core</artifactId>
<version>1.5.3</version>
</dependency>
4. Come connettersi utilizzando Morphia?
Ora che MongoDB è installato e funzionante e abbiamo configurato Morphia nel nostro progetto Java, siamo pronti per connetterci a MongoDB usando Morphia.
Vediamo come possiamo farlo:
Morphia morphia = new Morphia();
morphia.mapPackage("com.baeldung.morphia");
Datastore datastore = morphia.createDatastore(new MongoClient(), "library");
datastore.ensureIndexes();
Questo è praticamente tutto! Capiamolo meglio. Abbiamo bisogno di due cose per far funzionare le nostre operazioni di mappatura:
- Un mappatore:è responsabile della mappatura dei nostri POJO Java alle raccolte MongoDB . Nel nostro frammento di codice sopra, Morphia è la classe responsabile di questo. Nota come stiamo configurando il pacchetto in cui dovrebbe cercare i nostri POJO.
- Una connessione:questa è la connessione a un database MongoDB su cui il mapper può eseguire diverse operazioni. La classe Datastore prende come parametro un'istanza di MongoClient (dal driver Java MongoDB) e il nome del database MongoDB, restituendo una connessione attiva con cui lavorare .
Quindi, siamo pronti per utilizzare questo Datastore e lavorare con le nostre entità.
5. Come lavorare con le entità?
Prima di poter utilizzare il nostro Datastore appena coniato , dobbiamo definire alcune entità di dominio con cui lavorare.
5.1. Entità semplice
Iniziamo definendo un semplice Libro entità con alcuni attributi:
@Entity("Books")
public class Book {
@Id
private String isbn;
private String title;
private String author;
@Property("price")
private double cost;
// constructors, getters, setters and hashCode, equals, toString implementations
}
Ci sono un paio di cose interessanti da notare qui:
- Nota l'annotazione @Entità che qualifica questo POJO per la mappatura ODM di Morfia
- Morphia, per impostazione predefinita, associa un'entità a una raccolta in MongoDB in base al nome della sua classe, ma possiamo sovrascriverlo esplicitamente (come abbiamo fatto per l'entità Book qui)
- Morphia, per impostazione predefinita, mappa le variabili in un'entità alle chiavi in una raccolta MongoDB in base al nome della variabile, ma ancora una volta possiamo sovrascriverlo (come abbiamo fatto per la variabile costo qui)
- Infine, dobbiamo contrassegnare una variabile nell'entità che funga da chiave primaria tramite l'annotazione @Id (come se usiamo l'ISBN per il nostro libro qui)
5.2. Entità con relazioni
Nel mondo reale, tuttavia, le entità non sono così semplici come sembrano e hanno relazioni complesse tra loro. Ad esempio, la nostra semplice entità Book può avere un editore e può fare riferimento ad altri libri di accompagnamento. Come li modelliamo?
MongoDB offre due meccanismi per costruire relazioni:referenziamento e incorporamento . Come suggerisce il nome, con il riferimento, MongoDB memorizza i dati correlati come un documento separato nella stessa raccolta o in una raccolta diversa e vi fa semplicemente riferimento utilizzando il suo ID.
Al contrario, con l'incorporamento, MongoDB memorizza o meglio incorpora la relazione all'interno del documento padre stesso.
Vediamo come possiamo usarli. Iniziamo con l'incorporamento di Publisher nel nostro Libro :
@Embedded
private Publisher publisher;
Abbastanza semplice. Ora andiamo avanti e aggiungiamo riferimenti ad altri libri:
@Reference
private List<Book> companionBooks;
Questo è tutto:Morphia fornisce annotazioni convenienti per modellare le relazioni supportate da MongoDB. La scelta tra riferimento e incorporamento, tuttavia, dovrebbe trarre spunto dalla complessità, ridondanza e coerenza del modello di dati tra le altre considerazioni.
L'esercizio è simile alla normalizzazione nei database relazionali.
Ora siamo pronti per eseguire alcune operazioni su Prenota utilizzando Datastore .
6. Alcune operazioni di base
Vediamo come lavorare con alcune delle operazioni di base utilizzando Morphia.
6.1. Salva
Iniziamo con l'operazione più semplice, creando un'istanza di Book nel nostro database MongoDB libreria :
Publisher publisher = new Publisher(new ObjectId(), "Awsome Publisher");
Book book = new Book("9781565927186", "Learning Java", "Tom Kirkman", 3.95, publisher);
Book companionBook = new Book("9789332575103", "Java Performance Companion",
"Tom Kirkman", 1.95, publisher);
book.addCompanionBooks(companionBook);
datastore.save(companionBook);
datastore.save(book);
Questo è sufficiente per consentire a Morphia di creare una raccolta nel nostro database MongoDB, se non esiste, ed eseguire un'operazione di upsert.
6.2. Interroga
Vediamo se siamo in grado di interrogare il libro che abbiamo appena creato in MongoDB:
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(1, books.size());
assertEquals(book, books.get(0));
La query su un documento in Morphia inizia con la creazione di una query utilizzando Datastore e poi l'aggiunta dichiarativa di filtri, per la gioia degli innamorati della programmazione funzionale!
Morphia supporta la costruzione di query molto più complesse con filtri e operatori. Inoltre, Morphia consente di limitare, saltare e ordinare i risultati nella query.
Inoltre, Morphia ci consente di utilizzare query grezze scritte con il driver Java per MongoDB per un maggiore controllo, se necessario.
6.3. Aggiorna
Sebbene un'operazione di salvataggio possa gestire gli aggiornamenti se la chiave primaria corrisponde, Morphia fornisce modi per aggiornare selettivamente i documenti:
Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");
UpdateOperations<Book> updates = datastore.createUpdateOperations(Book.class)
.inc("price", 1);
datastore.update(query, updates);
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(4.95, books.get(0).getCost());
Qui stiamo costruendo una query e un'operazione di aggiornamento per aumentare di uno il prezzo di tutti i libri restituiti dalla query.
6.4. Elimina
Infine, ciò che è stato creato deve essere cancellato! Ancora una volta, con Morphia, è abbastanza intuitivo:
Query<Book> query = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java");
datastore.delete(query);
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.find()
.toList();
assertEquals(0, books.size());
Creiamo la query in modo abbastanza simile a prima ed eseguiamo l'operazione di eliminazione sul Datastore .
7. Utilizzo avanzato
MongoDB ha alcune operazioni avanzate come aggregazione, indicizzazione e molte altre . Sebbene non sia possibile eseguire tutto ciò utilizzando Morphia, è certamente possibile ottenerne una parte. Per altri, purtroppo, dovremo ricorrere al driver Java per MongoDB.
Concentriamoci su alcune di queste operazioni avanzate che possiamo eseguire tramite Morphia.
7.1. Aggregazione
L'aggregazione in MongoDB ci consente di definire una serie di operazioni in una pipeline in grado di operare su un insieme di documenti e produrre un output aggregato .
Morphia ha un'API per supportare tale pipeline di aggregazione.
Supponiamo di voler aggregare i dati della nostra biblioteca in modo tale da avere tutti i libri raggruppati per autore:
Iterator<Author> iterator = datastore.createAggregation(Book.class)
.group("author", grouping("books", push("title")))
.out(Author.class);
Quindi, come funziona? Iniziamo creando una pipeline di aggregazione utilizzando lo stesso vecchio Datastore . Dobbiamo fornire l'entità su cui desideriamo eseguire operazioni di aggregazione, ad esempio Prenota qui.
Successivamente, vogliamo raggruppare i documenti per "autore" e aggregare il loro "titolo" sotto una chiave chiamata "libri". Infine, stiamo lavorando con un ODM qui. Quindi, dobbiamo definire un'entità per raccogliere i nostri dati aggregati:nel nostro caso, è Autore .
Ovviamente dobbiamo definire un'entità chiamata Autore con una variabile chiamata libri:
@Entity
public class Author {
@Id
private String name;
private List<String> books;
// other necessary getters and setters
}
Questo, ovviamente, graffia solo la superficie di un costrutto molto potente fornito da MongoDB e può essere esplorato ulteriormente per i dettagli.
7.2. Proiezione
La proiezione in MongoDB ci consente di selezionare solo i campi che vogliamo recuperare dai documenti nelle nostre query . Nel caso in cui la struttura del documento sia complessa e pesante, questo può essere davvero utile quando abbiamo bisogno solo di pochi campi.
Supponiamo di dover recuperare solo i libri con il loro titolo nella nostra query:
List<Book> books = datastore.createQuery(Book.class)
.field("title")
.contains("Learning Java")
.project("title", true)
.find()
.toList();
assertEquals("Learning Java", books.get(0).getTitle());
assertNull(books.get(0).getAuthor());
Qui, come possiamo vedere, otteniamo solo il titolo nel nostro risultato e non l'autore e altri campi. Dovremmo, tuttavia, fare attenzione nell'usare l'output previsto per salvare nuovamente in MongoDB. Ciò potrebbe comportare la perdita di dati!
7.3. Indicizzazione
Gli indici svolgono un ruolo molto importante nell'ottimizzazione delle query con i database, sia relazionali che non relazionali.
MongoDB definisce gli indici a livello di raccolta con un indice univoco creato sulla chiave primaria per impostazione predefinita . Inoltre, MongoDB consente di creare indici su qualsiasi campo o sottocampo all'interno di un documento. Dovremmo scegliere di creare un indice su una chiave a seconda della query che desideriamo creare.
Ad esempio, nel nostro esempio, potremmo voler creare un indice nel campo "titolo" di Libro come spesso finiamo per interrogarci su di esso:
@Indexes({
@Index(
fields = @Field("title"),
options = @IndexOptions(name = "book_title")
)
})
public class Book {
// ...
@Property
private String title;
// ...
}
Naturalmente, possiamo passare ulteriori opzioni di indicizzazione per personalizzare le sfumature dell'indice che viene creato. Nota che il campo deve essere annotato da @Proprietà da utilizzare in un indice.
Inoltre, oltre all'indice a livello di classe, Morphia ha un'annotazione per definire anche un indice a livello di campo.
7.4. Convalida dello schema
Abbiamo un'opzione per fornire regole di convalida dei dati per una raccolta che MongoDB può utilizzare durante l'esecuzione di un'operazione di aggiornamento o inserimento . Morphia supporta questo tramite le sue API.
Diciamo che non vogliamo inserire un libro senza un prezzo valido. Possiamo sfruttare la convalida dello schema per raggiungere questo obiettivo:
@Validation("{ price : { $gt : 0 } }")
public class Book {
// ...
@Property("price")
private double cost;
// ...
}
C'è una ricca serie di convalide fornite da MongoDB che possono essere impiegate qui.
8. ODM MongoDB alternativi
Morphia non è l'unico MongoDB ODM disponibile per Java. Ce ne sono molti altri che possiamo considerare di utilizzare nelle nostre applicazioni. Una discussione sul confronto con Morphia non è possibile qui, ma è sempre utile conoscere le nostre opzioni:
- Dati Spring:fornisce un modello di programmazione basato su Spring per lavorare con MongoDB
- MongoJack:fornisce la mappatura diretta da JSON a oggetti MongoDB
Questo non è un elenco completo di ODM MongoDB per Java, ma sono disponibili alcune alternative interessanti!