31

Using annotations how do you map a field in an entity which is a "Map" (Hashtable) of String to a given object? The object is annotated and instances of it are already stored in the hibernate databse.

I've found the syntax for definging a map with a simple key and value as such:

<class name="Foo" table="foo">
    ...
    <map role="ages">
         <key column="id"/>
         <index column="name" type="string"/>
         <element column="age" type="string"/>
     </map>
 </class>

And oddly with an entity as the key and a simple type as the value like so:

<class name="Foo" table="foo">
    ...
  <map role="ages">
    <key column="id"/>
    <index-many-to-many column="person_id" 
         class="Person"/>
    <element column="age" type="string"/>
  </map>
</class>
<class name="Person" table="person">
    ...
    <property name="name" column="name" 
         type="string"/>
</class>

But I don't see how to do this for a simple key to element mapping, and I don't see how to do this using annotations.

Aritz
  • 30,971
  • 16
  • 136
  • 217
Omar Kooheji
  • 54,530
  • 68
  • 182
  • 238
  • 2
    It is correctly explained here http://stackoverflow.com/questions/3393649/storing-a-mapstring-string-using-jpa – jalogar Jan 24 '12 at 14:48

4 Answers4

41

You could simply use the JPA annotation @MapKey (note that the JPA annotation is different from the Hibernate one, the Hibernate @MapKey maps a database column holding the map key, while the JPA's annotation maps the property to be used as the map's key).

@javax.persistence.OneToMany(cascade = CascadeType.ALL)
@javax.persistence.MapKey(name = "name")
private Map<String, Person> nameToPerson = new HashMap<String, Person>();
Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • Thanks I'll try this. What kind of databse schema does this reflect? I'm using DB unit and want to be able to populate the dataset using XML. Does this use a join table? if so can you specify what the table is called and what are the columns of the join table? – Omar Kooheji Feb 25 '10 at 09:41
  • 2
    @Omar It creates a join table (FOO_PERSON by default). You can control the name using `@javax.persistence.JoinTable`. Not sure about the columns. – Pascal Thivent Feb 25 '10 at 10:17
11
@CollectionOfElements(fetch = FetchType.LAZY)
@JoinTable(name = "JOINTABLE_NAME",
    joinColumns = @JoinColumn(name = "id"))
@MapKey(columns = @Column(name = "name"))
@Column(name = "age")
private Map<String, String> ages = new HashMap<String, String>();
whiskeysierra
  • 5,030
  • 1
  • 29
  • 40
  • That is the kind of thing I am looking for however how would I do it if the key was a string as in the example and the value was another object? – Omar Kooheji Feb 24 '10 at 21:15
  • That looks similar to the annotation for mapping an Enum. – James P. Feb 24 '10 at 23:41
  • @Omar: Should be as simple as replacing "String" with the entity type of your choice. You might need to remove @Column(name = "age") and add something like inverseJoinColumns=@JoinColumn(name="fk_ik") to the @JoinTable-annotation. Not sure though. – whiskeysierra Feb 25 '10 at 02:05
  • I tried something like that for persisting an ArrayList (The inverse join column thing) but ran into some trouble with my DBUnit tests they couldn't drop the tables to repopulate them because there was a circular dependancy between the two tables. – Omar Kooheji Feb 25 '10 at 09:45
  • Did u try to persist a single arraylist as a value of a map? – whiskeysierra Feb 25 '10 at 18:46
2

I know this question is very old but maybe this could help someone.

Other posibility is something like that:

@Entity
@Table(name = "PREFERENCE", uniqueConstraints = { @UniqueConstraint(columnNames = { "ID_DOMAIN", "ID_USER", "KEY" })})
public class Preferences {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", unique = true, nullable = false)
    private Long id;

    @Column(name = "ID_DOMAIN", unique = false, nullable = false")
    private Long domainId;

    @Column (name = "PREFERENCE_KEY")
    @Enumerated(EnumType.STRING)
    private PreferenceKey key;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ID_USER", referencedColumnName = "ID")
    private User user;
}

and 

@Entity
@Table(name = "USER", uniqueConstraints = { @UniqueConstraint(columnNames = { "ID_DOMAIN", "LOGIN" })})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID", unique = true, nullable = false)
    private Long id;

    @Column(name = "ID_DOMAIN", unique = false, nullable = false")
    private Long domainId;

    // more fields

    @ElementCollection(fetch = FetchType.LAZY)
    @JoinColumns({@JoinColumn(name = "ID_USER", referencedColumnName = "ID"), @JoinColumn(name = "ID_DOMAIN", referencedColumnName = "ID_DOMAIN")})
    @OneToMany(targetEntity = Preferences.class, fetch = FetchType.LAZY)
    @MapKey(name = "key")
    private Map<PreferenceKey, Preferences> preferencesMap;
}

That only produces two tables User and Preferences, note that PreferenceKey is unique for a User into a domain

Juan
  • 544
  • 6
  • 20
1

You should probably use a UserType or UserCollectionType. Or, you can use a custom tupleizer.

see hibernate core documentation for the concepts and hibernate annotations documentation for the equivalent annotation approach.

Let me know if that isn't what you are asking for.

Jeff Walker
  • 1,656
  • 1
  • 18
  • 36
  • 1
    I hate to sound like an idiot but I've been looking at those very same two pages on and off all day and I don't seem to get it. I've been looking at this style: Which seems to be the kind of thing I need I just don't understand how to do it as annotation. I also don't know how I'd do it if the element wasn't a string. Do I have the wrong end of the stick. – Omar Kooheji Feb 24 '10 at 18:37
  • Yes, I think you really want a UserType, because all of the other things discussed here (I believe) create another table to store the map in. Look at the UserType interface and you will see that there are only a few methods to implement. Basically you tell hibernate, given a ResultSet, how to put it into the java type and vice-versa. – Jeff Walker Feb 25 '10 at 14:50