Reputation: 2364
I would like to map a Java Map, where all key values are stored in the same table. Something similar to https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Example_of_a_map_key_column_relationship_database but with the key being an object and not a simple type.
Say I have an Entity "User"
@Entity
public class User{
@Id
private String userId;
@OneToMany
@MapKeyClass(CalenderWeek.class)
private Map<CalenderWeek, WorkedTime> workedTimeMap;
The key CalendarWeek would be something like this
@Embeddable
public class CalenderWeek {
int year;
Month month; // Month is the enum java.time.Month
The WorkedTime would be something like
@Embeddable
public class WorkedTime {
private long workedHours;
The corresponding worked time table should be like this
worked_time
user_id | year | month | worked_hours
---------|------|-------| ---
1 | 2017 | 11 | 42
Is it possible to get that or do I have to do it as described here https://en.wikibooks.org/wiki/Java_Persistence/Relationships#Example_of_a_map_key_class_embedded_relationship_annotation
i.e., with three tables.
Upvotes: 1
Views: 1623
Reputation: 2364
The answer of Maciej Kowalski was very useful and correct. Thank you.
I just want to complete here the answer and extend it a little as I used xml configuration at the end. The Annotated version ended like that
@Entity
public class UserWorklogAggregate {
@Id
private String userId;
@ElementCollection
@CollectionTable(
name = "worked_time",
joinColumns = @JoinColumn(name = "user_id")
)
@AttributeOverrides({
@AttributeOverride(name = "key.year", column = @Column(name = "year")),
@AttributeOverride(name = "key.week", column = @Column(name = "week")),
@AttributeOverride(name = "value.workedDuration", column = @Column(name = "worked_duration"))
})
private final Map<CalenderWeek, WorkedTime> workedTimeMap = new HashMap<>();
and the xml version
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping default-access="field" default-cascade="all">
<class name="org.whatever.UserWorklogAggregate" table="user_worklog_aggregate">
<id name="userId" length="10" column="user_id" />
<map name="workedTimeMap" table="worked_time" >
<key column="user_id" />
<composite-map-key class="org.whatever.CalenderWeek">
<key-property name="year" column="year"/>
<key-property name="week" column="week"/>
</composite-map-key>
<composite-element class="org.whatever.WorkedTime" >
<property name="workedDuration" column="worked_duration"/>
</composite-element>
</map>
</class>
Upvotes: 1
Reputation: 26572
In general if you want to have a Map u simply use @ElementCollection
annotation and if you want to override some of the column or associations from the Embeddables then you use @AttributeOverride
/ @AssociationOverride
:
@ElementCollection
@AttributeOverrides({
@AttributeOverride(name="key.year",
column=@Column(name="YEAR1")),
@AttributeOverride(name="value.workedHours",
column=@Column(name="WORKED_H"))
})
private Map<CalenderWeek, WorkedTime> workedTimeMap;
Depending whether you want to override the key or value attribute, you add those prefixes respectively for persistence provider to be able to recodgnize the difference.
Upvotes: 2
Reputation: 876
I assume you want to store working hours of a user per month+year. Looking at your result table I would recommend not using Embeddable. You could try this approach:
@Entity
public class User{
@Id
private String id;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<WorkedTime> workedTime;
plus the back reference:
@Entity
public class WorkedTime {
@Id
private long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
private int year;
private Month month;
private long workedHours;
This would result in having only two tables.
Upvotes: 0