Thank Mitesh. I think you may have hit the nail on the head. I think my
problem is that I'm maintaining the relationships, but, on objects that
aren't currently managed. One thing I'm wondering though, the following unit
test works. If i needs to be managed, shouldn't it be failing? I wonder if I
put a CascadeType.MERGE on the Item.reservations property, if that would
help? I'm guessing not, because Reservation is being PERSISTED, but, Item
already exists? I have all my persistence logic encapsulated in a class
called ReservationsService which has methods like
saveReservation(Reservation r) which either persist() or merge() the passed
in Reservation. It looks like the solution is for me to check within my save
methods whether sub objects have ids or not and merge them if they do.
Jon
----- Original Message -----
From: "Mitesh Meswani" <Mitesh.Meswani_at_Sun.COM>
To: <persistence_at_glassfish.dev.java.net>
Sent: Monday, February 12, 2007 6:52 PM
Subject: Re: Problems with shared cache
> Hi Jon,
>
> In general Toplink (and the JPA spec) requires that relationships are
> maintained on both sides of a bidirectional relationship. The symptoms you
> are mentioning smells like some path in your code is not doing it
> correctly. Can you please re-check your code. Please see inline for a
> comment
>
> Jon Miller wrote:
>> Hi all,
>>
>> I've been having some problems when I don't turn off the shared cache
>> (i.e. use the default). I was hoping to come up with a unit test that
>> demonstrates the problem. However, the one time that I want the test to
>> fail, it works. Basically, I have a bidirectional relationship.
>> Item.reservations <-> Reservation.items. I have some other more
>> complicated code which does basically the same thing that you see below.
>> It perssists a User, then an Item, and then a Reservation. The problem
>> happens when I do a query for the Item. The Item's reservations property
>> is size() == 0 instead of 1. I've ran into this problem in another app
>> too. If I disable the shared cache, it works fine. I'm wondering if it's
>> possible to corrupt the shared cache and if that's what I'm doing? The
>> way I have my app setup, I'm mostly working with detached entities.
>>
>> public void testPersist() {
>> String s = "testPersist";
>> EntityManagerFactory emf = Persistence.createEntityManagerFactory(
>> "Reservations");
>> EntityManager em = emf.createEntityManager();
>> em.getTransaction().begin();
>> Item i = new Item(s);
>> em.persist(i);
>> em.getTransaction().commit();
>> em.close();
>>
>> em = emf.createEntityManager();
>> em.getTransaction().begin();
>> User u = new User(s);
>> em.persist(u);
>> em.getTransaction().commit();
>> em.close();
>>
>> em = emf.createEntityManager();
>> em.getTransaction().begin();
>> Reservation r = new Reservation();
>> r.setStartTime(new GregorianCalendar(1969, 1, 1, 1, 0).getTime());
>> r.setEndTime(new GregorianCalendar(1969, 1, 1, 2, 0).getTime());
>> r.setUser(u);
> i needs to be managed above. can you try calling i = em.merge(i) at this
> point.
>
> Regards,
> Mitesh
>> r.getItems().add(i);
>> i.getReservations().add(r);
>> em.persist(r);
>> em.getTransaction().commit();
>> em.close();
>>
>> em = emf.createEntityManager();
>> Query q = em.createQuery(
>> "SELECT i FROM Item i LEFT JOIN FETCH i.reservations WHERE
>> i.name = :name");
>> q.setParameter("name", s);
>> List l = q.getResultList();
>> Item i2 = (Item)l.get(0);
>> assertTrue(i2.getReservations().size() == 1);
>>
>> em.close();
>>
>> emf.close();
>> }
>>
>> // Reservation.java
>> package edu.uchicago.at.reservations.persistence.entity;
>>
>> import edu.uchicago.at.common.persistence.entity.AuditObject;
>> import edu.uchicago.at.common.persistence.entity.User;
>> import java.util.ArrayList;
>> import java.util.Date;
>> import java.util.List;
>> import javax.persistence.Column;
>> import javax.persistence.Entity;
>> import javax.persistence.GeneratedValue;
>> import javax.persistence.GenerationType;
>> import javax.persistence.Id;
>> import javax.persistence.JoinColumn;
>> import javax.persistence.JoinTable;
>> import javax.persistence.ManyToMany;
>> import javax.persistence.ManyToOne;
>> import javax.persistence.OrderBy;
>> import javax.persistence.Table;
>> import javax.persistence.Temporal;
>> import javax.persistence.TemporalType;
>>
>> @Entity
>> @Table(name="Reservation")
>> public class Reservation extends AuditObject implements
>> Comparable<Reservation>
>> {
>> private Date checkInTime;
>> private Date checkOutTime;
>> private String description;
>> private Date endTime;
>> private Integer id;
>> private List<Item> items = new ArrayList<Item>();
>> private Date startTime;
>> private User user;
>>
>> public Reservation() {
>> }
>>
>> public Reservation(Date startTime, Date endTime, User user, Item item)
>> {
>> setStartTime(startTime);
>> setEndTime(endTime);
>> setUser(user);
>> items.add(item);
>> }
>>
>> public int compareTo(Reservation reservation) {
>> int i = 0;
>> if(startTime == null && reservation.getStartTime() != null) {
>> return -1;
>> }
>> if(startTime != null && reservation.getStartTime() == null) {
>> return 1;
>> }
>> if(startTime == null && reservation.getStartTime() == null) {
>> i = 0;
>> }
>> else {
>> i = startTime.compareTo(reservation.getStartTime());
>> }
>> if(i == 0) {
>> if(user == null && reservation.getUser() != null) {
>> return -1;
>> }
>> if(user != null && reservation.getUser() == null) {
>> return 1;
>> }
>> if(user == null && reservation.getUser() == null) {
>> i = 0;
>> }
>> else {
>> i = user.compareTo(reservation.getUser());
>> }
>> }
>> return i;
>> }
>>
>> @Column(name="CheckInTime")
>> @Temporal(TemporalType.TIMESTAMP)
>> public Date getCheckInTime() {
>> return checkInTime;
>> }
>>
>> public void setCheckInTime(Date checkInTime) {
>> this.checkInTime = checkInTime;
>> }
>>
>> @Column(name="CheckOutTime")
>> @Temporal(TemporalType.TIMESTAMP)
>> public Date getCheckOutTime() {
>> return checkOutTime;
>> }
>>
>> public void setCheckOutTime(Date checkOutTime) {
>> this.checkOutTime = checkOutTime;
>> }
>>
>> @Column(name="Description"/*, columnDefinition="nvarchar(max)"*/)
>> public String getDescription() {
>> return description;
>> }
>>
>> public void setDescription(String description) {
>> this.description = description;
>> }
>>
>> @Column(name="EndTime", nullable=false)
>> @Temporal(TemporalType.TIMESTAMP)
>> public Date getEndTime() {
>> return endTime;
>> }
>>
>> public void setEndTime(Date endTime) {
>> this.endTime = endTime;
>> }
>>
>> @Id
>> @GeneratedValue(strategy = GenerationType.IDENTITY)
>> @Column(name="Id")
>> public Integer getId() {
>> return id;
>> }
>>
>> public void setId(Integer id) {
>> this.id = id;
>> }
>>
>> @Column(name="StartTime", nullable=false)
>> @Temporal(TemporalType.TIMESTAMP)
>> public Date getStartTime() {
>> return startTime;
>> }
>>
>> public void setStartTime(Date startTime) {
>> this.startTime = startTime;
>> }
>>
>> @ManyToOne
>> @JoinColumn(name="UserId", nullable=false)
>> public User getUser() {
>> return user;
>> }
>>
>> public void setUser(User user) {
>> this.user = user;
>> }
>>
>> @ManyToMany
>> @JoinTable(name="ReservationItem",
>> joinColumns={_at_JoinColumn(name="ReservationId")},
>> inverseJoinColumns={_at_JoinColumn(name="ItemId")})
>> @OrderBy("name")
>> public List<Item> getItems() {
>> return items;
>> }
>>
>> public void setItems(List<Item> items) {
>> this.items = items;
>> }
>>
>> public String toString() {
>> StringBuilder sb = new StringBuilder();
>> sb.append("[");
>> sb.append(String.format("id = %d", id));
>> sb.append(String.format(", startTime = %s", startTime));
>> sb.append(String.format(", endTime = %s", endTime));
>> sb.append(String.format(", user = %s", user));
>> sb.append(String.format(", checkOutTime = %s", checkOutTime));
>> sb.append(String.format(", checkInTime = %s", checkInTime));
>> sb.append(String.format(", description = \"%s\"", description));
>> sb.append(String.format(", items = %s", items));
>> sb.append("]");
>> return sb.toString();
>> }
>> }
>>
>> // Item.java
>> package edu.uchicago.at.reservations.persistence.entity;
>>
>> import edu.uchicago.at.common.persistence.entity.AuditObject;
>> import java.util.ArrayList;
>> import java.util.List;
>> import javax.persistence.Column;
>> import javax.persistence.Entity;
>> import javax.persistence.GeneratedValue;
>> import javax.persistence.GenerationType;
>> import javax.persistence.Id;
>> import javax.persistence.JoinColumn;
>> import javax.persistence.JoinTable;
>> import javax.persistence.ManyToMany;
>> import javax.persistence.OrderBy;
>> import javax.persistence.Table;
>>
>> @Entity
>> @Table(name="Item")
>> public class Item extends AuditObject implements Comparable<Item> {
>> private String description;
>> private Integer id;
>> private String name;
>> private List<Reservation> reservations = new ArrayList<Reservation>();
>> private List<Item> subItems = new ArrayList<Item>();
>> private List<Item> superItems = new ArrayList<Item>();
>>
>> public Item() {
>> }
>>
>> public Item(String name) {
>> setName(name);
>> }
>>
>> public int compareTo(Item item) {
>> if(name == null && item.getName() == null) {
>> return 0;
>> }
>> if(name == null) {
>> return -1;
>> }
>> if(item.getName() == null) {
>> return 1;
>> }
>> return name.compareTo(item.getName());
>> }
>>
>> public boolean contains(Item item) {
>> for(Item i : subItems) {
>> if (i.id == item.id) {
>> return true;
>> }
>> if (i.contains(item)) {
>> return true;
>> }
>> }
>> return false;
>> }
>>
>> @Column(name="Description"/*, columnDefinition="nvarchar(max)"*/)
>> public String getDescription() {
>> return description;
>> }
>>
>> public void setDescription(String description) {
>> this.description = description;
>> }
>>
>> @Id
>> @GeneratedValue(strategy = GenerationType.IDENTITY)
>> @Column(name="Id")
>> public Integer getId() {
>> return id;
>> }
>>
>> public void setId(Integer id) {
>> this.id = id;
>> }
>>
>> @Column(name="Name"/*, columnDefinition="nvarchar(255)"*/)
>> public String getName() {
>> return name;
>> }
>>
>> public void setName(String name) {
>> this.name = name;
>> }
>>
>> @ManyToMany(mappedBy="items")
>> @OrderBy("startTime")
>> public List<Reservation> getReservations() {
>> return reservations;
>> }
>>
>> public void setReservations(List<Reservation> reservations) {
>> this.reservations = reservations;
>> }
>>
>> @ManyToMany
>> @JoinTable(name="ItemItem", joinColumns={_at_JoinColumn(name="ItemId")},
>> inverseJoinColumns={_at_JoinColumn(name="SubItemId")})
>> @OrderBy("name")
>> public List<Item> getSubItems() {
>> return subItems;
>> }
>>
>> public void setSubItems(List<Item> subItems) {
>> this.subItems = subItems;
>> }
>>
>> @ManyToMany(mappedBy="subItems")
>> @OrderBy("name")
>> public List<Item> getSuperItems() {
>> return superItems;
>> }
>>
>> public void setSuperItems(List<Item> superItems) {
>> this.superItems = superItems;
>> }
>>
>> public String toString() {
>> StringBuilder sb = new StringBuilder();
>> sb.append("[");
>> sb.append(String.format("id = %d", id));
>> sb.append(String.format(", name = \"%s\"", name));
>> sb.append(String.format(", subItems = %s", subItems));
>> sb.append(String.format(", description = \"%s\"", description));
>> sb.append("]");
>> return sb.toString();
>> }
>> }
>