Morphia – Java ODM for MongoDB
1. Introduction
This is an in-depth article on Morphia Java ODM for MongoDB example. Mongo Database is a no SQL database. It has capabilities such as query language to retrieve from the database. It also provides operational and administrative procedures. Morphia can be used as Object Document Mapper for MongoDB. This operates on top of Java Driver for MongoDB.
2. Morphia
2.1 Prerequisites
MongoDB needs to be installed for the MongoDB Upsert example.
2.2 Download
You can download the Mongo DB from the Mongo Database website for Linux, Windows, or macOS versions.
2.3 Setup
On macOS, you need to tap the formula repository of MongoDB. This repo needs to be added to the formula list. The command below adds the formula repository of MongoDB to the formula list:
Brew Tap Command
brew tap mongodb/brew
After setting the formula list, you can install the Mongo DB with the following command :
Brew Install command
brew install mongodb-community@4.0
2.4 MongoDB CommandLine
After installation, you can run MongoDB on the command line. To run MongoDB on the command line, the following command can be used:
Mongo Execution command
mongod --config /usr/local/etc/mongod.conf
The output of the executed command is shown below.
2.5 Mongo DB Operations
After starting the Mongod process, Mongo Shell can be invoked on the command line. Mongo shell can be run using the command below:
Mongo Shell command
mongo
The output of the executed command is shown below.
2.5.1 Database Initialization
You can use database_name to create a database. This command will create a new database. If the database exists, it will start using the existing database. The command below is used to create “octopus” database:
Database creation command
use octopus
The output of the executed command is shown below.
2.5.2 Drop Database
You can use the dropDatabase()
command to drop the existing database. The command below is used to drop a database. This command will delete the database. If used this command without db, then the default ‘test’ database is deleted.
Delete Database command
db.dropDatabase()
The output of the executed command is shown below.
2.5.3 What Is an ODM?
Object Document Mapper is used for mapping Java Plain Old Java Objects to MongoDB Collections. ODM provides a connection to the MongoDB database. This connection can be used for different database operations. DataStore class is provided for MongoDB client operations. You can pass MongoClient Instance and database name to get the active connection.
2.5.4 Dependencies
Dependencies are managed by maven. Morphia and MongoDb driver dependencies are added in pom.xml as shown below.
Dependencies
<dependency> <groupId>de.flapdoodle.embedmongo</groupId> <artifactId>de.flapdoodle.embedmongo</artifactId> <version>${flapdoodle.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.mongodb</groupId> <artifactId>mongo-java-driver</artifactId> <version>${mongo.version}</version> </dependency> <dependency> <groupId>dev.morphia.morphia</groupId> <artifactId>core</artifactId> <version>${morphia.version}</version> </dependency> <dependency> <groupId>org.testcontainers</groupId> <artifactId>mongodb</artifactId> <version>1.16.3</version> <scope>test</scope> </dependency>
2.5.5 Entities (Simple, with relationships)
You can model relationships with Morphia using Referencing and Embedding. The product can be modeled to have a relationship with Company and other products created by the same company. You can look at the sample code below Product has a relationship with Writer and Company.
Product & Writer & Company relationships
package org.javacodegeeks.morphia.domain; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.HashSet; import java.util.Set; import dev.morphia.annotations.Embedded; import dev.morphia.annotations.Entity; import dev.morphia.annotations.Field; import dev.morphia.annotations.Id; import dev.morphia.annotations.Index; import dev.morphia.annotations.IndexOptions; import dev.morphia.annotations.Indexes; import dev.morphia.annotations.Property; import dev.morphia.annotations.Reference; import dev.morphia.annotations.Validation; @Entity("Products") @Indexes({ @Index(fields = @Field("title"), options = @IndexOptions(name = "product_title")) }) @Validation("{ price : { $gt : 0 } }") public class Product { @Id private String isbn; @Property private String title; private String writer; @Embedded private Company company; @Property("price") private double cost; @Reference private Set companionProducts; @Property private LocalDateTime publishDate; public Product() { } public Product(String isbn, String title, String writer, double cost, Company company) { this.isbn = isbn; this.title = title; this.writer = writer; this.cost = cost; this.company = company; this.companionProducts = new HashSet(); } public String getIsbn() { return isbn; } public Product setIsbn(String isbn) { this.isbn = isbn; return this; } public String getTitle() { return title; } public Product setTitle(String title) { this.title = title; return this; } public String getWriter() { return writer; } public Product setWriter(String writer) { this.writer = writer; return this; } public Company getCompany() { return company; } public Product setCompany(Company company) { this.company = company; return this; } public double getCost() { return cost; } public Product setCost(double cost) { this.cost = cost; return this; } public LocalDateTime getPublishDate() { return publishDate; } public Product setPublishDate(LocalDateTime publishDate) { this.publishDate = publishDate; return this; } public Set getCompanionProducts() { return companionProducts; } public Product addCompanionProducts(Product product) { if (companionProducts != null) this.companionProducts.add(product); return this; } @Override public String toString() { return "Product [isbn=" + isbn + ", title=" + title + ", writer=" + writer + ", company=" + company + ", cost=" + cost + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((writer == null) ? 0 : writer.hashCode()); long temp; temp = Double.doubleToLongBits(cost); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + ((isbn == null) ? 0 : isbn.hashCode()); result = prime * result + ((company == null) ? 0 : company.hashCode()); result = prime * result + ((title == null) ? 0 : title.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Product other = (Product) obj; if (writer == null) { if (other.writer != null) return false; } else if (!writer.equals(other.writer)) return false; if (Double.doubleToLongBits(cost) != Double.doubleToLongBits(other.cost)) return false; if (isbn == null) { if (other.isbn != null) return false; } else if (!isbn.equals(other.isbn)) return false; if (company == null) { if (other.company != null) return false; } else if (!company.equals(other.company)) return false; if (title == null) { if (other.title != null) return false; } else if (!title.equals(other.title)) return false; return true; } }
2.5.6 Basic Operations with Database
Let us look at the basic save operation and how Morphia can be used to save the products in a datastore. The sample code is shown below:
MongoDB Operations
package org.javacodegeeks.morphia; import static dev.morphia.aggregation.Group.grouping; import static dev.morphia.aggregation.Group.push; import java.util.Iterator; import java.util.List; import org.bson.types.ObjectId; import org.javacodegeeks.morphia.domain.Writer; import org.javacodegeeks.morphia.domain.Product; import org.javacodegeeks.morphia.domain.Company; import com.mongodb.MongoClient; import dev.morphia.Datastore; import dev.morphia.Morphia; import dev.morphia.query.Query; import dev.morphia.query.UpdateOperations; public class MorphiaExample { private static Datastore datastore; private static ObjectId id = new ObjectId(); public static void main(String[] args) { Morphia morphia = new Morphia(); morphia.mapPackage("org.javacodegeeks.morphia"); datastore = morphia.createDatastore(new MongoClient(), "productshop"); datastore.ensureIndexes(); Company company = new Company(id, "Apress"); Product product = new Product("23186", "Learning Java 8", "Tom Smith", 4.65, company); Product companionProduct = new Product("2975103", "Rust Companion", "Mike Douglas", 2.45, company); product.addCompanionProducts(companionProduct); datastore.save(companionProduct); datastore.save(product); List products = datastore.createQuery(Product.class) .field("title") .contains("Learning Java 8") .find() .toList(); System.out.println(products.size()); System.out.println(products.get(0)); company = new Company(id, "Oreilly"); product = new Product("231787", "Learning Java 9", "George Smith", 9.98, company); datastore.save(product); Query query = datastore.createQuery(Product.class) .field("title") .contains("Learning Java 9"); UpdateOperations updates = datastore.createUpdateOperations(Product.class) .inc("price", 1); datastore.update(query, updates); products = datastore.createQuery(Product.class) .field("title") .contains("Learning Java 9") .find() .toList(); System.out.println( products.get(0).getCost()); company = new Company(id, "Manning"); product = new Product("24176", "Learning Go", "John Smith", 6.43, company); datastore.save(product); query = datastore.createQuery(Product.class) .field("title") .contains("Learning Go"); datastore.delete(query); products = datastore.createQuery(Product.class) .field("title") .contains("Learning Go") .find() .toList(); System.out.println( products.size()); company = new Company(id, "Hatcher"); datastore.save(new Product("9781565927186", "Learning Dart", "Tom Smith", 3.21, company)); datastore.save(new Product("9781449313142", "Learning Adobe", "Mark Smith", 6.32, company)); datastore.save(new Product("9787564100476", "Learning Python 3", "Sandy Beger", 9.81, company)); datastore.save(new Product("9781449368814", "Learning Scala 6", "Mark Sawyer", 8.72, company)); datastore.save(new Product("9781784392338", "Learning Go 3", "John Sawyer", 6.43, company)); Iterator authors = datastore.createAggregation(Product.class) .group("author", grouping("products", push("title"))) .out(Writer.class); System.out.println(authors.hasNext()); company = new Company(id, "Macmillan"); product = new Product("654321", "Learning C++", "Kerngie Richhie", 4.53, company); datastore.save(product); products = datastore.createQuery(Product.class) .field("title") .contains("Learning C++") .project("title", true) .find() .toList(); System.out.println(products.size()); System.out.println( products.get(0) .getTitle()); System.out.println(products.get(0) .getWriter()); } }
2.5.7 Aggregation
Aggregation is another operation that Morphia supports. Aggregation is a set of sequential operations in a pipeline to create aggregated output. You can use group operation as shown below in the sample code.
Aggregation
company = new Company(id, "Hatcher"); datastore.save(new Product("9781565927186", "Learning Dart", "Tom Smith", 3.21, company)); datastore.save(new Product("9781449313142", "Learning Adobe", "Mark Smith", 6.32, company)); datastore.save(new Product("9787564100476", "Learning Python 3", "Sandy Beger", 9.81, company)); datastore.save(new Product("9781449368814", "Learning Scala 6", "Mark Sawyer", 8.72, company)); datastore.save(new Product("9781784392338", "Learning Go 3", "John Sawyer", 6.43, company)); Iterator authors = datastore.createAggregation(Product.class) .group("author", grouping("products", push("title"))) .out(Writer.class); System.out.println(authors.hasNext());
2.5.8 Projection
You can use projection to pick only a set of fields from the entity properties to query the MongoDB database. The sample code is shown below.
Projection
company = new Company(id, "Macmillan"); product = new Product("654321", "Learning C++", "Kerngie Richhie", 4.53, company); datastore.save(product); products = datastore.createQuery(Product.class) .field("title") .contains("Learning C++") .project("title", true) .find() .toList(); System.out.println(products.size()); System.out.println( products.get(0) .getTitle()); System.out.println(products.get(0) .getWriter());
2.5.9 Schema Validation
You can use Morphia for data validation rules of a collection. This can be done while data is being updated or inserted.
Schema Validation
@Entity("Products") @Indexes({ @Index(fields = @Field("title"), options = @IndexOptions(name = "product_title")) }) @Validation("{ price : { $gt : 0 } }") public class Product { @Id private String isbn; @Property private String title; private String writer; @Embedded private Company company; @Property("price") private double cost; @Reference private Set companionProducts; @Property private LocalDateTime publishDate; public Product() { }
3. Download the Source Code
You can download the full source code of this example here: Morphia – Java ODM for MongoDB