I have recently started playing around with the Play! Framework for Java, version 1.2.3 (the latest). While testing out the framework, I came across a strange problem when trying to persist a Map
object inside a Hibernate entity called FooSystem
. The Map object maps a long to a Hibernate entity I have called Foo
, with the declaration Map<Long, Foo> fooMap;
My problem is as follows: The correct tables are created as I have annotated them. However, when the FooSystem
object fs
is persisted, the data in fs.fooMap
is not!
Here is the code I am using for the entities. First is Foo
:
package models.test;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import play.db.jpa.Model;
@Entity
public class Foo extends Model
{
@ManyToOne
private FooSystem foosystem;
public Foo(FooSystem foosystem)
{
this.foosystem = foosystem;
}
}
And here is FooSystem
:
package models.test;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import play.db.jpa.Model;
@Entity
public class FooSystem extends Model
{
@ManyToMany(cascade = {CascadeType.ALL, CascadeType.PERSIST})
@JoinTable(
name = "fooMap",
joinColumns = @JoinColumn(name = "foosystem"),
inverseJoinColumns = @JoinColumn(name = "foo")
)
private Map<Long, Foo> fooMap = new HashMap<Long, Foo>();
public FooSystem()
{
Foo f1 = new Foo(this);
Foo f2 = new Foo(this);
fooMap.put(f1.getId(), f1);
fooMap.put(f2.getId(), f2);
}
public Map<Long, Foo> getFooMap()
{
return fooMap;
}
}
Here is the Controller
class I am using to test my set-up:
package controllers;
import javax.persistence.EntityManager;
import models.test.FooSystem;
import play.db.jpa.JPA;
import play.mvc.Controller;
public class TestController extends Controller
{
public static void index() {
EntityManager em = JPA.em();
FooSystem fs = new FooSystem();
em.persist(fs);
render();
}
}
The Play! framework automatically created a transaction for the HTTP request. Although data is inserted into the foo
and foosystem
tables, nothing is ever inserted into the foomap
table, which is the desired result. What can I do about this? What am I missing?
I managed to solve this problem using the advice of Java Ka Baby. The issue was actually not in my Model
classes; the problem lay within the Controller
. Specifically, I was saving the entities in the wrong order. Once I realized that using the @ElementCollection
annotation on the Map<Long, Foo>
produced the same effects as the join table I was manually specifying, I tried I thought experiment where I re-thought how I was saving my entities.
In the code I posted above, you can see in the FooSystem
constructor that two Foo
objects, f1
and f2
, are put into fooMap
before the Foo
objects are persisted. I realized that if f1
is not in the database when it is put into the map, how is JPA able to use its ID as a foreign key in the join table?
If you can see where I'm going with this line of reasoning, you can see that the obvious answer is that JPA is not able to accomplish this amazing feat of using a foreign key to reference a nonexistent key. The bizarre thing is that the Play! console did not note any errors at all for the original code I posted, even though it was not correct at all. Either the framework swallowed every Exception
thrown during the process, or I've written code that should produce an Exception
.
So to fix the problem, I persisted the Foo
entities before any operations were performed on them. Only then did I put them into fooMap
. Finally, once fooMap
was populated, I persisted the FooSystem
entity.
Here is the corrected TestController
class:
package controllers;
import javax.persistence.EntityManager;
import models.test.Foo;
import models.test.FooSystem;
import play.db.jpa.JPA;
import play.mvc.Controller;
public class TestController extends Controller
{
public static void index() {
EntityManager em = JPA.em();
FooSystem fs = new FooSystem();
Foo f1 = new Foo(fs);
Foo f2 = new Foo(fs);
f1.save();
f2.save();
fs.put(f1.getId(), f1);
fs.put(f2.getId(), f2);
fs.save();
render();
}
}
And, since I changed FooSystem
, here is the final code for that class:
package models.test;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import play.db.jpa.Model;
@Entity
public class FooSystem extends Model
{
@ElementCollection
private Map<Long, Foo> fooMap = new HashMap<Long, Foo>();
public FooSystem()
{
}
public Map<Long, Foo> getFooMap()
{
return fooMap;
}
public void put(Long l, Foo f)
{
fooMap.put(l, f);
}
}
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