ORM for NoSQL (part II)

There are plenty of sites explaining the virtues and advantages of normalization versus denormalization of data in the design of a NoSQL database. So there’s no need for me to “cut ‘n’ paste” them here.

However, I chose to denormalize my MySQL database when migrating  it to MongoDB mainly for the following two reasons (from AWS: Should Your DynamoDB Table Be Normalized or Denormalized?):

  • You need to store small items, with few attributes.  For reads, the item size should not exceed 4 KB.  (A read capacity unit (RCU) is defined as one read per second, for an item up to 4 KB in size.)  For writes, the item size should not exceed 1 KB (the size of one WCU).
  • Your applications need to read and write data in a high-traffic environment, without being concerned about consistency and synchronization of data across multiple tables.

The following JavaScript creates my MongoDB collection and inserts one document.

collectionFoodsCosmeticsMedicines.insertOne(
    {
        // _id MongoDB default PK.
        //_id: "",
        "ean": "05052319711639",
        "gtin": "05052319711639",
        "tpnb": "051627961",
        "tpnc": "258290127",
        "description": "Tesco Everyday Valuebaked Beans In Tomato Sauce 420G",
        "brand": "TESCO VALUE",
        "supermarket": "Tesco",

        "product_name": "product_name",
        "quantity_grammes": "420.0",
        "portion_serving_sachet_item_amount": "",
        "live": "yes",
        "timestamp_lastupdated": lastupdated,

        "qtyContents": [
            {
                "quantity": "420.0",
                "totalQuantity": "420.0",
                "quantityUom": "g",
                "netContents": "420g e",
                "avgMeasure": ""
            }
        ],

        "productCharacteristics": {
            "isFood": "yes",
            "isDrink": "no",
            "healthScore": "10",
            "isHazardous": "no",
            "storageType": "dry"
        },


        // Arrays of data
        "ingredients": [
            "Haricot Beans (44%) (Water, Haricot Beans)",
            "Tomato Puree (27%) (Water, Tomato Purée)",
            "Water",
            "Sugar",
            "Glucose-Fructose Syrup",
            "Modified Maize Starch",
            "Salt",
            "Onion Powder",
            "Paprika",
            "Maltodextrin",
            "Spice Extracts (Paprika Extract, Clove Extract, Capsicum Extract)",
            "Flavouring"
        ],

        "calcNutrition": {
            "per100Header": "100g contains",
            "perServingHeader": "Half of a can (210g) contains",

            calcNutrients: [
                {
                    "name": "Energy (kJ)",
                    "valuePer100": "364.8",
                    "valuePerServing": "766.08"
                },
                {
                    "name": "Energy (kcal)",
                    "valuePer100": "364.80kj86.5",
                    "valuePerServing": "766.08kj181.65"
                },
                {
                    "name": "Fat (g)",
                    "valuePer100": ".5",
                    "valuePerServing": "1.05"
                },
                {
                    "name": "Saturates (g)",
                    "valuePer100": ".14",
                    "valuePerServing": ".29"
                },
                {
                    "name": "Carbohydrate (g)",
                    "valuePer100": "14.6",
                    "valuePerServing": "30.66"
                },
                {
                    "name": "Sugars (g)",
                    "valuePer100": "4.5",
                    "valuePerServing": "9.45"
                },
                {
                    "name": "Fibre (g)",
                    "valuePer100": "4.4",
                    "valuePerServing": "9.24"
                },
                {
                    "name": "Protein (g)",
                    "valuePer100": "3.7",
                    "valuePerServing": "7.77"
                },
                {
                    "name": "Salt (g)",
                    "valuePer100": ".55",
                    "valuePerServing": "1.15"
                }
            ]
        },

        // Arrays of data
        "nutrients": {
            "nutrient_name": "d"
        },

        // Arrays of data
        "e_numbers": {
            "e_number_name": "e"
        },

        // Arrays of data
        "chemicals_minerals_vitamins": {
            "chemical_mineral_vitamin_name": "f"
        },

        //
        "lifestyle": {
            "name": "Lifestyle",
            "value": "Suitable for Vegetarians"
        }
    }
);

Using MongoDB Compass I can see my Collection ‘foodsCosmeticsMedicines’ is < 4K:

I create the Entity Bean to represent the ‘foodsCosmeticsMedicines’ MongoDB collection:

import com.google.gson.annotations.Expose;
import org.hibernate.annotations.Type;
import org.hibernate.search.annotations.Indexed;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;

@Entity(name = "FoodsCosmeticsMedicines")
@Indexed
@Table(name = "foodsCosmeticsMedicines")
public class FoodsCosmeticsMedicines implements Serializable {

    // Arrays of Objects
    @Expose(deserialize = true, serialize = true)
    @Embedded
    ProductCharacteristics productCharacteristics;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    CalcNutrition calcNutrition;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Nutrients nutrients;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Enumbers enumbers;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    ChemicalsMineralsVitamins chemicalsMineralsVitamins;
    @Expose(deserialize = true, serialize = true)
    @Embedded
    Lifestyle lifestyle;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Type(type = "objectid")
    private String id;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "ean")
    private String ean;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "gtin")
    private String gtin;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "tpnb")
    private String tpnb;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "tpnc")
    private String tpnc;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "description")
    private String description;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "brand")
    private String brand;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "supermarket")
    private String supermarket;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "product_name")
    private String product_name;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "quantity_grammes")
    private String quantity_grammes;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "portion_serving_sachet_item_amount")
    private String portion_serving_sachet_item_amount;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @Column(name = "timestamp_lastupdated")
    private Date timestamp_lastupdated;

    /**
     *
     */
    @Expose(deserialize = true, serialize = true)
    @ElementCollection
    private List<QtyContents> qtyContents = new ArrayList<QtyContents>();
    @Expose(deserialize = true, serialize = true)
    @ElementCollection
    private List<Ingredients> ingredients = new ArrayList<Ingredients>();

    /**
     *
     */
    public FoodsCosmeticsMedicines() {
    }

    /**
     *
     * @param id String
     */
    public FoodsCosmeticsMedicines(String id) {
        this.id = id;
    }

    /**
     *
     * @return String
     */
    public String getId() {
        return this.id;
    }

    /**
     *
     * @param id String
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     *
     * @return String
     */
    public String getEan() {
        return this.ean;
    }

    /**
     *
     * @param ean String
     */
    public void setEan(String ean) {
        this.ean = ean;
    }

    /**
     *
     * @return String
     */
    public String getGtin() {
        return this.gtin;
    }

    /**
     *
     * @param gtin String
     */
    public void setGtin(String gtin) {
        this.gtin = gtin;
    }

    /**
     *
     * @return String
     */
    public String getTpnb() {
        return this.tpnb;
    }

    /**
     *
     * @param tpnb String
     */
    public void setTpnb(String tpnb) {
        this.tpnb = tpnb;
    }

    /**
     *
     * @return String
     */
    public String getTpnc() {
        return this.tpnc;
    }

    /**
     *
     * @param tpnc String
     */
    public void setTpnc(String tpnc) {
        this.tpnc = tpnc;
    }

    /**
     *
     * @return String
     */
    public String getDescription() {
        return this.description;
    }

    /**
     *
     * @param description String
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     *
     * @return String
     */
    public String getBrand() {
        return this.brand;
    }

    /**
     *
     * @param brand String
     */
    public void setBrand(String brand) {
        this.brand = brand;
    }

    /**
     *
     * @return String
     */
    public String getSupermarket() {
        return this.supermarket;
    }

    /**
     *
     * @param supermarket String
     */
    public void setSupermarket(String supermarket) {
        this.supermarket = supermarket;
    }

    /**
     *
     * @return String
     */
    public String getProduct_name() {
        return this.product_name;
    }

    /**
     *
     * @param product_name String
     */
    public void setProduct_name(String product_name) {
        this.product_name = product_name;
    }

    /**
     *
     * @return String
     */
    public String getPortion_serving_sachet_item_amount() {
        return this.portion_serving_sachet_item_amount;
    }

    /**
     *
     * @param portion_serving_sachet_item_amount String
     */
    public void setPortion_serving_sachet_item_amount(String portion_serving_sachet_item_amount) {
        this.portion_serving_sachet_item_amount = portion_serving_sachet_item_amount;
    }

    /**
     *
     * @return Date
     */
    public Date getTimestamp_lastupdated() {
        return this.timestamp_lastupdated;
    }

    /**
     *
     * @param timestamp_lastupdated Date
     */
    public void setTimestamp_lastupdated(Date timestamp_lastupdated) {
        this.timestamp_lastupdated = timestamp_lastupdated;
    }

    /**
     *
     * @return String
     */
    public String getQuantity_grammes() {
        return this.quantity_grammes;
    }

    /**
     *
     * @param quantity_grammes String
     */
    public void setQuantity_grammes(String quantity_grammes) {
        this.quantity_grammes = quantity_grammes;
    }

    /**
     *
     * @return String
     */
    public ProductCharacteristics getProductCharacteristics() {
        return this.productCharacteristics;
    }

    /**
     *
     * @param productCharacteristics ProductCharacteristics
     */
    public void setProductCharacteristics(ProductCharacteristics productCharacteristics) {
        this.productCharacteristics = productCharacteristics;
    }

    /**
     *
     * @return Ingredients
     */
    public List<Ingredients> getIngredients() {
        return this.ingredients;
    }

    /**
     *
     * @param ingredients List<Ingredients>
     */
    public void setIngredients(List<Ingredients> ingredients) {
        this.ingredients = ingredients;
    }

    /**
     *
     * @return CalcNutrition
     */
    public CalcNutrition getCalcNutrition() {
        return this.calcNutrition;
    }

    /**
     *
     * @param calcNutrition CalcNutrition
     */
    public void setCalcNutrition(CalcNutrition calcNutrition) {
        this.calcNutrition = calcNutrition;
    }

    /**
     *
     * @return Nutrients
     */
    public Nutrients getNutrients() {
        return this.nutrients;
    }

    /**
     *
     * @param nutrients Nutrients
     */
    public void setNutrients(Nutrients nutrients) {
        this.nutrients = nutrients;
    }

    /**
     *
     * @return Enumbers
     */
    public Enumbers getEnumbers() {
        return this.enumbers;
    }

    /**
     *
     * @param enumbers Enumbers
     */
    public void  setEnumbers(Enumbers enumbers) {
        this.enumbers = enumbers;
    }

    /**
     *
     * @return ChemicalsMineralsVitamins
     */
    public ChemicalsMineralsVitamins getChemicalsMineralsVitamins() {
        return this.chemicalsMineralsVitamins;
    }

    /**
     *
     * @param chemicalsMineralsVitamins ChemicalsMineralsVitamins
     */
    public void setChemicalsMineralsVitamins(ChemicalsMineralsVitamins chemicalsMineralsVitamins) {
        this.chemicalsMineralsVitamins = chemicalsMineralsVitamins;
    }

    /**
     *
     * @return Lifestyle
     */
    public Lifestyle getLifestyle() {
        return this.lifestyle;
    }

    /**
     *
     * @param lifestyle Lifestyle
     */
    public void setLifestyle(Lifestyle lifestyle) {
        this.lifestyle = lifestyle;
    }

    /**
     *
     * @return List<QtyContents>
     */
    public List<QtyContents> getQtyContents() {
        return this.qtyContents;
    }

    /**
     *
     * @param qtyContents List<QtyContents>
     */
    public void setQtyContents(List<QtyContents> qtyContents) {
        this.qtyContents = qtyContents;
    }

    /**
     *
     * @param o Object
     * @return boolean
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof FoodsCosmeticsMedicines)) return false;
        FoodsCosmeticsMedicines that = (FoodsCosmeticsMedicines) o;
        return Objects.equals(this.getId(), that.getId());
    }

    /**
     *
     * @return int
     */
    @Override
    public int hashCode() {

        return Objects.hash(this.getId());
    }

    /**
     *
     * @return String
     */
    @Override
    public String toString() {
        return "FoodsCosmeticsMedicines{" +
                "productCharacteristics=" + productCharacteristics +
                ", calcNutrition=" + calcNutrition +
                ", nutrients=" + nutrients +
                ", enumbers=" + enumbers +
                ", chemicalsMineralsVitamins=" + chemicalsMineralsVitamins +
                ", lifestyle=" + lifestyle +
                ", id='" + id + '\'' +
                ", ean='" + ean + '\'' +
                ", gtin='" + gtin + '\'' +
                ", tpnb='" + tpnb + '\'' +
                ", tpnc='" + tpnc + '\'' +
                ", description='" + description + '\'' +
                ", brand='" + brand + '\'' +
                ", supermarket='" + supermarket + '\'' +
                ", product_name='" + product_name + '\'' +
                ", quantity_grammes='" + quantity_grammes + '\'' +
                ", portion_serving_sachet_item_amount='" + portion_serving_sachet_item_amount + '\'' +
                ", timestamp_lastupdated=" + timestamp_lastupdated +
                ", qtyContents=" + qtyContents +
                ", ingredients=" + ingredients +
                '}';
    }
}

The following method in my EJB (3.2) queries the MongoDB collection ‘FoodsCosmeticsMedicines’

/**
 * @return List<FoodsCosmeticsMedicines>
 */
public List<FoodsCosmeticsMedicines> getAllFoodsCosmeticsMedicines() {
    String json = null;

    Query query = mongoDBEntityManager.createQuery("FROM FoodsCosmeticsMedicines");
    List<com.notifywell.entity.FoodsCosmeticsMedicines> foodsCosmeticsMedicinesList = query.getResultList();
    logger.info(">>>>> getAllFoodsCosmeticsMedicines foodsCosmeticsMedicinesList = {}", foodsCosmeticsMedicinesList.size());

    for (com.notifywell.entity.FoodsCosmeticsMedicines foodsCosmeticsMedicines : foodsCosmeticsMedicinesList) {
        logger.info(">>>>> foodsCosmeticsMedicines id = {}", foodsCosmeticsMedicines.getId());
        logger.info(">>>>> foodsCosmeticsMedicines ean = {}", foodsCosmeticsMedicines.getEan());
        logger.info(">>>>> foodsCosmeticsMedicines description = {}", foodsCosmeticsMedicines.getDescription());

        for (com.notifywell.entity.Ingredients ingredients : foodsCosmeticsMedicines.getIngredients()) {
            logger.info(">>>>> foodsCosmeticsMedicines ingredients = {}", ingredients.getIngredientName());
        }
    }

    return foodsCosmeticsMedicinesList;
}

When I call my method I see in the console all the products (1) with a Bar Code of ‘05052319711639′ and the 9 ingredients. 

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> getAllFoodsCosmeticsMedicines foodsCosmeticsMedicinesList = 1

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines id = 5ae7fc9ec0a118606e42667e

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ean = 05052319711639

06:35:38,357 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines description = Tesco Everyday Valuebaked Beans In Tomato Sauce 420G

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Haricot Beans (44%) (Water, Haricot Beans)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Tomato Puree (27%) (Water, Tomato Purée)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Water

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Sugar

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Glucose-Fructose Syrup

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Modified Maize Starch

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Salt

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Onion Powder

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Paprika

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Maltodextrin

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Spice Extracts (Paprika Extract, Clove Extract, Capsicum Extract)

06:35:38,358 INFO  [com.notifywell.ejb.FoodsCosmeticsMedicinesEJB] (default task-6) >>>>> foodsCosmeticsMedicines ingredients = Flavouring

The above example shows how easy it is to add JPA ORM to a NoSQL database.

See the following links for installing Hibernate OGM.

Hibernate OGM FAQ and the Hibernate OGM 5.3.1.Final: Reference Guide.

 

Leave a Reply

Your e-mail address will not be published. Required fields are marked *