Advanced Query Builder
All Query Operators, Pagination, Sorting, @Aliases, @Transient
What Morphium Offers
Morphium's fluent Query<T> API covers every MongoDB query operator — from
basic comparisons and regex to text search, array operators and projections — with type-safe
field references. Pagination, sorting and cursor-based iteration are built in.
The Challenge
Most ODMs are either too raw (error-prone string-based queries) or too abstract (limiting what you can express). Morphium's Query API gives you the full power of MongoDB's query language with compile-time field safety.
Morphium Features Used
Prerequisites & Key Concepts
@Aliasesfor field name migration — if a field was previously stored under a different name (e.g.,"mail"or"e_mail"), Morphium will still map those legacy field names to the current Java field when reading. Writes always use the current name, so documents naturally migrate over time.@Transientfields are computed in@PostLoad— thefullNamefield is never stored in MongoDB. Instead, the@PostLoadcallback recomputes it fromfirstNameandlastNameevery time the entity is loaded. The class must be annotated with@Lifecyclefor this to work.@IgnoreNullFromDBkeeps Java defaults when DB field is null — theskillslist uses this annotation so that if a document lacks askillsfield, the Java field keeps its default value instead of being set tonull. This preventsNullPointerExceptions.- Compound Index
@Index({"lastName, firstName"})— declared at class level, creates a compound index that accelerates queries filtering or sorting bylastNameand/orfirstName.
Entity Source Code
Employee.java
Java
import de.caluga.morphium.annotations.Aliases; import de.caluga.morphium.annotations.Entity; import de.caluga.morphium.annotations.Id; import de.caluga.morphium.annotations.IgnoreNullFromDB; import de.caluga.morphium.annotations.Index; import de.caluga.morphium.annotations.Transient; import de.caluga.morphium.annotations.lifecycle.Lifecycle; import de.caluga.morphium.annotations.lifecycle.PostLoad; import de.caluga.morphium.driver.MorphiumId; import lombok.experimental.FieldNameConstants; @Entity(collectionName = "employees") @Index({"lastName, firstName"}) // compound index1 @Lifecycle // enables @PostLoad, @PreStore, etc.2 @Data @NoArgsConstructor @AllArgsConstructor @Builder @FieldNameConstants3 public class Employee { @Id private MorphiumId id; private String firstName; private String lastName; @Aliases({"mail", "e_mail"}) // reads legacy field names4 private String email; @Index // single-field index for department queries private String department; private String position; private double salary; private LocalDateTime hireDate; @Transient // NOT stored in MongoDB5 private transient String fullName; @IgnoreNullFromDB // keeps Java default if DB field is null6 private List<String> skills; private boolean active; @PostLoad // called after deserialization from MongoDB7 public void onPostLoad() { this.fullName = firstName + " " + lastName; } }
1 Class-level
@Index with multiple fields creates a compound index — accelerates queries filtering or sorting by lastName and firstName together.2
@Lifecycle must be present for Morphium to scan and invoke lifecycle callbacks like @PostLoad on this class.3
@FieldNameConstants generates Employee.Fields.salary etc. for type-safe field references in queries.4
@Aliases maps legacy field names from older documents to this Java field on read — writes always use the current name email.5
@Transient excludes this field from MongoDB persistence; it exists only in Java memory and is populated by the @PostLoad callback.6
@IgnoreNullFromDB prevents Morphium from overwriting the Java field with null when the field is absent in the MongoDB document.7
@PostLoad is called after deserialization — ideal for computing derived fields like fullName that are not stored in the database.Query Operations Reference
Morphium API — All Query Operators
Java
import de.caluga.morphium.Morphium; import de.caluga.morphium.query.Query; // Equality: f().eq(value) → MongoDB $eq morphium.createQueryFor(Employee.class) .f(Employee.Fields.department).eq("Engineering").asList();1 // Range: gte/lte → MongoDB $gte/$lte (inclusive bounds) morphium.createQueryFor(Employee.class) .f(Employee.Fields.salary).gte(60000)2 .f(Employee.Fields.salary).lte(100000) .sort(Map.of(Employee.Fields.salary, 1)).asList();3 // Regex: matches() → MongoDB $regex morphium.createQueryFor(Employee.class) .f(Employee.Fields.lastName).matches("^M").asList();4 // Membership: in(list) → MongoDB $in morphium.createQueryFor(Employee.class) .f(Employee.Fields.skills).in(List.of("Java", "Python")).asList();5 // Exclusion: nin(list) → MongoDB $nin morphium.createQueryFor(Employee.class) .f(Employee.Fields.department).nin(List.of("Sales", "HR")).asList(); // Existence: exists() → MongoDB $exists: true morphium.createQueryFor(Employee.class) .f(Employee.Fields.email).exists().asList();6 // Pagination: sort + skip + limit morphium.createQueryFor(Employee.class) .sort(Map.of(Employee.Fields.lastName, 1)) .skip(page * size).limit(size).asList();7 // Count: server-side count without data transfer morphium.createQueryFor(Employee.class) .f(Employee.Fields.department).eq("Engineering").countAll();8 // Distinct: unique values for a field, runs on server morphium.createQueryFor(Employee.class) .distinct(Employee.Fields.department);
1
.eq() translates to MongoDB's $eq — chain multiple .f().eq() calls to AND conditions together.2
.gte() and .lte() create inclusive range bounds; chain them on the same field for a bounded salary range.3
.sort() accepts a Map of field name to direction (1 = ascending, -1 = descending) for server-side ordering.4
.matches() maps to MongoDB's $regex — prefix-anchored patterns like ^M can leverage string indexes.5
.in() maps to $in — for array fields it matches documents where ANY element of the array is in the provided list.6
.exists() maps to $exists: true — finds documents where the field is present (Morphium omits null fields by default, so this also filters them out).7
.skip(n).limit(m) implements cursor-based pagination entirely on the MongoDB server without transferring unwanted documents.8
.countAll() runs a server-side count — no documents are transferred to the application, making it very efficient for large collections.Related Documentation
- API Reference — Query API: eq, gte, lte, matches, in, nin
- Developer Guide — @Aliases, @Transient, @FieldNameConstants
- Field Names — Type-safe Field References