Given Domain Classes like these:
class A { // in reality this is a basic User class that is required in multiple projects
}
class B extends A { // in reality B is a "patient"-kind of user.
static hasMany = [c: C]
}
// c/d is stuff like "MedicationPrescription", so basically data only relevant to the patient. However the system needs to realize that Patients are Users, as the User base class is used for spring security logins and in general has a lot of the basic data a person just has. (Name, etc.)
class C {
static belongsTo = [b: B, a: A, d: D]
}
class D {
}
I get this error:
org.hibernate.MappingException: Foreign key (FK_pwu2w72ul5a5213husrv3onr3:c [])) must have same number of columns as the referenced primary key (a [id])
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:110)
at org.hibernate.mapping.ForeignKey.alignColumns(ForeignKey.java:93)
at org.hibernate.cfg.Configuration.secondPassCompileForeignKeys(Configuration.java:1818)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1741)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1426)
at org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration.secondPassCompile(HibernateMappingContextConfiguration.java:287)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.grails.orm.hibernate.cfg.HibernateMappingContextConfiguration.buildSessionFactory(HibernateMappingContextConfiguration.java:196)
at org.grails.orm.hibernate.HibernateMappingContextSessionFactoryBean.doBuildSessionFactory(HibernateMappingContextSessionFactoryBean.java:476)
at org.grails.orm.hibernate.HibernateMappingContextSessionFactoryBean.buildSessionFactory(HibernateMappingContextSessionFactoryBean.java:470)
at org.grails.orm.hibernate.HibernateMappingContextSessionFactoryBean.afterPropertiesSet(HibernateMappingContextSessionFactoryBean.java:93)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 41 common frames omitted
The error goes away if I remove the inheritance between A and B, but I havee this constellation in my Domain. I don't understand why Gorm does what it does, from the error message it seems to think the list of attributes in the foreign key is empty?
Additionally I cannot even mention classes B, C or D in class A, as class A is part of a plugin that just doesn't know about those classes.
EDIT:
I might just do away with the inheritance and use composition instead like this:
class B {
A a
static hasMany = [c: C]
}
That doesn't blow up on startup at least, but still: Why?
You can take care of your domain class constellation like this:
class User { }
class Patient extends User {
static hasMany = [prescriptions: MedicationPrescription, stuff: OtherStuff]
}
class MedicationPrescription {
static belongsTo = [patient: Patient]
}
class OtherStuff {
static belongsTo = [patient: Patient]
}
This creates a bi-directional one-to-many association between Patient and MedicationPrescription and also between Patient and OtherStuff. Notice that each belongsTo has a corresponding hasMany.
As you've already discovered, you can also use composition.
Related
I have 2 domain classes
class A {
static hasMany = [ b : B ]
}
class B {
static belongsTo = A
}
I would like to keep the cascading saves, so when I save A, it updates the B's, but when I do a delete of A, I would like it to fail if any B's exist that are associated with that A. So you would have to explicitly delete all of the B's first.
I'm not sure the easiest way to do this in Grails. I could put a check in A before I delete it to verify that there are no B's - simple enough. But is there a way to control this through cascading or relationship behaviors so I don't have to put the logic in there?
Specify the cascade behavior for the collection
class A {
static hasMany = [ b : B ]
static mapping = {
b cascade: 'save-update'
}
}
It will cascade save and update but not delete.
I have the following domain classes:
class A {
hasMany = [bs : B]
}
class B { }
Note that B has no backward relation to a. GORM creates a join table in my MYSQL database a_b. This table has two columns the id of a and the id of b.
How can I get a dateCreatedin the join table?
Create a model of the join table yourself and add whatever properties you want on it. Simple, and done.
For example:
class A {
static hasMany = [bs: JoinB]
}
class JoinB {
static belongsTo = [a: A]
B b
Date dateCreated
static mapping = {
autoTimestamp true // default, but I like to be explicit about it.
}
}
class B {
String whatever
}
(Careful of typos etc. I just did that off the top of my head)
I have, for example, the following domain objects:
class A {
B b
static constraints = {
b nullable: true
}
}
class B {
}
Given instance of A, I would like to fetch only the id of B.
I tried the following, but received null every time:
def id = a.bId
Is it possible to fetch the id of b without doing a.b.id ?
You will need to enhance your domain to use GORM based mapping hints (given to Hibernate) to accomplish this.
Your domain could look something like this:
class A {
static hasOne = [b: B]
}
class B {
// stuff
}
Using the hasOne will allow Hibernate to manage the association and thus allows you to use the a.bId notation.
Hope this helps.
I have a question about GORM and "multiple" hasmany relationships, and I didn't find an answer in my previous searches.
Let's say we have three domains:
class A {
...
static hasMany = [Bs: B]
}
and
class B {
...
static belongsTo = A
static hasMany = [Cs: C]
}
and
class C {
static belongsTo = B
String name
dateCreated date
}
I want to know if it is possible to get a list of objects of the class C, sorted by dateCreated, using an object of the class A (something like C.findAll(...., a: a.id) ) or if I have to use a more complex query ?
Best regards,
Its a little more difficult because you aren't storing a back reference to the parent objects
Something like this - I didn't test it myself
A.executeQuery("select distinct c from A a join a.bs as b join b.cs as c where a = :a", [a: a])
If B has:
static belongsTo = [a:A]
then you can do:
C.withCriteria {
a{
eq('id', <a's id here>)
}
order('dateCreated', 'desc')
}
Using ArgoUML, I very quickly created this trivial representation of a few Domain classes (Person, Store, Product) and their relationships.
I'm struggling with the implementation of the relationships. Below was my initial approach for the Person domain, but it seems that I am missing something important.
class PersonToPerson {
Person from
Person to
String relation
static constraints = {
relation(inList:["Friend to", "Enemy of", "Likes", "Hates"])
}
static belongsTo = [ Person ]
}
class Person {
String firstName
String secondName
.
.
.
static hasMany= [ personToPerson:PersonToPerson,
personToStore:PersonToStore ]
}
Edit: updated question for clarity
After thinking on the problem I think I have a better way to ask the question(s). In the implementation of PersonToPerson above I have the relation as a simple string. I want the user to be able to select from a list of unique relations, which are defined in the constraints, for the string value for PersonToPerson. So this leads to the questions...
Should personToPerson and personToStore be consolidated into one list of type Relationship? Or should they stay independent lists as shown?
What is the mechanism to allow the user to add new values to the relation constraint?
1) Domain model
Keep your code simple. Don't create generic data model. It's way to hell. When you personToPerson and personToStore keep separate, it's much easier to follow your code.
Actually suggested solution makes it possible to access relations as consolidated and independent list simultaneously.
For this problem I would use inheritance feature in GORM.
Your classes would look like this:
class Person {
String name
static hasMany = [personToPerson:PersonToPerson,
personToProduct:PersonToProduct,
personToStore:PersonToStore]
static mappedBy = [personToPerson:"from"]
}
class Product{
String productName
}
class Relationship{
String note
}
class Store{
String storeName
}
class PersonToPerson extends Relationship{
Person from
Person to
String relation
static constraints = {
relation(inList:["Friend to", "Enemy of", "Likes", "Hates"])
}
static belongsTo = [ from:Person ]
}
class PersonToProduct extends Relationship{
Person person
Product product
String relation
static constraints = {
relation(inList:["likes", "dislikes"])
}
static belongsTo = [ person:Person ]
}
class PersonToStore extends Relationship{
Person person
Store store
String relation
static constraints = {
relation(inList:["Stock person", "Owner", "Manager", "Patron"])
}
static belongsTo = [ person:Person ]
}
DB schema for Person, Product and Store is usual. But for Relational domains look like this:
Relationship
Field Type Null Default
id bigint(20) No
version bigint(20) No
note varchar(255) No
class varchar(255) No
person_id bigint(20) Yes NULL
product_id bigint(20) Yes NULL
relation varchar(8) Yes NULL
from_id bigint(20) Yes NULL
to_id bigint(20) Yes NULL
store_id bigint(20) Yes NULL
Relationship domain makes possible to access all relational domain throw one domain.
2) Constraint
Just switch inList to validator. Than you can store constrain in file or DB.
See documentation or file example.
Example how to store constraint values in DB. First create a domain object.
class Constrain{
String name
String type
}
Than the domain class looks:
class PersonToPerson extends Relationship{
Person from
Person to
String relation
static constraints = {
relation(nullable:false, validator:{val, obj ->
def list = Constrain.findAllByType('person').collect{it.name}
return list.contains(val)
})
}
static belongsTo = [ from:Person ]
}
Look fine. You may want to consider a belongsTo in the PersonToPerson class.
Also, your has many in Person should be: [ personToPersons:PersonToPerson.... <- remove the s