Monday, February 15, 2010

EJB with EclipseLink: How to Synchronize to the Database the Objects that were Removed from a List Attribute while the Entity is in Detached State

When developing web applications, it is likely that we will deal with entity objects in detached state (unmanaged state) while working with them in the web layer.
In my case, I have an AcctgEntry object that has a list of AcctgLine objects as an attribute.
public class AcctgEntry{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_HEADER_SEQ_GEN")
    @Column(name = "GL_TRANS_HEADER_ID", nullable = false)
    private Long id;

 
    @OneToMany(mappedBy = "acctgEntry", cascade = CascadeType.ALL)
    private List<AcctgLine> acctgLineList = new ArrayList<AcctgLine>();
    
    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}
public class AcctgLine{
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_DETAIL_SEQ_GEN")
    @Column(name = "GL_TRANS_DETAIL_ID", nullable = false)
    private Long id;

    @Column(name = "LINE_NO", nullable = false)
    private Long lineNo;
    
    @ManyToOne
    @JoinColumn(name = "GL_NATURAL_ACCT_ID", nullable = false)
    private NaturalAcct naturalAcct;

    @ManyToOne
    @JoinColumn(name = "GL_TRANS_HEADER_ID")
    private AcctgEntry acctgEntry;

    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}

AcctgEntry objects can be created, saved and can be edited multiple times as long as the entry is not yet posted. My web interface is bound to a model which is queried from a stateless session bean. In this scenario, my model object is in detached state.
Sample case:
A user created an entry with the following AcctgLine objects:
acctgEntry JV#001
    -acctgLine1  DR100  
    -acctgLine2         CR100
When the user invoked Save, my service class verify if the Debit and Credit are balanced, and indeed it is, so the the entry was persisted to the database.
Now the problem is this. The user edited the acctgEntry, he removed the -acctgLine2 and and added two additional acctgLines as follows:
acctgEntry JV#001
    -acctgLine1  DR100
    -acctgLine3         CR50
    -acctgLine4         CR50
The user again invoked Save and my service class verify if the entry is balanced and and again succeeded, so the entry was merged to the database. But when I requery the acctgEntry that was updated, it has now as follows:
acctgEntry JV#001
    -acctgLine1  DR100
    -acctgLine2         CR100
    -acctgLine3         CR50
    -acctgLine4         CR50
I was puzzled because the -acctgLine2 was not removed from the database, and now my entry is imbalanced (Total debits 100, but the totals credits is 200).

I check on the internet and noted that this is a valid behavior based on the jpa specification. You can read the discussion here: JPA OneToMany not deleting child

With further research, I am happy to solved my issue with just a single additional persistence annotation- @PrivateOwned. More about this annotation here: How to Use the @PrivateOwned Annotation. With this annotation, whenever acctgLines were removed from the AcctgLine list attribute on my AcctgEntry object, the changes will be synchronized to the database upon merge.
My AcctgEntry class is now something like below:
public class AcctgEntry{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "GL_TRANS_HEADER_SEQ_GEN")
    @Column(name = "GL_TRANS_HEADER_ID", nullable = false)
    private Long id;

    @PrivateOwned
    @OneToMany(mappedBy = "acctgEntry", cascade = CascadeType.ALL)
    private List<AcctgLine> acctgLineList = new ArrayList<AcctgLine>();
    
    //plus other attributes
    //plus getters and setters
    //plus other relevant methods

}

Cheers!

No comments:

Post a Comment