Product Catalog
CRUD, Indexing, Embedded Documents, Caching
What Morphium Offers
Morphium maps Java objects to MongoDB documents with annotations. @Entity, @Id,
@Cache, @Index — four annotations turn a POJO into a cached, indexed document.
Embedded sub-documents, field name mapping and write safety are all declarative.
The Challenge
Traditional ODMs require XML mapping files or manual codec registries. The MongoDB Java driver gives you raw BSON documents with no type safety. Morphium eliminates both problems with a pure-annotation approach that works at the field level.
Morphium Features Used
Prerequisites & Key Concepts
- Lombok
@FieldNameConstantsgenerates an inner classProduct.Fieldswith constants likeFields.name,Fields.price. These are used in Morphium queries instead of hard-coded strings:query.f(Product.Fields.name). MorphiumIdis Morphium's own ObjectId type, compatible with MongoDB's native ObjectId. Import:de.caluga.morphium.driver.MorphiumId.@Embeddedgoes on the sub-document class (e.g.Category), NOT on the field in the parent. An@Embeddedclass must NOT also have@Entity.@Cacherequires the Morphium cache to be enabled. In Quarkus:quarkus.morphium.cache.read-cache-enabled=true(default). The cache is auto-invalidated on writes through the same Morphium instance.- Compound Index
@Indexon the class creates a compound index — optimal for price-range queries sorted by name.
Entity Relationship
Entity Source Code
import de.caluga.morphium.annotations.Entity; import de.caluga.morphium.annotations.Id; import de.caluga.morphium.annotations.Index; import de.caluga.morphium.annotations.Property; import de.caluga.morphium.annotations.caching.Cache; import de.caluga.morphium.annotations.caching.Cache.ClearStrategy; import de.caluga.morphium.annotations.WriteSafety; import de.caluga.morphium.annotations.SafetyLevel; import de.caluga.morphium.annotations.DefaultReadPreference; import de.caluga.morphium.annotations.ReadPreferenceLevel; import de.caluga.morphium.driver.MorphiumId; import lombok.Data; import lombok.experimental.FieldNameConstants; import java.util.List; @Entity(collectionName = "products") 1 @Index("-price, name") 2 @Cache(maxEntries = 100, strategy = ClearStrategy.LRU, timeout = 30000) 3 @WriteSafety(level = SafetyLevel.NORMAL) 4 @DefaultReadPreference(ReadPreferenceLevel.PRIMARY) 5 @Data @FieldNameConstants public class Product { @Id 6 private MorphiumId id; @Index private String name; @Property(fieldName = "product_description") 7 private String description; @Index private double price; private int stock; private Category category; 8 private List<String> tags; }
products MongoDB collection_id. Auto-generated on first store()description → MongoDB product_descriptionimport de.caluga.morphium.annotations.Embedded; import lombok.Data; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @Embedded 1 @Data @NoArgsConstructor @AllArgsConstructor public class Category { private String name; private String description; }
@EntityService Code
CRUD Operations
import de.caluga.morphium.Morphium; @Inject Morphium morphium; // Store (insert or upsert) a product morphium.store(product); // Bulk insert morphium.storeList(products); // Delete a single entity morphium.delete(product); // Find by ID morphium.findById(Product.class, id);
Query Examples
// Find all products morphium.createQueryFor(Product.class).asList(); // Search by name (regex, case-insensitive) morphium.createQueryFor(Product.class) .f(Product.Fields.name).matches("(?i)laptop") .asList(); // Price range query with sort morphium.createQueryFor(Product.class) .f(Product.Fields.price).gte(min) .f(Product.Fields.price).lte(max) .sort(Map.of(Product.Fields.price, 1)) .asList(); // Query embedded sub-document fields morphium.createQueryFor(Product.class) .f("category.name").eq("Electronics") .asList(); // Distinct values morphium.createQueryFor(Product.class) .distinct(Product.Fields.name);
Bonus: MorphiumId JSON Serialization over REST
The id field above is typed MorphiumId — Morphium's own ObjectId
implementation, not a plain String. Without special handling, a generic JSON mapper
(Jackson or JSON-B) would introspect its bean getters and serialize it as a nested struct:
{ "id": { "pid": 12345, "counter": 1, "machineId": 987, "bytes": "...", "time": 1700000000000 }, "name": "Laptop Pro 15" }quarkus-morphium 6.3.1 registers an automatic Jackson module
(MorphiumIdJacksonModule) and, if quarkus-jsonb is on the classpath instead
of/alongside Jackson, a JSON-B adapter (MorphiumIdJsonbModule /
MorphiumIdJsonbAdapter) that (de)serialize every @Id MorphiumId field as a
plain 24-character hex string — no code to write, no annotation to add:
{ "id": "68a1c2f3e4d5a6b7c8d9e0f1", "name": "Laptop Pro 15" }This showcase demonstrates it end-to-end with a small, dedicated JSON REST resource
(de.caluga.morphium.showcase.api.MorphiumIdJsonResource, reusing this same
Product entity unmodified) — try it live:
# list -> every id is a flat hex string curl -s localhost:8080/api/morphium-id-json/products | jq '.[0].id' # echo -> hex string in the body round-trips through a real MorphiumId curl -s -X POST localhost:8080/api/morphium-id-json/products/echo \ -H 'Content-Type: application/json' \ -d '{"id":"507f1f77bcf86cd799439011","name":"Echo","price":1.0,"stock":1}' | jq .
No @JsonSerialize/@JsonDeserialize annotation and no hand-written
(de)serializer for MorphiumId exists anywhere in this application — the extension
handles both Jackson and JSON-B automatically, for every entity, everywhere it's used.
Related Documentation
- Developer Guide — Object Mapping, @Entity, @Embedded
- Caching Examples — LRU Cache, Cache Invalidation
- API Reference — store(), Query, delete()
- Field Names — @Property, @FieldNameConstants