Search code examples
javajpaderby

Persisting Map<Entity, Double>: having BLOB instead of String key in join table


I am developing a calories calculator. I have entities Dish (edible item consisting of ingredients or other dishes) and Ingredient (atomic edible, e.g. "water" of "potatoes"). Both extends class AbstractEdible which implements interface Edible.

Dish contains field recipe of type Map<Edible, Double>, where Double represents weight of each "dish element" (ingredient or other dish).

I'm trying to persist all this stuff using JPA and Derby. I expect to have a join table with following fields:

  1. Key of dish entity
  2. DOUBLE value representing weight of "dish element"
  3. Key of "dish element" entity

Here is what I have in reality:

enter image description here

Instead of VARCHAR key to "dish element" entity I have a BLOB and I can't figure out why. Because of that I'm having problems to update existing Dish (getting ERROR 42818: Comparisons between 'BLOB' and 'BLOB' are not supported). I will really appreaciate your help!

Dish

package com.singularityfx.kcalibri2.model.edibles;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Transient;
import javax.persistence.JoinColumn;

@Entity
public class Dish extends AbstractEdible implements EdiblesCollection {
    @Transient
    private static final long serialVersionUID = -5646610412222252829L;

    @ElementCollection
    @CollectionTable(name="dish_recipe")
    @Column(name="weight")
    @MapKeyJoinColumn(name="ingredient_or_dish", referencedColumnName="name")
    private Map<Edible, Double> recipe = new HashMap<Edible, Double>();

    @Transient
    private KCalculator kCalculator = new KCalculator();

    public Dish() {}

    public Dish(String name, DishCategory category) {
        this.name = name;
        this.category = category;
    }

    @Override
    public double getKCalNonDessert() {
        kCalculator.init(recipe);
        return kCalculator.getkCalNonDessertPer100g();
    }

    @Override
    public double getKCalDessert() {
        kCalculator.init(recipe);
        return kCalculator.getkCalDessertPer100g();
    }

    @Override
    public double getKCal() {
        kCalculator.init(recipe);
        return kCalculator.getkCalPer100g();
    }

    @Override
    public double getKCalDessertTotal() {
        kCalculator.init(recipe);
        return kCalculator.getTotalKCalDessert();
    }

    @Override
    public double getKCalNonDessertTotal() {
        kCalculator.init(recipe);
        return kCalculator.getTotalKCalNonDessert();
    }

    @Override
    public double getKCalTotal() {
        kCalculator.init(recipe);
        return kCalculator.getTotalKCal();
    }

    public Double addIngredient(Edible edible, Double weight) {
        return recipe.put(edible, weight);
    }

    public Double removeIngredient(Edible edible) {
        return recipe.remove(edible);
    }

    @Override
    public Map<Edible, Double> getContent() {
        return recipe;
    }

    @Override
    public void setContent(Map<Edible, Double> content) {
        this.recipe = content;
    }

    @Override
    public void clearContent() {
        this.recipe.clear();
    }

    public double getWeight(Edible edible) {
        return recipe.get(edible);
    }

    public Collection<Edible> getEdibles() {
        return recipe.keySet();
    }

}

Ingredient

package com.singularityfx.kcalibri2.model.edibles;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.NamedQuery;
import javax.persistence.Transient;

@Entity
public class Ingredient extends AbstractEdible {
    @Transient
    private static final long serialVersionUID = -7669934586986624995L;
    private double kCal;
    private boolean isDessert;

    public Ingredient() {}

    public Ingredient(String name, IngredientCategory category, 
            double kCal, boolean isDessert) {
        this.name = name;
        this.kCal = kCal;
        this.category = category;
        this.isDessert = isDessert;
    }

    @Override
    public double getKCalNonDessert() {
        return isDessert ? 0 : kCal;
    }

    @Override
    public double getKCalDessert() {
        return isDessert ? kCal : 0;
    }
}

AbstractEdible

package com.singularityfx.kcalibri2.model.edibles;

import java.io.Serializable;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;

@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class AbstractEdible implements Edible, Serializable {

    private static final long serialVersionUID = 8684184950268663225L;
    @Id
    protected String name;
    @OneToOne()
    @JoinColumn(name="NAME_OF_CATEGORY")
    protected EdibleCategory category;

    @Override
    public String getName() {
        return name;
    }

    @Override
    public EdibleCategory getCategory() {
        return category;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setCategory(EdibleCategory category) {
        this.category = category;
    }

    @Override
    public String toString() {
        return name + " [" + category + "]";
    }

    @Override
    public int compareTo(Edible c) {
        return name.compareTo(c.getName());
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((category == null) ? 0 : category.hashCode());
        result = prime * result + ((name == null) ? 0 : name.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;
        AbstractEdible other = (AbstractEdible) obj;
        if (category == null) {
            if (other.category != null)
                return false;
        } else if (!category.equals(other.category))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
}

Edible

package com.singularityfx.kcalibri2.model.edibles;

import javax.persistence.Entity;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;

public interface Edible extends Comparable<Edible> {
    public double getKCalNonDessert();
    public double getKCalDessert();
    public String getName();
    public EdibleCategory getCategory();
}

Solution

  • My mistake was here:

    private Map<Edible, Double> recipe
    

    Edible (interface implemented by my entities) is not an entity itself, therefore persistence provider doesn't recognize map key as entity.

    When I refer to abstract class (which is annotaded with @Entity) instead of interface everything works as expected:

    private Map<AbstractEdible, Double> recipe