I found that if I use EntityManager instead of Session that when I do a refresh() of a CalendarItem, in some cases I receive a StackOverflowError exception. It apparently has something to do with the way I have the cascades setup for CalendarItem.getUser() and User.getCalendarItems(). If I comment out the CascadeType.ALL, it doesn't give me the exception. If I use Hibernate's Session instead, I don't receive the exception. This seems strange to me as I would expect the behavior to pretty much be the same since I'm guessing that EntityManager is just a wrapper on top of Session. Does anyone see anything wrong here? I'm wondering if maybe there is a problem with my equals() implementation in CalendarItem since it references user. I think it's basically getting caught in an infinite loop refreshing CalendarItems and Users.
Hibernate version: hibernate-3.1alpha1, hibernate-annotations-3.1beta3, hibernate-entitymanager-3.1beta1
Mapping documents:
Code:
// CalendarItem.java
package edu.uchicago.at.blackboard.persistence.entity;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
@Entity
@Table(name="calendar", schema="bb_bb60")
public class CalendarItem implements Comparable<CalendarItem>, Serializable {
private Course course;
private String description;
private String descriptionType = "P";
private Date endTime;
private int id;
private Date modifyTime;
private Date startTime;
private String title;
private String type = "P";
private User user;
public CalendarItem() {
}
public CalendarItem(Date startTime, Date endTime) {
setStartTime(startTime);
setEndTime(endTime);
}
public CalendarItem(Date startTime, Date endTime, String title) {
this(startTime, endTime);
setTitle(title);
}
public CalendarItem(User user, Date startTime, Date endTime, String title,
String description) {
setUser(user);
setStartTime(startTime);
setEndTime(endTime);
setTitle(title);
setDescription(description);
}
public int compareTo(CalendarItem calendarItem) {
return startTime.compareTo(calendarItem.getStartTime());
}
public boolean equals(Object o) {
if(! (o instanceof CalendarItem)) {
return false;
}
CalendarItem ci = (CalendarItem)o;
if((user == ci.getUser() || (user != null && user.equals(ci.getUser())))
&& (startTime == ci.getStartTime()
|| startTime.getTime() == ci.getStartTime().getTime())
&& (endTime == ci.getEndTime()
|| endTime.getTime() == ci.getEndTime().getTime())
&& (title == ci.getTitle()
|| (title != null && title.equals(ci.getTitle())))) {
return true;
}
return false;
}
@ManyToOne
@JoinColumn(name="crsmain_pk1")
public Course getCourse() {
return course;
}
@Column(name="message")
public String getDescription() {
return description;
}
@Column(name="text_format_type")
public String getDescriptionType() {
return descriptionType;
}
@Column(name="end_date")
public Date getEndTime() {
return endTime;
}
@Column(name="pk1")
@Id(generate=GeneratorType.AUTO,
generator="calendarItemSequenceGenerator")
@SequenceGenerator(name="calendarItemSequenceGenerator",
sequenceName="bb_bb60.calendar_seq")
public int getId() {
return id;
}
@Column(name="dtmodified")
public Date getModifyTime() {
return modifyTime;
}
@Column(name="start_date")
public Date getStartTime() {
return startTime;
}
@Column(name="subject")
public String getTitle() {
return title;
}
@Column(name="event_type")
public String getType() {
return type;
}
@ManyToOne(cascade=CascadeType.REFRESH)
@JoinColumn(name="users_pk1")
public User getUser() {
return user;
}
public int hashCode() {
int i = 17;
if(user != null) {
i = 37 * i + user.hashCode();
}
if(startTime != null) {
i = 37 * i + startTime.hashCode();
}
if(endTime != null) {
i = 37 * i + endTime.hashCode();
}
if(title != null) {
i = 37 * i + title.hashCode();
}
return i;
}
public void setCourse(Course course) {
this.course = course;
}
public void setDescription(String description) {
if(description != null) {
description = description.trim();
}
this.description = description;
}
public void setDescriptionType(String descriptionType) {
this.descriptionType = descriptionType;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public void setId(int id) {
this.id = id;
}
public void setModifyTime(Date modifyTime) {
this.modifyTime = modifyTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public void setTitle(String title) {
if(title != null) {
title = title.trim();
}
this.title = title;
}
public void setType(String type) {
this.type = type;
}
public void setUser(User user) {
this.user = user;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(id);
sb.append(", ");
sb.append(startTime.toString());
sb.append(", ");
sb.append(endTime.toString());
sb.append(", ");
sb.append(title);
sb.append(", ");
sb.append(description);
sb.append("]");
return sb.toString();
}
}
Code:
// User.java
package edu.uchicago.at.blackboard.persistence.entity;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratorType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
@Entity
@Table(name="users", schema="bb_bb60")
public class User implements Comparable<User>, Serializable {
private Collection<CalendarItem> calendarItems =
new ArrayList<CalendarItem>();
private String emailAddress;
private String firstName;
private int id;
private static Logger logger = Logger.getLogger(User.class.getName());
private String lastName;
private String middleName;
private String userId;
public User() {
}
public User(String userId, String firstName, String middleName,
String lastName, String emailAddress) {
setUserId(userId);
setFirstName(firstName);
setMiddleName(middleName);
setLastName(lastName);
setEmailAddress(emailAddress);
}
public int compareTo(User user) {
return getUserId().compareTo(user.getUserId());
}
public boolean equals(Object o) {
if(! (o instanceof User)) {
return false;
}
User u = (User)o;
if(userId == u.getUserId()
|| (userId != null && userId.equals(u.getUserId()))) {
return true;
}
return false;
}
@OneToMany(mappedBy="user", cascade=CascadeType.ALL)
@org.hibernate.annotations.Cascade(
org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
public Collection<CalendarItem> getCalendarItems() {
return calendarItems;
}
@Column(name="email")
public String getEmailAddress() {
return emailAddress;
}
public String getFirstName() {
return firstName;
}
@Column(name="pk1")
@Id(generate=GeneratorType.SEQUENCE,
generator="userSequenceGenerator")
@SequenceGenerator(name="userSequenceGenerator",
sequenceName="users_seq")
public int getId() {
return id;
}
public String getLastName() {
return lastName;
}
public String getMiddleName() {
return middleName;
}
@Transient
public String getName() {
StringBuilder sb = new StringBuilder();
if(firstName != null) {
sb.append(firstName);
}
if(middleName != null) {
sb.append(" ");
sb.append(middleName);
}
if(lastName != null) {
sb.append(" ");
sb.append(lastName);
}
return sb.toString().trim();
}
@Column(name="user_id")
public String getUserId() {
return userId;
}
public int hashCode() {
int i = 17;
if(userId != null) {
i = 37 * i + userId.hashCode();
}
return i;
}
public void setCalendarItems(Collection<CalendarItem> calendarItems) {
this.calendarItems = calendarItems;
}
public void setEmailAddress(String emailAddress) {
if(emailAddress != null) {
emailAddress = emailAddress.trim();
}
this.emailAddress = emailAddress;
}
public void setFirstName(String firstName) {
if(firstName != null) {
firstName = firstName.trim();
}
this.firstName = firstName;
}
public void setId(int id) {
this.id = id;
}
public void setLastName(String lastName) {
if(lastName != null) {
lastName = lastName.trim();
}
this.lastName = lastName;
}
public void setMiddleName(String middleName) {
if(middleName != null) {
middleName = middleName.trim();
}
this.middleName = middleName;
}
public void setUserId(String userId) {
if(userId != null) {
userId = userId.trim();
}
this.userId = userId;
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("[");
sb.append(id);
sb.append(", ");
sb.append(userId);
sb.append(", ");
sb.append(firstName);
sb.append(", ");
sb.append(middleName);
sb.append(", ");
sb.append(lastName);
sb.append(", ");
sb.append(emailAddress);
sb.append("]");
return sb.toString();
}
}
Code between sessionFactory.openSession() and session.close():The exception occurs when doing something similar to the following. However, it works OK in this simple case.
Using one EntityManager, do something like this,
Code:
CalendarItem ci = em.find(CalendarItem.class, 2212);
Then, later in another thread, with another EntityManager, do this,
Code:
em.refresh(ci);
Full stack trace of any exception that occurs:
Caused by: java.lang.StackOverflowError
at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:81)
at org.hibernate.collection.PersistentBag.iterator(PersistentBag.java:246)
at org.hibernate.type.CollectionType.getElementsIterator(CollectionType.java:197)
at org.hibernate.type.CollectionType.getElementsIterator(CollectionType.java:189)
at org.hibernate.engine.CascadingAction.getLoadedElementsIterator(CascadingAction.java:252)
at org.hibernate.engine.CascadingAction$3.getCascadableChildrenIterator(CascadingAction.java:96)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:286)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:183)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:158)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:106)
at org.hibernate.engine.Cascade.cascade(Cascade.java:246)
at org.hibernate.engine.Cascade.cascade(Cascade.java:221)
at org.hibernate.event.def.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:85)
at org.hibernate.impl.SessionImpl.refresh(SessionImpl.java:685)
at org.hibernate.engine.CascadingAction$3.cascade(CascadingAction.java:92)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:211)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:155)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:106)
at org.hibernate.engine.Cascade.cascade(Cascade.java:246)
at org.hibernate.engine.Cascade.cascade(Cascade.java:221)
at org.hibernate.event.def.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:85)
at org.hibernate.impl.SessionImpl.refresh(SessionImpl.java:685)
at org.hibernate.engine.CascadingAction$3.cascade(CascadingAction.java:92)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:211)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:155)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:106)
at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:288)
at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:183)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:158)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:106)
at org.hibernate.engine.Cascade.cascade(Cascade.java:246)
at org.hibernate.engine.Cascade.cascade(Cascade.java:221)
at org.hibernate.event.def.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:85)
at org.hibernate.impl.SessionImpl.refresh(SessionImpl.java:685)
at org.hibernate.engine.CascadingAction$3.cascade(CascadingAction.java:92)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:211)
at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:155)
at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:106)
at org.hibernate.engine.Cascade.cascade(Cascade.java:246)
at org.hibernate.engine.Cascade.cascade(Cascade.java:221)
at org.hibernate.event.def.DefaultRefreshEventListener.onRefresh(DefaultRefreshEventListener.java:85)
at org.hibernate.impl.SessionImpl.refresh(SessionImpl.java:685)
at org.hibernate.engine.CascadingAction$3.cascade(CascadingAction.java:92)
...
Name and version of the database you are using: Oracle 9i