persistence@glassfish.java.net

Re: PreRemove ConcurrentModificationException

From: Greg Ederer <greg_at_ergonosis.com>
Date: Thu, 14 Jun 2007 11:48:56 -0700

Markus Fuchs wrote:
> Hi Greg,
>
> That sounds like a bug to me. Could you send me your pojos and a small
> snipplet of your application code as a test case?
>
> You are talking about em.remove(article) and no @PreRemove in
> ArticlePlacement resulting in a foreign key violation, correct?
>
Actually, I get the fk violation when I em.remove(articlePlacement) w/o
the @PreRemove. Does this still sound like a bug, or is it just my
incorrect way of doing things?

Code below.

Cheers,

Greg

In my servlet:

        Object o = em.find(javaType, id);
        responseMap.put("javaType", javaType);
        responseMap.put("id", id);
        utx.begin();
        em.merge(o);
        em.joinTransaction();
        em.remove(o);
        utx.commit();


/*
 * Article.java
 *
 * Created on May 7, 2007, 2:15 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.acadept.model.article;

import com.acadept.model.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.PreRemove;
import javax.persistence.SequenceGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * Entity class Article
 *
 * @author gregederer
 */
@Entity
public class Article implements Serializable
{
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ARTICLE_SEQ_GENERATOR")
  @SequenceGenerator(name = "ARTICLE_SEQ_GENERATOR", sequenceName = "ARTICLE_ID_SEQ")
  private Long id;
  
  @ManyToOne
  private ArticleCategory articleCategory;
  
  @OneToMany(mappedBy = "article", cascade=CascadeType.ALL)
  private List<ArticlePlacement> articlePlacements = new ArrayList<ArticlePlacement>();
  
  @Column(name="lead_in_image")
  private String leadInImage;
  
  private String title;
  
  private String subTitle;
  
  @Column(name="lead_in", columnDefinition="text")
  private String leadIn;
  
  @Column(columnDefinition="text")
  private String body;
  
  @OneToOne
  private AppUser poster;
  
  @Column(name="publication_date")
  @Temporal(TemporalType.DATE)
  private Date createdDate;
  
  /** Creates a new instance of Article */
  public Article()
  {
  }

  /**
   * Gets the id of this Article.
   * @return the id
   */
  public Long getId()
  {
    return this.id;
  }

  /**
   * Sets the id of this Article to the specified value.
   * @param id the new id
   */
  public void setId(Long id)
  {
    this.id = id;
  }

  /**
   * Returns a hash code value for the object. This implementation computes
   * a hash code value based on the id fields in this object.
   * @return a hash code value for this object.
   */
  @Override
  public int hashCode()
  {
    int hash = 0;
    hash += (this.getId() != null ? this.getId().hashCode() : 0);
    return hash;
  }

  /**
   * Determines whether another object is equal to this Article. The result is
   * <code>true</code> if and only if the argument is not null and is a Article object that
   * has the same id field values as this object.
   * @param object the reference object with which to compare
   * @return <code>true</code> if this object is the same as the argument;
   * <code>false</code> otherwise.
   */
  @Override
  public boolean equals(Object object)
  {
    // TODO: Warning - this method won't work in the case the id fields are not set
    if (!(object instanceof Article)) {
      return false;
    }
    Article other = (Article)object;
    
    // If both are the same object, return true
    if(this == other)
    {
      return true;
    }
    
    // If both objects have ids, compare their ids
    if(this.getId() != null && other.getId() != null)
    {
      // If their ids are equal, return true
      if(this.getId().equals(other.getId()))
      {
        return true;
      }
      // Otherwise, return false
      else
      {
        return false;
      }
    }
    
    // If one or both do not have ids, compare their 'natural keys'
    if(this.getTitle() != null && this.getTitle().equals(other.getTitle())
       && this.getCreatedDate() != null && this.getCreatedDate().equals(other.getCreatedDate())
       && this.getPoster() != null && this.getPoster().equals(other.getPoster())
       )
    {
      return true;
    }
    
    return false;
  }

  /**
   * Returns a string representation of the object. This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString()
  {
    return "com.acadept.model.Article[id=" + getId() + "]";
  }

  public ArticleCategory getArticleCategory()
  {
    return articleCategory;
  }

  public void setArticleCategory(ArticleCategory articleCategory)
  {
    this.articleCategory = articleCategory;
    articleCategory.addArticle(this);
  }

  public String getTitle()
  {
    return title;
  }

  public void setTitle(String title)
  {
    this.title = title;
  }

  public String getSubTitle()
  {
    return subTitle;
  }

  public void setSubTitle(String subTitle)
  {
    this.subTitle = subTitle;
  }

  public String getLeadIn()
  {
    return leadIn;
  }

  public void setLeadIn(String leadIn)
  {
    this.leadIn = leadIn;
  }

  public String getBody()
  {
    return body;
  }

  public void setBody(String body)
  {
    this.body = body;
  }

  public AppUser getPoster()
  {
    return poster;
  }

  public void setPoster(AppUser poster)
  {
    this.poster = poster;
  }

  public Date getCreatedDate()
  {
    return createdDate;
  }

  public void setCreatedDate(Date createdDate)
  {
    this.createdDate = createdDate;
  }

  public String getLeadInImage()
  {
    return leadInImage;
  }

  public void setLeadInImage(String leadInImage)
  {
    this.leadInImage = leadInImage;
  }

  public List<ArticlePlacement> getArticlePlacements()
  {
    return articlePlacements;
  }

  public void setArticlePlacements(List<ArticlePlacement> articlePlacements)
  {
    this.articlePlacements = articlePlacements;
  }
  
  public void addArticlePlacement(ArticlePlacement articlePlacement)
  {
    if(this.getArticlePlacements().contains(articlePlacement))
    {
      return;
    }
    
    getArticlePlacements().add(articlePlacement);
    articlePlacement.setArticle(this);
  }
  
  public void removeArticlePlacement(ArticlePlacement articlePlacement)
  {
    getArticlePlacements().remove(articlePlacement);
    articlePlacement.setArticle(null);
  }
  
  @PreRemove
  public void beforeRemove()
  {
    List<ArticlePlacement> articlePlacements = getArticlePlacements();
    for(ArticlePlacement ap : articlePlacements)
    {
      ap.setArticle(null);
    }
  }
}


/*
 * ArticlePlacement.java
 *
 * Created on May 7, 2007, 2:32 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.acadept.model.article;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.PreRemove;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

/**
 * Entity class ArticlePlacement
 *
 * @author gregederer
 */
@Entity
@Table(name="article_placement")
public class ArticlePlacement implements Serializable
{
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ARTICLE_PLACEMENT_SEQ_GENERATOR")
  @SequenceGenerator(name = "ARTICLE_PLACEMENT_SEQ_GENERATOR", sequenceName = "ARTICLE_PLACEMENT_ID_SEQ")
  private Long id;
  
  @ManyToOne
  private Article article;
  
  @ManyToOne
  private ArticleGroup articleGroup;
  
  @Temporal(value = TemporalType.TIMESTAMP)
  @Column(name="order_position")
  private Date position;
  
  @Temporal(TemporalType.TIMESTAMP)
  private Date startRunDate;
  
  @Temporal(TemporalType.TIMESTAMP)
  private Date endRunDate;
  
  /** Creates a new instance of ArticlePlacement */
  public ArticlePlacement()
  {
  }
  
  /**
   * Gets the id of this ArticlePlacement.
   * @return the id
   */
  public Long getId()
  {
    return this.id;
  }
  
  /**
   * Sets the id of this ArticlePlacement to the specified value.
   * @param id the new id
   */
  public void setId(Long id)
  {
    this.id = id;
  }
  
  /**
   * Returns a hash code value for the object. This implementation computes
   * a hash code value based on the id fields in this object.
   * @return a hash code value for this object.
   */
  @Override
  public int hashCode()
  {
    int hash = 0;
    hash += (this.getId() != null ? this.getId().hashCode() : 0);
    return hash;
  }
  
  /**
   * Determines whether another object is equal to this ArticlePlacement. The result is
   * <code>true</code> if and only if the argument is not null and is a ArticlePlacement object that
   * has the same id field values as this object.
   * @param object the reference object with which to compare
   * @return <code>true</code> if this object is the same as the argument;
   * <code>false</code> otherwise.
   */
  @Override
  public boolean equals(Object object)
  {
    if (!(object instanceof ArticlePlacement))
    {
      return false;
    }
    ArticlePlacement other = (ArticlePlacement)object;
    
    // If both are the same object, return true
    if(this == other)
    {
      return true;
    }
    
    // If both objects have ids, compare their ids
    if(this.getId() != null && other.getId() != null)
    {
      // If their ids are equal, return true
      if(this.getId().equals(other.getId()))
      {
        return true;
      }
      // Otherwise, return false
      else
      {
        return false;
      }
    }
    
    // If one or both do not have ids, compare their 'natural keys'
    if(this.getArticle() != null && this.getArticle().equals(other.getArticle())
            && this.getArticleGroup() != null && this.getArticleGroup().equals(other.getArticleGroup())
            )
    {
      return true;
    }
    
    return false;
  }
  
  /**
   * Returns a string representation of the object. This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString()
  {
    return "com.acadept.model.ArticlePlacement[id=" + getId() + "]";
  }
  
  public Article getArticle()
  {
    return article;
  }
  
  public void setArticle(Article article)
  {
    this.article = article;
    
    if(article != null)
    {
      article.addArticlePlacement(this);
    }
  }
  
  public ArticleGroup getArticleGroup()
  {
    return articleGroup;
  }
  
  public void setArticleGroup(ArticleGroup articleGroup)
  {
    this.articleGroup = articleGroup;
    
    if(articleGroup != null)
    {
      articleGroup.addArticlePlacement(this);
    }
  }
  
  public Date getPosition()
  {
    return position;
  }
  
  public void setPosition(Date position)
  {
    this.position = position;
  }
  
  public Date getStartRunDate()
  {
    return startRunDate;
  }
  
  public void setStartRunDate(Date startRunDate)
  {
    this.startRunDate = startRunDate;
  }
  
  public Date getEndRunDate()
  {
    return endRunDate;
  }
  
  public void setEndRunDate(Date endRunDate)
  {
    this.endRunDate = endRunDate;
  }
  
  @PreRemove
  public void beforeRemove()
  {
    getArticleGroup().removeArticlePlacement(this);
    if(getArticle() != null)
    {
      getArticle().removeArticlePlacement(this);
    }
  }
}


/*
 * ArticleGroup.java
 *
 * Created on May 7, 2007, 2:27 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.acadept.model.article;

import com.acadept.model.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

/**
 * Entity class ArticleGroup
 *
 * @author gregederer
 */
@Entity
@Table(name="article_group")
public class ArticleGroup implements Serializable
{
  private static Logger logger = Logger.getLogger(ContentGroup.class.getName());
  
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ARTICLE_GROUP_SEQ_GENERATOR")
  @SequenceGenerator(name = "ARTICLE_GROUP_SEQ_GENERATOR", sequenceName = "ARTICLE_GROUP_ID_SEQ")
  private Long id;
  
  @Column(name="content_name", unique=true)
  private String name;
  
  @Column(columnDefinition="text")
  private String description;
  
  @OneToMany(cascade=CascadeType.ALL)
  @OrderBy("position ASC")
  private List<ArticlePlacement> articlePlacements = new ArrayList<ArticlePlacement>();

  /**
   * Gets the id of this ArticleGroup.
   * @return the id
   */
  public Long getId()
  {
    return this.id;
  }

  /**
   * Sets the id of this ArticleGroup to the specified value.
   * @param id the new id
   */
  public void setId(Long id)
  {
    this.id = id;
  }

  /**
   * Returns a hash code value for the object. This implementation computes
   * a hash code value based on the id fields in this object.
   * @return a hash code value for this object.
   */
  @Override
  public int hashCode()
  {
    int hash = 0;
    hash += (this.getId() != null ? this.getId().hashCode() : 0);
    return hash;
  }

  /**
   * Determines whether another object is equal to this ArticleGroup. The result is
   * <code>true</code> if and only if the argument is not null and is a ArticleGroup object that
   * has the same id field values as this object.
   * @param object the reference object with which to compare
   * @return <code>true</code> if this object is the same as the argument;
   * <code>false</code> otherwise.
   */
  @Override
  public boolean equals(Object object)
  {
    // TODO: Warning - this method won't work in the case the id fields are not set
    if (!(object instanceof ArticleGroup)) {
      return false;
    }
    ArticleGroup other = (ArticleGroup)object;
    
    // If both are the same object, return true
    if(this == other)
    {
      return true;
    }
    
    // If both objects have ids, compare their ids
    if(this.getId() != null && other.getId() != null)
    {
      // If their ids are equal, return true
      if(this.getId().equals(other.getId()))
      {
        return true;
      }
      // Otherwise, return false
      else
      {
        return false;
      }
    }
    
    // If one or both do not have ids, compare their 'natural keys'
    if(this.getName() != null && this.getName().equals(other.getName())
       )
    {
      return true;
    }
    
    return false;
  }

  /**
   * Returns a string representation of the object. This implementation constructs
   * that representation based on the id fields.
   * @return a string representation of the object.
   */
  @Override
  public String toString()
  {
    return "com.acadept.model.ArticleGroup[id=" + getId() + "]";
  }

  public static Logger getLogger()
  {
    return logger;
  }

  public static void setLogger(Logger aLogger)
  {
    logger = aLogger;
  }

  public String getName()
  {
    return name;
  }

  public void setName(String name)
  {
    this.name = name;
  }

  public String getDescription()
  {
    return description;
  }

  public void setDescription(String description)
  {
    this.description = description;
  }

  public List<ArticlePlacement> getArticlePlacements()
  {
    return articlePlacements;
  }

  public void setArticlePlacements(List<ArticlePlacement> articlePlacements)
  {
    this.articlePlacements = articlePlacements;
  }
  
  public void addArticlePlacement(ArticlePlacement articlePlacement)
  {
    if(this.getArticlePlacements().contains(articlePlacement))
    {
      return;
    }
    
    getArticlePlacements().add(articlePlacement);
    articlePlacement.setArticleGroup(this);
  }
  
  public void removeArticlePlacement(ArticlePlacement articlePlacement)
  {
    getArticlePlacements().remove(articlePlacement);
    articlePlacement.setArticleGroup(null);
  }
}


> Thanks,
>
> -- markus.
>
> Greg Ederer wrote:
>> Markus Fuchs wrote:
>>> Hi Greg,
>>>
>>> Do you get a foreign key violation w/o using @PreRemove callbacks in
>>> your pojos? Are you using a current glassfish build?
>>>
>> Hi Markus,
>>
>> Yes. When I em.remove() an ArticlePlacement without the @PreRemove,
>> I get a PSQLException (I'm using PostgreSQL) with a foreign key
>> constraint violation message.
>>
>> I'm using v2b50.
>>
>> Thanks for the reply.
>>
>> Cheers,
>>
>> Greg
>>> Thanks,
>>>
>>> -- markus.
>>>
>>> Greg Ederer wrote:
>>>> I managed to get this working by adding the following to Article:
>>>>
>>>> @PreRemove
>>>> public void beforeRemove()
>>>> {
>>>> List<ArticlePlacement> articlePlacements = getArticlePlacements();
>>>> for(ArticlePlacement ap : articlePlacements)
>>>> {
>>>> ap.setArticle(null);
>>>> }
>>>> }
>>>>
>>>> And in ArticlePlacement:
>>>>
>>>> @PreRemove
>>>> public void beforeRemove()
>>>> {
>>>> getArticleGroup().removeArticlePlacement(this);
>>>> if(getArticle() != null)
>>>> {
>>>> getArticle().removeArticlePlacement(this);
>>>> }
>>>> }
>>>>
>>>> I have a feeling this is not the best way to handle this (and, I'm
>>>> guessing that I'll feel like a dummy when I find out the correct
>>>> way to do this, because it will be pretty obvious).
>>>>
>>>> Any advice welcome.
>>>>
>>>> Cheers,
>>>>
>>>> Greg
>>>>
>>>> Greg Ederer wrote:
>>>>> Oops! Accidentally hit send. Please ignore previous post.
>>>>>
>>>>> I have a model containing three entity classes: Article,
>>>>> ArticleGroup and ArticlePlacement. An ArticleGroup contains zero
>>>>> or more ArticlePlacement objects. Each ArticlePlacement wraps an
>>>>> Article (ArticlePlacement also has a start run date and an end run
>>>>> date, which allows me to run an article in multiple groups during
>>>>> different periods).
>>>>>
>>>>> So, in Article, I have:
>>>>>
>>>>> @OneToMany(mappedBy = "article", cascade=CascadeType.ALL)
>>>>> private List<ArticlePlacement> articlePlacements = new
>>>>> ArrayList<ArticlePlacement>();
>>>>>
>>>>> In ArticleGroup, I have:
>>>>>
>>>>> @OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE,
>>>>> CascadeType.REFRESH})
>>>>> @OrderBy("position ASC")
>>>>> private List<ArticlePlacement> articlePlacements = new
>>>>> ArrayList<ArticlePlacement>();
>>>>>
>>>>> And, in ArticlePlacement, I have:
>>>>>
>>>>> @ManyToOne
>>>>> private Article article;
>>>>>
>>>>> @ManyToOne
>>>>> private ArticleGroup articleGroup;
>>>>>
>>>>> @PreRemove
>>>>> public void beforeRemove()
>>>>> {
>>>>> getArticleGroup().removeArticlePlacement(this);
>>>>> getArticle().removeArticlePlacement(this);
>>>>> }
>>>>>
>>>>> When I EntityManager.remove() an Article, I get a
>>>>> java.util.ConcurrentModificationException due to the
>>>>> getArticle().removeArticlePlacement(this) in @PreRemove, above.
>>>>> But, if I comment this out, I get a "violates foreign key"
>>>>> PSQLException.
>>>>>
>>>>> Can someone tell me how to deal with this situation correctly?
>>>>>
>>>>> Thanks!
>>>>>
>>>>> Greg
>>>>>
>>>>
>>>>
>>
>>


-- 
| E R G O N O S I S
| Greg Ederer
| Lead Developer
| greg_at_ergonosis.com
| 360.774.6848
|