JPA: How to annotate HashMap?

6,511 views
Skip to first unread message

Alin Alin

unread,
May 24, 2013, 12:57:34 PM5/24/13
to play-fr...@googlegroups.com
I have this model:
public class ImageModel extends Model {
    @Id
    private String id;
    private String url;

    @ManyToOne
    private Map<String,String> tags = new HashMap<>();

    public void add(String key, String value){
        tags.put(key,value);
    }
    
    public String get(String key){
         return tags.get(key);
    }

}

This how I create a new object:
ImageModel imageModel = new ImageModel();
/* fill object with data */
imageModel.save(); // I saved it


But when I try to retrieve data, the HashMap has zero records:
 HashMap<String, String> imageTags = (HashMap<String, String>) imageModel.getTags();


What can I do, so the informations from hashmap will be saved too?
            

Luís Loureiro

unread,
May 27, 2013, 10:33:25 AM5/27/13
to play-fr...@googlegroups.com
Hi Alin!

When you're using the ManyToOne annotation the generic type should be another Model. In this case i suppose you need to create a model named Tags with two String properties.
In the Tags model you should have a ImageModel property with the annotation OneToMany.
When using a relational database you should normalize the model separating different information into different tables and create a relationship between them.

Hope I have helped and understood the problem.

Luís Loureiro

Alin Alin

unread,
May 27, 2013, 11:31:47 AM5/27/13
to play-fr...@googlegroups.com

First, I want to thank you for your response.:) I thought I will never get an answer. 

I am still trying to figure out how to store a collection in database. I have never used JPA before, but I thought I can just annotate the HashMap and everything will work fine.
I abandoned the HashMap and tried first with an ArrayList<String> (I thought it will be more simple). I annotated with @ElementCollection, but when I try to read, has zero elements.

I annotated, I added some elements to ArrayList and done  imagemodel.save().  When I retrieve it: 
Model.Finder<String,ImageModel> finder = new Model.Finder<String,ImageModel>(String.class,ImageModel.class);
List<ImageModel> list = finder.all();

All the images had the ArrayList empty.

What am I doing wrong?


xkape

unread,
May 27, 2013, 2:52:04 PM5/27/13
to play-fr...@googlegroups.com
Hi, you can actually annotate hashmap in JPA but not with@ManyToOne.

It should be like

@ElementCollection(fetch=FetchType.Lazy)
@Column(name="tableName")
private Map<String,String> tableName =new HashMapp<String,String> ();

This should work for you.

Luís Loureiro

unread,
May 27, 2013, 3:12:14 PM5/27/13
to play-fr...@googlegroups.com
After reading the Philips response i finally understood what you're meaning.
I agree with Philips, that should work!

Follow this tutorial: http://docs.oracle.com/javaee/6/tutorial/doc/bnbqa.html#giqvn, it helped me a lot!

Alin Alin

unread,
May 27, 2013, 3:29:32 PM5/27/13
to play-fr...@googlegroups.com
Pardon me, I still not succeeded. I don't know what I am missing. What I suppose to do? Because on the documentation says:

  • When the Map value is an embeddable class or basic type, use the @ElementCollection annotation. (String is a basic type)

    And I already used that annotation. 

Alin Alin

unread,
May 27, 2013, 4:19:11 PM5/27/13
to play-fr...@googlegroups.com
I realized that I used @ElementCollection(fetch=FetchType.LAZY)  // here you have "fetch=Fetch.Lazy" . Is there any difference?

Edited source from Philips Effah:
Message has been deleted

Alin Alin

unread,
May 29, 2013, 5:36:09 PM5/29/13
to play-fr...@googlegroups.com
Still nothing solved.


I will even put a bounty on that question (in 2 days).

Luís Loureiro

unread,
May 29, 2013, 8:01:30 PM5/29/13
to play-fr...@googlegroups.com
Based on the tutorial:

When using Map elements or relationships, the following rules apply.

  • The Map key or value may be a basic Java programming language type, an embeddable class, or an entity.

  • When the Map value is an embeddable class or basic type, use the @ElementCollection annotation.

  • When the Map value is an entity, use the @OneToMany or @ManyToMany annotation.

  • Use the Map type on only one side of a bidirectional relationship.

If the key type of a Map is a Java programming language basic type, use the annotation javax.persistence.MapKeyColumn to set the column mapping for the key. By default, the name attribute of @MapKeyColumn is of the form RELATIONSHIP-FIELD/PROPERTY-NAME_KEY. For example, if the referencing relationship field name is image, the default name attribute is IMAGE_KEY.

Try to add the annotation MapKeyColumn keeping the @ElementCollection annotation.

Using this configuration the JPA implementation it's suppose to create a new table to hold the collection values! The name of the table should be ImageModel_tags, the name of the class + "_" + field/property name. If you want to change it use the annotation CollectionTable.

As there's no JPA built-in implementation you're suppose to configure one. Try the Hibernate version. It's the most widely used.
Follow this tutorial: http://docs.oracle.com/javaee/6/tutorial/doc/bnbqw.html#bnbrj
I'm using the Eclipse implementation and my persistence unit is:
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <class>models.db.User</class>
        <properties>
            <property name="eclipselink.target-database" value="PostgreSQL"/>
            <property name="eclipselink.ddl-generation" value="create-or-extend-tables"/>
            <property name="eclipselink.ddl-generation.output-mode" value="database"/>
        </properties>
    </persistence-unit>
</persistence>

My Build.scala:

...
val appDependencies = Seq(
      ....
    , "org.eclipse.persistence" % "org.eclipse.persistence.jpa" % "2.4.0"
....
val main = play.Project(appName, appVersion, appDependencies).settings(
      ....
      resolvers += (
          "EclipseLink JPA" at "http://download.eclipse.org/rt/eclipselink/maven.repo"
      )
 
Did it helped?

Alin Alin

unread,
May 30, 2013, 1:46:42 PM5/30/13
to play-fr...@googlegroups.com
I added in Build.scala settings as you, I used 

@ElementCollection(fetch=FetchType.LAZY)
@MapKeyColumn(name="IMAGE_particulars")
private Map<String,String> particulars;


But still only other attributes are seen like "id".

So, is still not working. :(
 

Luís Loureiro

unread,
May 31, 2013, 10:40:31 AM5/31/13
to play-fr...@googlegroups.com
For what i've seen in the tutorial i think that property particulars is suppose to reference a database table named ImageModel_particulars.
Is there any other table instead of the ImageModel?
Did you create your persistence.xml file?

BTW, it's seems that the fetch=FetchType.LAZY is redundant because it's like that by default. See this: http://docs.oracle.com/javaee/6/api/javax/persistence/ElementCollection.html#fetch%28%29

Alin Alin

unread,
May 31, 2013, 10:48:35 AM5/31/13
to play-fr...@googlegroups.com
I also tested "samples" projects from play l all worked fine. But none of them is using a HashMap. If you have a simple project that have a HashMap or even an ArrayList that works for you, I will be happy to try to run and see if works.


BTW, I have added in a folder META-INF persistence.xml. 
persistence.xml
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             version="2.0">
    <persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <class>models.db.ImageModel</class>
        <properties>
            <property name="eclipselink.target-database" value="MySQL"/>

Alin Alin

unread,
Jun 2, 2013, 8:28:57 AM6/2/13
to play-fr...@googlegroups.com

Lev Tverdokhlebov

unread,
Jul 1, 2013, 9:29:31 PM7/1/13
to play-fr...@googlegroups.com
This is workaround, not a solution. I would also like to see an example of using Map as entity field.
Message has been deleted

Raymond Lau

unread,
Jul 12, 2013, 2:37:24 PM7/12/13
to play-fr...@googlegroups.com

Lev Tverdokhlebov

unread,
Jul 12, 2013, 2:40:05 PM7/12/13
to play-fr...@googlegroups.com
Thx for pointing out!
Reply all
Reply to author
Forward
0 new messages