Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to annotate Map<Entity,INTEGER> with JPA?

I have this map:

Map<Owner, Integer> ownerSharesMap = new HashMap<>();

I know how to map a HashMap with @OneToMany relation that has a primitive type as key, but I googled a lot and didn't find a way to map the above HashMap with JPA. I see two options though:

to change my HashMap to this: (in which the uuid is the key column of Owner)

Map<UUID, Integer> ownerIdSharesMap = new HashMap<>();

or create a class like this :

public class Share{
  int count;
  Owner owner
}

then persist this Set:

Set<Share> shares;

what do you suggest? is there a way to annotate first Map with JPA or should I use the later solutions?

Note that in my project, querying performance is the main concern but I want clean OOD too.

Thanks.

like image 277
MoienGK Avatar asked Oct 21 '15 18:10

MoienGK


People also ask

How do you indicate entity in JPA?

The JPA specification requires the @Entity annotation. It identifies a class as an entity class. You can use the name attribute of the @Entity annotation to define the name of the entity. It has to be unique for the persistence unit, and you use it to reference the entity in your JPQL queries.


1 Answers

This is possible in JPA.

The values in your map collection is not an entity, therefore you need to map it using @ElementCollection.

Your mapping can be simple as this:

@ElementCollection
private Map<Owner, Integer> ownerSharesMap = new HashMap<Owner, Integer>();

Although, you can specify a collection table where the values will be stored. Using @CollectionTable annotation you can specify the name of the collection table as well as the join column.

@ElementCollection
@CollectionTable(name="OWNER_SHARES",
    joinColumns=@JoinColumn(name="SHARE_ID"))
private Map<Owner, Integer> ownerSharesMap = new HashMap<Owner, Integer>();

If @CollectionTable is not specified, the table name will default to the name of the referencing entity, appended with an underscore and the name of the entity attribute that contains the element collection. In our example, this will be SHARE_OWNERSHARESMAP. The join column default is similarly the name of the referencing entity, appended with an underscore and the name of the primary key column of the entity table.

You can use @Column annotation, to specify which column in the collection table where the integer values of the map collection table. If not specified, this will default to OWNERSHARESMAP.

@ElementCollection
@CollectionTable(name = "OWNER_SHARES", joinColumns = @JoinColumn(name = "SHARE_ID") )
@Column(name="SHARE_AMOUNT")
private Map<Owner, Integer> ownerSharesMap = new HashMap<Owner, Integer>();

When keying by entity, not all properties of the entity will be stored in the collection table. It is only the primary key of the entity that will be stored. For this we will have an additional column in our collection table to store the key of your map, that is a foreign key to the Owner entity's primary key. To override the name of this column, you can use @MapKeyJoinColumn.

@ElementCollection
@CollectionTable(name="OWNER_SHARES", joinColumns=@JoinColumn(name="SHARE_ID"))
@Column(name="SHARE_AMOUNT")
@MapKeyJoinColumn(name="OWNER_KEY")
private Map<Owner, Integer> ownerSharesMap = new HashMap<Owner, Integer>();

If @MapKeyJoinColumn is not specified, then the default column name of will be the name of element collection attribute, appended with the string "_KEY". So in our example, this will be OWNERSHARESMAP_KEY.

Here's a sample of how your entities will look like in code:

@Entity
@Table(name="OWNER")
public class Owner {

    @Id
    @Column(name="OWNER_ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Integer id;
...
}

@Entity
@Table(name = "SHARE")
public class Share {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "SHARE_ID")
    private Integer id;

    @ElementCollection
    @CollectionTable(name = "OWNER_SHARES", joinColumns = @JoinColumn(name = "SHARE_ID") )
    @Column(name="SHARE_AMOUNT")
    @MapKeyJoinColumn(name = "OWNER_KEY")
    private Map<Owner, Integer> ownerSharesMap = new HashMap<Owner, Integer>();
...
}

Here's a sample code persisting the entities and their collections:

    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();

    Owner owner1 = new Owner();
    Owner owner2 = new Owner();

    em.persist(owner1);
    em.persist(owner2);

    Share share = new Share();
    share.getOwnerSharesMap().put(owner1, 20);
    share.getOwnerSharesMap().put(owner2, 40);

    em.persist(share);

    em.getTransaction().commit();

Here's how Hibernate generated the schema in MySQL:

Hibernate: 
    create table OWNER (
        OWNER_ID integer not null auto_increment,
        primary key (OWNER_ID)
    )
Hibernate: 
    create table OWNER_SHARES (
        SHARE_ID integer not null,
        SHARE_AMOUNT integer,
        OWNER_KEY integer not null,
        primary key (SHARE_ID, OWNER_KEY)
    )
Hibernate: 
    create table SHARE (
        SHARE_ID integer not null auto_increment,
        primary key (SHARE_ID)
    )
Hibernate: 
    alter table OWNER_SHARES 
        add constraint FK_th03t34g0d8hj7hmhppaa9juk 
        foreign key (OWNER_KEY) 
        references OWNER (OWNER_ID)
Hibernate: 
    alter table OWNER_SHARES 
        add constraint FK_smwhicxpq0ydqan5jn1p3goom 
        foreign key (SHARE_ID) 
        references SHARE (SHARE_ID)

Here's how data will look like in your tables:

enter image description here

You'll see a sample of this implementation in my GitHub repo.

like image 115
Ish Avatar answered Oct 09 '22 21:10

Ish