Grails

Deleting an element from a List in a bidirectional many-to-one association does not correctly update the SQL tables (crash while accessing the list later)

Details

  • Type: Bug Bug
  • Status: Closed Closed
  • Priority: Critical Critical
  • Resolution: Fixed
  • Affects Version/s: 1.0
  • Fix Version/s: 1.0.2
  • Component/s: None
  • Labels:
    None
  • Environment:
    Linux JDK 1.6

Description

When I create a bidirectional many to one association, and then delete an element from the middle of the list, the delete works (eg, the element row is removed from the SQL table), but the other elements associated index are not updated. Eg, if I had 3 elements with index 0,1,2 and delete the second element, I still have element 0 and 2 that have index 0 and 2.

This later causes a Null Pointer exception when loading the list in a next request. Apparently, Grails thinks that the middle element is still there (but null).

If I change the relationship to a unidirectional one, Grails uses a join table (as per Hibernate recommandations), and everything works as expected. Indexs are updated etc.

But since a bidirectional many to one is the most common association, this should really be fixed. It is perfectly supported by Hibernate, so I don't know why it fails. While this bug is not fixed, using Lists in bidirectional relationships won't work. Again, the kind of bug that can confuse newcomers to Grails

The code I used was a simple:

account.removeFromArticles(article)
article.delete()

This is probably closely related to http://jira.codehaus.org/browse/GRAILS-1819. I just create a new JIRA because the symptoms are not exactly the same. In particular, the delete works in my case, only the indexes are messed up after.

Activity

Hide
Jean-Noël Rivasseau added a comment -

Ok, so Hibernate supports this with the following mapping. All that is needed is to tell GORM to use this mapping. This will also solve bug 1819 (and many other) regarding bidirectional lists, as currently it does not work at all.

For reference,

http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/#entity-hibspec-collection-extratype

is a must read (section 2.4.6.2.3. Bidirectional association with indexed collections).

PS: on the following mapping "position" should be changed for something ending with _idx to follow GORM conventions

import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_section")
class FaqSection
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String title

@OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class)
@JoinColumn(name = "section_id", nullable = false)
@IndexColumn(name = "position", base = 0)
List elements
}

import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_element")
class FaqElement
{
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
Long id

@Version
Long version

String question
String answer

@ManyToOne
@JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false)
FaqSection section

static constraints =

{ question(size: 1..500) answer(size: 1..5000) }

}

Show
Jean-Noël Rivasseau added a comment - Ok, so Hibernate supports this with the following mapping. All that is needed is to tell GORM to use this mapping. This will also solve bug 1819 (and many other) regarding bidirectional lists, as currently it does not work at all. For reference, http://www.hibernate.org/hib_docs/annotations/reference/en/html_single/#entity-hibspec-collection-extratype is a must read (section 2.4.6.2.3. Bidirectional association with indexed collections). PS: on the following mapping "position" should be changed for something ending with _idx to follow GORM conventions import javax.persistence.* import org.hibernate.annotations.* @Entity @Table(name="faq_section") class FaqSection { @Id @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY) Long id @Version Long version String title @OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class) @JoinColumn(name = "section_id", nullable = false) @IndexColumn(name = "position", base = 0) List elements } import javax.persistence.* import org.hibernate.annotations.* @Entity @Table(name="faq_element") class FaqElement { @Id @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY) Long id @Version Long version String question String answer @ManyToOne @JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false) FaqSection section static constraints = { question(size: 1..500) answer(size: 1..5000) } }
Hide
Jean-Noël Rivasseau added a comment - - edited

Humm, formatting was strange for the mapping, trying again, sorry for the noise if it fails again.

import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_section")
class FaqSection
{
    @Id
    @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
    Long id
   
    @Version
    Long version
   
    String title
   
    @OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class)
    @JoinColumn(name = "section_id", nullable = false)
    @IndexColumn(name = "position", base = 0)
    List elements
}

import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_element")
class FaqElement
{
    @Id
    @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
    Long id
   
    @Version
    Long version
   
    String question
    String answer
   
    @ManyToOne
    @JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false)
    FaqSection section
   
    static constraints =
    {
        question(size: 1..500)
        answer(size: 1..5000)
    }
}
Show
Jean-Noël Rivasseau added a comment - - edited Humm, formatting was strange for the mapping, trying again, sorry for the noise if it fails again.
import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_section")
class FaqSection
{
    @Id
    @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
    Long id
   
    @Version
    Long version
   
    String title
   
    @OneToMany(cascade = [javax.persistence.CascadeType.ALL], targetEntity = FaqElement.class)
    @JoinColumn(name = "section_id", nullable = false)
    @IndexColumn(name = "position", base = 0)
    List elements
}

import javax.persistence.*
import org.hibernate.annotations.*

@Entity
@Table(name="faq_element")
class FaqElement
{
    @Id
    @GeneratedValue(strategy = javax.persistence.GenerationType.IDENTITY)
    Long id
   
    @Version
    Long version
   
    String question
    String answer
   
    @ManyToOne
    @JoinColumn(name = "section_id", nullable = false, updatable = false, insertable = false)
    FaqSection section
   
    static constraints =
    {
        question(size: 1..500)
        answer(size: 1..5000)
    }
}
Hide
Graeme Rocher added a comment -

fixed

Show
Graeme Rocher added a comment - fixed
Hide
Graeme Rocher added a comment -

Bulk closing bunch of resolved issues

Show
Graeme Rocher added a comment - Bulk closing bunch of resolved issues

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: