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.
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.
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:
You'll see a sample of this implementation in my GitHub repo.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With