Shouldn't Hibernate at least produce some kind of query for the join-table? Jugding from the Hibernate logs and the MySQL/HSQLDB logs no such query is created. Since i don't know how a working many-to-many unidirectional mapping should look like, maybe somebody could tell me?
I know changed the cascading style of the many-to-many to nothing to not have any differences to the example of the Hibernate references:
Code:
<class name="Parent">
<id column="PARENT_ID" name="id" type="long">
<generator class="native"/>
</id>
<set name="children" table="ParentChild">
<key column="PARENT_ID"/>
<many-to-many class="Child" column="CHILD_ID" unique="true"/>
</set>
</class>
Therefor i now save the Child-objects by hand before i save the Parent-object.
Here is what Hibernate is doing. This is the place the save is performed:
Code:
DefaultSaveEventListener(AbstractSaveEventListener).performSaveOrReplicate(Object, EntityKey, EntityPersister, boolean, Object, EventSource, boolean) line: 259
DefaultSaveEventListener(AbstractSaveEventListener).performSave(Object, Serializable, EntityPersister, boolean, Object, EventSource, boolean) line: 204
DefaultSaveEventListener(AbstractSaveEventListener).saveWithGeneratedId(Object, String, Object, EventSource, boolean) line: 130
DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) line: 210
DefaultSaveEventListener.saveWithGeneratedOrRequestedId(SaveOrUpdateEvent) line: 56
DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).entityIsTransient(SaveOrUpdateEvent) line: 195
DefaultSaveEventListener.performSaveOrUpdate(SaveOrUpdateEvent) line: 50
DefaultSaveEventListener(DefaultSaveOrUpdateEventListener).onSaveOrUpdate(SaveOrUpdateEvent) line: 93
SessionImpl.fireSave(SaveOrUpdateEvent) line: 705
SessionImpl.save(String, Object) line: 693
SessionImpl.save(Object) line: 689
Code:
if ( substitute ) {
persister.setPropertyValues( entity, values, source.getEntityMode() );
Here Set of my parent object becomes a
PersistentSet which holds the Child-objects as expected. Also the Child-objects have an identifier as expected as they now get persisted before the Parent-object gets persisted.
Code:
if ( useIdentityColumn ) {
EntityIdentityInsertAction insert = new EntityIdentityInsertAction(
values, entity, persister, source, shouldDelayIdentityInserts
);
if ( !shouldDelayIdentityInserts ) {
log.debug( "executing identity-insert immediately" );
source.getActionQueue().execute( insert );
id = insert.getGeneratedId();
key = new EntityKey( id, persister, source.getEntityMode() );
source.getPersistenceContext().checkUniqueness( key, entity );
}
Here the
useIdentityColumn is true and
shouldDelayIdentityInserts is false, therefor Hibernate executes the insert in this branch.
From there the journey continues to the AbstractEntityPersister:
Code:
SingleTableEntityPersister(AbstractEntityPersister).insert(Object[], Object, SessionImplementor) line: 2836
EntityIdentityInsertAction.execute() line: 71
ActionQueue.execute(Executable) line: 268
DefaultSaveEventListener(AbstractSaveEventListener).performSaveOrReplicate(Object, EntityKey, EntityPersister, boolean, Object, EventSource, boolean) line: 321
Code:
if ( entityMetamodel.isDynamicInsert() ) {
// For the case of dynamic-insert="true", we need to generate the INSERT SQL
boolean[] notNull = getPropertiesToInsert( fields );
id = insert( fields, notNull, generateInsertString( true, notNull ), object, session );
for ( int j = 1; j < span; j++ ) {
2836: insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
}
}
else {
// For the case of dynamic-insert="false", use the static SQL
id = insert( fields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
for ( int j = 1; j < span; j++ ) {
insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
}
isDynamicInsert() is false so the else branch is taken. The paremeter have the following values:
fields still contain the PersistentSet with the expected Child-objects.
getPropertyInsertability() (notNull parameter) is true for the children.
getSQLIdentityInsertString() (sql parameter) is "insert into Parent values ( )"
And
session is: SessionImpl(PersistenceContext[entityKeys=[EntityKey[components.Child#1]],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[]])
Maybe the ActionQueue is too empty?Inside the insert-statement the following statement in AbstractEntityPersister on line 2329 is called:
Code:
return identityDelegate.performInsert( sql, session, binder );
Then it goes straight for the insert to:
Code:
DelegatingPreparedStatement.executeUpdate() line: 102
IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(PreparedStatement) line: 94
IdentityGenerator$GetGeneratedKeysDelegate(AbstractReturningDelegate).performInsert(String, SessionImplementor, Binder) line: 57
SingleTableEntityPersister(AbstractEntityPersister).insert(Object[], boolean[], String, Object, SessionImplementor) line: 2329
There the new Parent is inserted. The journey goes back to SingleTableEntityPersister(AbstractEntityPersister).insert(Object[], Object, SessionImplementor) line: 2836:
Code:
2836: id = insert( fields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
for ( int j = 1; j < span; j++ ) {
insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
span here is 1 so the for-loop doesn't do anything. from here it goes back to EntityIdentityInsertAction.execute() line: 71
Code:
if ( !veto ) {
71: generatedId = persister.insert( state, instance, session );
if ( persister.hasInsertGeneratedProperties() ) {
persister.processInsertGeneratedProperties( generatedId, instance, state, session );
}
//need to do that here rather than in the save event listener to let
//the post insert events to have a id-filled entity when IDENTITY is used (EJB3)
persister.setIdentifier( instance, generatedId, session );
}
persister.hasInsertGeneratedProperties() is false, therefor nothing is done there.
Afterwards
postInsert() is done:
Code:
if ( isDelayed ) {
getSession().getPersistenceContext().replaceDelayedEntityIdentityInsertKeys( delayedEntityKey, generatedId );
}
PostInsertEventListener[] postListeners = getSession().getListeners()
.getPostInsertEventListeners();
if (postListeners.length>0) {
PostInsertEvent postEvent = new PostInsertEvent(
getInstance(),
generatedId,
state,
getPersister(),
(EventSource) getSession()
);
for ( int i = 0; i < postListeners.length; i++ ) {
postListeners[i].onPostInsert(postEvent);
}
}
isDelayed is false and
(postListeners.length>0) too. Therefor nothing is done here.
Maybe this is the place the join-table ought to be filled?After this it goes back to ActionQueue.execute(Executable) line: 271
Code:
finally {
271: beforeTransactionProcesses.register( executable.getBeforeTransactionCompletionProcess() );
if ( session.getFactory().getSettings().isQueryCacheEnabled() ) {
final String[] spaces = (String[]) executable.getPropertySpaces();
afterTransactionProcesses.addSpacesToInvalidate( spaces );
session.getFactory().getUpdateTimestampsCache().preinvalidate( executable.getPropertySpaces() );
}
afterTransactionProcesses.register( executable.getAfterTransactionCompletionProcess() );
}
executable.getBeforeTransactionCompletionProcess() returns null.
isQueryCacheEnabled() is false too.
executable.getAfterTransactionCompletionProcess() is null.
Then it goes back to DefaultSaveEventListener(AbstractSaveEventListener).performSaveOrReplicate(Object, EntityKey, EntityPersister, boolean, Object, EventSource, boolean) line: 348
Code:
if ( !useIdentityColumn ) {
source.getActionQueue().addAction(
new EntityInsertAction( id, values, entity, version, persister, source )
);
}
cascadeAfterSave( source, persister, entity, anything );
useIdentityColumn is true therefor the if-statement is jumped over.
cascadeAfterSave also does nothing because cascading isn't used anymore.
Then it goes back to SessionImpl.fireSave(SaveOrUpdateEvent) line: 704
Code:
private Serializable fireSave(SaveOrUpdateEvent event) {
errorIfClosed();
checkTransactionSynchStatus();
SaveOrUpdateEventListener[] saveEventListener = listeners.getSaveEventListeners();
704: for ( int i = 0; i < saveEventListener.length; i++ ) {
saveEventListener[i].onSaveOrUpdate(event);
}
return event.getResultId();
}
saveEventListener.length is 1 therefor the for-loop ends here.
And that's it. Where should have Hibernate did something different? Please help me i'm really stuck on this problem. I don't know what to do anymore and this is already the 6. day of fighting.