-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: Problem inserting into association using Map<PK, Entity&g
PostPosted: Sat Mar 28, 2009 1:53 pm 
Newbie

Joined: Sat Mar 28, 2009 12:44 pm
Posts: 6
Location: England
I'm developing some code based on "Java Persistence with Hibernate" section 7.2.4 "Mapping Maps".

This is a One To Many parent child situation. The child collection is coded (in the parent entity class) as follows: -

@MapKey (name="batchId")
@OneToMany (cascade = CascadeType.ALL, mappedBy = "account")
@org.hibernate.annotations.Cascade
(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Map <Long, HibEntBatch> batches = new HashMap <Long, HibEntBatch> ();

In this Map the key (Long) is the Primary Key of the Child table. The Child entity is HibEntBatch.

The reverse mapping in the child entity is

@ManyToOne
@JoinColumn (name="ACCOUNT_ID", nullable=false, updatable=false)
private HibEntFinAccount account;

Children are added using an "addChild" method in the parent. The code is

public HibEntBatch addBatch (Date clDate, boolean conf)
{
HibEntBatch result = new HibEntBatch (this, clDate, conf);
batches.put (new Long (0), result);
return result;
}

This sets the reverse mapping in the child constructor: -

public
HibEntBatch (HibEntFinAccount acc, Date clDate, boolean conf)
{
account = acc;
closeDate = clDate.toJavaUtilDate ();
confirmed = conf;
}

This works fine until I try adding multiple children into the parent using parent.addBatch (...). The batches.put (...) fails (silently - no exception) because I am adding multiple children with the same key Long (0). Some of the hibernate debug output indicates that each batches.put (...) call should force a DB update resetting the key to the correct generated ID value. This is not happening.

I've tried a couple of ways to work around this problem as follows: -

Attempt 1 - Phony keys.
I tried adding a negative decrementing sequence of keys in the batches.put (..) calls as follows
Call 1 - batches.put (new Long (-1), Object A);
Call 2 - batches.put (new Long (-2), Object B);
etc.
This works and updates the table correctly. However the collection is cached with the negative key values. This messes up any future attempt to use this collection.

Attempt 2 - Add one child per transaction (key Long(0) in each case)
This exhibits the following behavior.
1st child is stored correctly.
2nd child is stored correctly.
3rd child overwrites 2nd child (child 3 added, child 2 deleted)
4th child overwrites 3rd child
etc.

Any help appreciated. I am using the following versions: -

java -version
java version "1.6.0_12"
Java(TM) SE Runtime Environment (build 1.6.0_12-b04)
Java HotSpot(TM) 64-Bit Server VM (build 11.2-b01, mixed mode)

Running on openSUSE 11.1 64 bit

hibernate-distribution-3.3.1.GA
hibernate-annotations-3.4.0.GA
hibernate-entitymanager-3.4.0.GA

Kind Regards,

Steve

_________________
Steve


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 28, 2009 5:55 pm 
Expert
Expert

Joined: Wed Mar 03, 2004 6:35 am
Posts: 1240
Location: Lund, Sweden
Quote:
Some of the hibernate debug output indicates that each batches.put (...) call should force a DB update resetting the key to the correct generated ID value. This is not happening.


I have never seen anything like this. Updates to the db may be delayed as long as until commit is called. Even if the db update should happen immediately it wouldn't work since the key in a map is not allowed to change since it will mess up the map if it does.

If you want to use the primary key as a map key I think you need to manually assign the primary key values and not rely on Hibernate for generating this.

Or, you could choose to not use the map for insertions. After all, the important thing for making things work is that you have set the HibEntBatch.account property to the correct parent item. The drawback is that you will not be able to take advantage of cascades for saving child items.


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 28, 2009 7:49 pm 
Newbie

Joined: Sat Mar 28, 2009 12:44 pm
Posts: 6
Location: England
Hi Nordberg and thanks for your comments. I'm still a newbie with Hibernate and any help is very welcome.

The Hibernate debug statements that filled me with optimism were the following: -

23:14:39,680 DEBUG [IdentifierGeneratorFactory] Natively generated identity: 9
23:14:39,682 DEBUG [AbstractBatcher] about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
23:14:39,687 DEBUG [AbstractSaveEventListener] executing identity-insert immediately

These seem to accompany each child insert.

I took the strategy of a map keyed on the primary key from the "Java Persistence with Hibernate" book as mentioned. I presumed this is the most common strategy for a collection of child entities mapped by a map. This seemed likely as the default name attribute for the @MapKey annotation is the primary key. I am / was hoping that this strategy will allow me to delete and retrieve individual children without populating the entire child collection.

Not being able to add a child with a generated primary key value seems to be a serious flaw in this strategy :-(

I previously used a Set instead of a Map. The code worked OK but deleting a child identified by its ID value meant populating the entire collection so the child with the correct ID could be located. To avoid this I tried using a JPA QL query "DELETE ... where ID = ?". This works fine but when (in another transaction) I repopulated the child collection it pulled in the old collection (from the cache? - no SQL emitted) including the children representing the deleted records.

I'm now ploughing my way through the good book to get a better understanding of object lifecycles and trying to think of a better strategy for mapping my DB.

Many thanks for your help. Any further suggestions appreciated.

Steve

_________________
Steve


Top
 Profile  
 
 Post subject:
PostPosted: Fri Apr 03, 2009 10:33 am 
Newbie

Joined: Sat Mar 28, 2009 12:44 pm
Posts: 6
Location: England
I have resolved this situation although I suspect there are better solutions.

I have kept my parent - child mapping as a Map<Child PK, Child>. This works very well for all my use cases except for child addition.

Child addition uses the following pseudo code

// Start transaction and establish persistent context
entityManager.getTransaction ().begin ();

Parent parent = new Parent () / Parent = entityManager.load (parentid);

/*
If the parent is a new object (no current DB entry exists) then it must be
persisted to generate the primary key ID. Otherwise we will have a foreign
key constraint violation when we persist the child.
*/

if (parent is new)
entityManager.persist (parent);

Child child = new Child ();
child.setParent (parent); // Now the child knows the parent foreign key
child.setProperties (...);

/*
We cannot add the child to the parent as the child ID is unknown until
after the database write. Therefore we force the database write.
*/
entityManager.persist (child);

/*
The database is now correct. However the parent is within the persistent
context but out of sync with the database. The parent does not realise
this so we need to correct the situation before the transaction is flushed.
*/
parent.addChild (child); // Adds child to parents child collection now ID is known

entityManager.getTransaction ().commit ();

This is very inelegant. Especially when the calls are wrapped up in the domain models. What I would like is the following.

entityManager.getTransaction ().begin ();
Parent parent = new Parent ();
Child child = new Child ();
parent.addChild (child);
entityManager.persist (parent);
entityManager.getTransaction ().commit ();

I don't know any way of making his code work whilst the mapping uses a Map<Child PK, Child> and the primary keys are auto-generated by the DB. Furthermore I don't know another mapping that allows for child access without loading the entire child collection. Therefore I live with the first solution.

Steve

_________________
Steve


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.