Issue Details (XML | Word | Printable)

Key: GRAILS-2607
Type: Bug Bug
Status: Resolved Resolved
Resolution: Fixed
Priority: Critical Critical
Assignee: Graeme Rocher
Reporter: Jean-Noël Rivasseau
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
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)

Created: 06/Mar/08 02:47 PM   Updated: 12/Mar/08 09:29 AM
Component/s: None
Affects Version/s: 1.0
Fix Version/s: 1.0.2

Time Tracking:
Not Specified

Environment: Linux JDK 1.6


 Description  « Hide
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.



 All   Comments   Work Log   Change History      Sort Order: Ascending order - Click to sort in descending order
Jean-Noël Rivasseau added a comment - 07/Mar/08 08:54 AM
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) }

}


Jean-Noël Rivasseau added a comment - 07/Mar/08 08:56 AM - 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)
    }
}

Graeme Rocher added a comment - 12/Mar/08 09:29 AM
fixed