This has now been raised as a new query after all my explorations today which I believe is a defect - please read the newer post first.
I have an RootEntity domain class and an EntityRelationship entity (I have separate explicit entity here as I want the relationship to have attributes of its own.
I want two collections of unidirectional links from RootEntity to EntityRelationship like this
A --references --> link (+attribs) --referenced by --> B
Code:
abstract class RootEntity {
Long id
String name
LocalDateTime dateCreated
LocalDateTime lastUpdated
Collection<? extends EntityRelationship> entityReferences = []
Collection<? extends EntityRelationship> entityReferencedBy = []
static hasMany = [entityReferences: EntityRelationship, entityReferencedBy: EntityRelationship]
/* doesn't appear to be required - mucks up the test
static mappedBy = [
entityReferences : "references",
entityReferencedBy : "referencedBy"
]*/
static mapping = {
tablePerHierarchy false //multiple tables+joins
}
static constraints = {
entityReferences nullable:true
entityReferencedBy nullable:true
}
}
And my EntityRelationship object has two discrete ForKeys - one for referenced (entity that owns the 'link' , and other referencedBy for entities at remote end, like this:
class EntityRelationship<M extends RootEntity, N extends RootEntity> {
String relationshipType
String name
String owningRole
String referencedRole
M references //fk to owning entity
N referencedBy //fk to other end
static mappedBy = [references: "none", referencedBy: "none"] //seems to need this here - but not in RootEntity
static constraints = {
relationshipType unique:true, nullable:true
name nullable:true
owningRole nullable:true
references nullable:true
referencedRole nullable:true
referencedBy nullable:true
}
}
In order to get my test to work correctly - integration test snippet looks like this
...
EntityRelationship <Device, Device> rel = new EntityRelationship()
rel.name = "i need a PE"
rel.owningRole = "i need this PE "
rel.referencedRole = "i am supporting "
ce.addToEntityReferences(rel)
pe.addToEntityReferencedBy(rel)
rel.save(failOnError:true)
assert ce.entityReferences.size() == 1
assert ce.entityReferencedBy.size() == 0
assert pe.entityReferences.size() == 0
assert pe.entityReferencedBy.size() == 1
So reading the the examples I though I had to have the mappedBy closure on the one to many side (rootEntity) - to tell hibernate which column in EntityLink was to be updated.
However if I tried that in the test code - it would tell me that the pe.referencedBy.size() was 2 not 1 as expected and fail. I looked at the ce and pe and rel in the debugger and they looked right in memory - but the assert for the pe.referencedBy fails.
In order to fix this what seems to work is to comment out the mappedBy in the RootEntity that defines the one-to-many collections, and instead put a mappedBy clause on the many (EntityLink) table withe mapped field:none.
Now when I run the test this will work.
This looks 'counter' to what the gorm example suggests (flights and airports example) where it says the mappedBy should be on the flights entity.
So clearly I've not understood the fine points of mappedBy. Why does my working code has to have the mappedBy on the many side?
Correction and addenda
Actually this is weirder than I thought
PS: stepped it through slowly in debugger and I do need the mappedBy on the RootEntity. This ensures that the call to addToReferences() and addToReferencedBy() correctly update the correct attribute in EntityRelationship - revised form now looks like
RootEntity.groovy
abstract class RootEntity {
//id provided default by grails implicit in infrastructure - shown explicitly here
Long id
String name
LocalDateTime dateCreated
LocalDateTime lastUpdated
Collection<? extends EntityRelationship> entityReferences = []
Collection<? extends EntityRelationship> entityReferencedBy = []
static hasMany = [entityReferences: EntityRelationship, entityReferencedBy: EntityRelationship]
static mappedBy = [
entityReferences : "references", //map entityReferences to EntityRelationship.references
entityReferencedBy : "referencedBy" //map entityReferencedBy to EntityRelationship.referencedBy
]
static mapping = {
tablePerHierarchy false //multiple tables+joins
}
static constraints = {
entityReferences nullable:true
entityReferencedBy nullable:true
}
}
EntityRelationship.groovy is looking like this
class EntityRelationship<M extends RootEntity, N extends RootEntity> {
String relationshipType
String name
String owningRole
String referencedRole
M references //fk to owning entity
N referencedBy //fk to other end
static mappedBy = [references: "none", referencedBy: "none"] //seems to need this here - but not in RootEntity
static constraints = {
relationshipType unique:true, nullable:true
name nullable:true
owningRole nullable:true
references nullable:true
referencedRole nullable:true
referencedBy nullable:true
}
static EntityRelationship createRelationship(String name, M from, N to) {
if (from == null || to == null)
return null
EntityRelationship rel = new EntityRelationship()
rel.name = name
rel.owningRole = from.name
rel.referencedRole = to.name
from.addToEntityReferences (from)
to.addToEntityReferencedBy (to)
rel.save(failOnError: true)
rel
}
}
However this doesn't work as expected. This is the weird bit.
I set two break points one just before rel.save() and one just after it.
Section of my integration test:
...EntityRelationship <Device, Device> rel = new EntityRelationship()
rel.name = "i need a PE"
rel.owningRole = "i need this PE "
rel.referencedRole = "i am supporting "
ce.addToEntityReferences(rel)
pe.addToEntityReferencedBy(rel)
rel.save(failOnError:true) //set Breakpoint 1
assert ce.entityReferences.size() == 1 //set breakpoint 2
assert ce.entityReferencedBy.size() == 0
assert pe.entityReferences.size() == 0
assert pe.entityReferencedBy.size() == 1
If I run the test with no debug - test fails with pe.entityReferencedBy.size() returned as 2.
If I run the debugger and step immediately past the first break point and onto the second, then look at the pe.entityReferencedBy collection it has two items in it assert then fails.
However if I stop at the first breakpoint, and inspect pe.entityReferencedBy collection before the rel.save() then the referencedBy collection has one entry. you step though the save and check again - still ok. When run to completion the test works!
So running without debug/or checking after rel.save() then the answer is wrong.
If I stop and check pe.entityReferencedBy before I call rel.save() the answer is correct 1 entry only.
Why does this happen, and more importantly why does it fail at all with no debugger?
Addenda 2
My last go for tonight - replaced the addToMethod calls and set up by hand like this
//ce.addToEntityReferences(rel)
//pe.addToEntityReferencedBy(rel)
ce.entityReferences << rel
rel.references = ce
//pe.entityReferencedBy << rel
rel.referencedBy = pe
rel.save(failOnError:true)
So if I uncover pe.entityReferencedBy << rel and run the save - I seem to get two entries in referencedBy collection on the PE. If I add the rel to the ce instance, set the rel.rel.referencedBy = pe, but exclude adding to the pe's entityReferencedBy collection - the test says its working.
Now I am confused when I trigger the rel.save() action that appears to be doing an auto insert back into pe.entityReferenced collection (but doesn't do an extra update back on the ce.