Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA Map w/ Entity Key and Entity Value

Key Entity:

@Entity public class KeyEntity
{
    @Id @GeneratedValue(strategy = GenerationType.TABLE)
    public Long id;

    public String handle;

    public boolean equals(Object o) {
        KeyEntity oke = (KeyEntity) o;
        return handle != null ? handle.equals(oke.handle) : oke.handle == null;
    }

    public int hashCode() {
        return handle != null ? handle.hashCode() : 0;
    }
}

Value Entity:

@Entity public class ValueEntity
{
    @Id @GeneratedValue(strategy = GenerationType.TABLE)
    public Long id;

    @ManyToOne
    public KeyEntity key;

    public String value;

    public boolean equals(Object o) {
        ValueEntity ove = (ValueEntity) o;
        return key != null ? key.equals(ove.key) : ove.key == null;
    }

    public int hashCode() {
        return key != null ? key.hashCode() : 0;
    }
}

Container Entity:

@Entity public class ContainerEntity
{
    @Id @GeneratedValue(strategy = GenerationType.TABLE)
    public Long id;

    @OneToMany @MapKey(name = "key")
    public Map<KeyEntity, ValueEntity> values = new HashMap<KeyEntity, ValueEntity>();
}

Main:

KeyEntity k1 = new KeyEntity();
k1.handle = "k1";
em.persist(k1);

KeyEntity k2 = new KeyEntity();
k2.handle = "k2";
em.persist(k2);

ValueEntity v1 = new ValueEntity();
v1.key = k1;
v1.value = "v1";
em.persist(v1);

ValueEntity v2 = new ValueEntity();
v2.key = k2;
v2.value = "v2";
em.persist(v2);

ContainerEntity ce = new ContainerEntity();
ce.values.put(k1, v1);
ce.values.put(k2, v2);
em.persist(ce);

// display number of values
System.out.println(ce.values.size());

// create new transaction
em.getTransaction().commit();
em.close();
em = emf.createEntityManager();
em.getTransaction().begin();

// find our container and inspect the number of values
ce = em.find(ContainerEntity.class, ce.id);
System.out.println(ce.values.size());

If I add several key-value pairs to a ContainerEntity and then reload said container, only one key-value pair will be present. If you look at the output of running the above main function, first '2' is printed, followed by '1'.

I can see that this is because of KeyEntity.hashCode - when inserting into the HashMap KeyEntity.handle is null, so all pairs will have the same hash code. KeyEntity.id is populated at this point - if I base the hash code off of this field everything works out. Also if I change the key to be a String it is loaded in time for the calls to hashCode.

How can I change my mappings in ContainerEntity so that KeyEntity.handle is loaded when it is placed inside the map, so hashCode can use it?

like image 210
rhollencamp Avatar asked Jan 04 '12 20:01

rhollencamp


1 Answers

See here and here:

... As Mike points out, @MapKey was only intended by the spec for the case Map<Basic, Entity>, not for Map<Basic, Embeddable>. The correct annotation for embeddable values would be.

I.E, it is intended that the key should be a simple basic type (which means not an entity or embeddable) and the value an entity. If the value is a basic type, @ElementCollection would do.

However, what you need is that the key is an entity type, and in this case, you are out of luck.

As you already said, if you change the the key to String, everything is fine, so I will suggest you to do this.

like image 187
Victor Stafusa Avatar answered Nov 09 '22 00:11

Victor Stafusa