I have three domain classes: Beer, Review, and Reviewer.
I want the Review table to create a many to many relationship between Beer and Reviewer, so I want the primary key of Review to be a composite of the id fields from Beer and Reviewer. I'm following this Grails documentation.
http://grails.org/doc/latest/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.5.2.5%20Composite%20Primary%20Keys
Here are my domain classes.
class Beer {
String name
String type
Brewery breweryId
static hasMany = [ reviews : Review ]
static constraints = {
}
}
class Reviewer {
String screenName
static hasMany = [ reviews : Review ]
static constraints = {
}
}
class Review implements Serializable {
int score
Beer beer
Reviewer reviewer
static constraints = {
}
static mapping = {
id composite:['beer', 'reviewer']
}
}
I was getting compilation errors, but another answer here on stackoverflow said I needed to add implements Serializable. That took care of the error, but when I look in the database, I'm still not getting a composite primary key.
Here is what I'm seeing when I look at the table definition. I'm using Postgres.
Table "public.review"
Column | Type | Modifiers
-------------+---------+-----------
id | bigint | not null
version | bigint | not null
beer_id | bigint | not null
reviewer_id | bigint | not null
score | integer | not null
Indexes:
"review_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"fkc84ef75823f39326" FOREIGN KEY (beer_id) REFERENCES beer(id)
"fkc84ef7587c483106" FOREIGN KEY (reviewer_id) REFERENCES reviewer(id)
I'd be happy with just a composite index with a unique constraint, but I can't figure out how to do that, either. I've been able to make a non-unique composite index, but this has two problems. One, it's non-unique. Two, the columns are specified in alphabetical order in the index (beer_id, reviewer_id). I'd like to specify the order of the columns in the index.
I have implemented a similar situation, with some different conditions:
There's no hasMany relationship.
Query to the join class is done by HQL
Using a more detailed mapping
When implementing like this, the mysql database is ok. (beer_id,reviewer_id) is the primary key.
class Review implements Serializable {
Beer beer
Reviewer reviewer
static Review get(long beerId, long reviewerId) {
find 'from Review where beer.id=:beerId and reviewer.id=:reviewerId',
[beerId: beerId, reviewerId: reviewerId]
}
static boolean remove(Beer beer, Reviewer reviewer, boolean flush = false) {
Review instance = Review.findByBeerAndReviewer(beer, reviewer)
instance ? instance.delete(flush: flush) : false
}
...
static mapping = {
table "REVIEW"
id composite: ['beer', 'reviewer']
beer(column: "beer_ID")
reviewer(column: "reviewer_ID")
version false
}
}
I don't know what exactly causes your problem, but hope this gives you some hint about where the problem can be.
I took the Grails mandate that I shouldn't use Composite Primary keys as wise advice and am avoiding it. If so, I believe a viable alternative to solve your problem is the Composite Unique Constraint.
ref: http://grails.org/doc/1.1.x/ref/Constraints/unique.html
Jacco's answer seems to be not correct, although it looks visually very close to correct, here is how you'd write a composite unique constraint for this problem:
static constraints = {
beer(unique: 'reviewer')
}
whereas if the programmer wanted to link 3 db fields as unique, the correct formation is:
static constraints = {
beer(unique: ['anotherField','reviewer'])
}
which looks like Jacco's answer, but the class name is not used as the first string of the constraint, the first field name is used.
I've just used this code structure in my own project app, and it seems to be behaving correctly, see also this on how to unit test unique constraints:
http://www.ibm.com/developerworks/java/library/j-grails10209/index.html
(see listing 11)
Try this, In your domain Review domain class:
static constraints = {
review(unique: ['beer','reviewer'])
}
you might need to drop the table and let Gorm recreate it.
the above constraint means that a review must consist of a unique record of beer/reviewer combination. it is still many to many where a reviewer has multiple beers and visa versa but the reviews are unique.
Related
I have the following entities:
class ProductVariant {
int id
long sku
static hasMany = [prices: ProductPrice]
}
class ProductPrice {
int id
int priceBandId
long sku
BigDecimal price
}
I am trying to achieve a one-to-many relationship between ProductVariant and ProductPrice where the join is on the sku as opposed to the id column.
I have seen an example of how this works in Hibernate using a #NaturalId annotation on the sku column and a referencedColumnName property in the #JoinColumn annotation.
Is is possible to achieve the same thing using Grails/GORM?
The key thing here is that each store has its own set of variants with their own IDs, but the prices are set at a SKU level and would apply across all stores stocking the same SKU.
It turns out that, whilst this is fine at the database level, it's not really something you do. Grails won't let you have a hasMany association on just one part of the primary key.
Instead I am just retrieving all prices by adding a getter to my ProductVariant domain object as follows:
List<ProductPrice> getPrices() {
return ProductPrice.findAll { it.sku == this.sku }
}
and then I can just use productVariant.prices when needed.
I have legacy database and some tables have composite ids
class Client {
String id
static hasMany = [
settings: Setting
]
static mapping = {
id column: 'client_id', generator: 'assigned'
}
}
class Setting {
Client client
String nodeId
String ccyPairPattern
Character qualifier
static mapping = {
id composite: ['client', 'nodeId', 'pattern', 'qualifier']
}
}
I want to delete entry from GORM association:
client.get('1').removeFromSettings(settingToRemove)
// settingToRemove.delete(flush: true)
// delete-orphans does not help
This always raises exception after flush
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) :
This happens because method removeFrom* sets client property to null and generates query to delete with clientId = null as client is part of composite key
What is the best solution in this case. Looks like GORM has poor support for composite keys or my mapping is incorrect.
When you use hasMany without a belongsTo on the many side, in other words a unidirectional association, you get a join table. For example...
class PurchaseOrder {
static hasMany = [items: Item]
}
class Item { }
Would yield three database tables: purchase_order, item, and purchase_order_items. The purchase_order_items table would contain two columns: purchase_order_id and item_id. You can read more about join tables here.
Since you're dealing with a legacy database, I think the best solution is not to use addTo*() and removeFrom*().
Ensure you don't have a join table. If you have a join table, remove the hasMany association.
You'll need to add/remove Setting instances manually.
Example:
def client = Client.get(1)
// Adding a setting
def setting = new Setting(client: client, nodeId: blah...)
setting.save()
// Removing a setting
/*
The prototype is used to search for a domain instance with a composite primary key.
So simply set the composite key properties accordingly.
*/
def prototype = new Setting(client: client, nodeId: blah...)
def setting = Setting.get(prototype)
setting.delete()
Lacking a hasMany association, you won't be able to access a client's settings via the client.settings property. Instead you'd have to query for them like this:
def settings = Setting.findByClient(client)
A consequence of using a legacy database is that if the database doesn't align with that GORM/Hibernate expects it will be limited in what it can do for you.
I have a domain class called FoapRequest. I want one of the properties called "approver" to be a list of integers. Order matters, so I've defined the class as described by http://grails.org/doc/latest/guide/GORM.html#sets,ListsAndMaps as a list:
class FoapRequest {
Integer requester
Integer subject
List approver
static hasMany = [foap:FOAP, newFoap:NewFoap, approver:Integer]
...
Just for clarification, FOAP and NewFoap are two other domain objects.
I need to map this class to a particular table in the Oracle database, so I also specify a static mapping with a join table:
static mapping = {
table 'OUR_SCHEMA.FOAP_REQUEST_TABLE
id column : 'ID', generator:'sequence', params: [sequence:'OUR_SCHEMA.FOAP_REQUEST_SEQ']
requester column : 'REQUESTER'
subject column : 'SUBJECT'
approver indexColumn: [name: "APPROVER_IDX"], generator:'sequence', params: [sequence:'OUR_SCHEMA.APPROVER_SEQ'],
joinTable: [name:'OUR_SCHEMA.APPROVER_TABLE',
key: 'ASSOCIATED_REQUEST',
column: 'APPROVER_PIDM',
type: "integer"
]
However, when I try to create a new instance of the FoapRequest object, I get the following error:
Invalid column type
The console displays the following:
Error 2012-08-01 12:29:31,619 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver - SQLException occurred when processing request: [POST] /FOAPauth/foapRequest/saveFoapRequests - parameters:
I am certain that the issue lies with the jointable. The domain model didn't include the joinTable originally- approver was just an Integer type (I realized too late that I was going to need to track multiple approvers).
Here's the SQL for creating the APPROVERS table:
CREATE TABLE "OUR_SCHEMA"."APPROVER_TABLE"
(
"APPROVER_IDX" NUMBER(*,0) NOT NULL ENABLE,
"ASSOCIATED_REQUEST" NUMBER(*,0) NOT NULL ENABLE,
"APPROVER_PIDM" NUMBER(8),
);
I'd prefer to avoid creating an Approver domain class if at all possible, since all I really need to keep track of are the integer identifiers.
So, after much janking with join tables, I determined that the best way to deal with my needs was to simply create an Approver object in my domain model.
class Approver {
Integer pidm
String approvalDecision
Date lastUpdated
Date dateCreated
static belongsTo = [foap: FOAP]
}
To be honest, I'm not really sure why I was trying so hard to avoid this. Possibly because my DBAs use a version control system for table definitions that I find a hair annoying. :)
Regardless, a simple one-to-many relationship between domain classes met all my needs, no join table required.
For those who are still burning to know, I did manage to get a statically mapped join table working using a Map, which was more appropriate for my needs (though not as appropriate for them as a new domain class, and not nearly as simple).
I ended up doing it in a different domain object- FOAP instead of FoapRequest:
import java.util.Map
class FOAP {
...
Map approvalData
...
static mapping = {
table 'OURSCHEMA.FOAP_TABLE'
id column : 'ID', generator:'jpl.hibernate.util.TriggerAssignedIdentityGenerator'
fund column : 'FUND'
org column : 'ORG'
chartOfAccounts column : 'CHART_OF_ACCOUNTS'
permissionType column: 'PERMISSION_TYPE'
foapRequest column: 'REQUEST_ID'
version column : 'VERSION'
approvalData joinTable: [name:'OURSCHEMA.FOAP_APPROVERS',
key: 'FOAP'
]
}
For the table definition, I used the column names similar to those in my original question.
CREATE TABLE "OUR_SCHEMA"."APPROVER_TABLE"
(
"FOAP" NUMBER(*,0) NOT NULL ENABLE,
"APPROVER_IDX" VARCHAR2(255),
"APPROVER_DLT" NUMBER(8),
);
The IDX column was the map object's key, the DLT column its value. I'd recommend against this approach, for anyone who can avoid it. Creating a new domain object is much simpler.
I've been struggling to produce the right configurations to produce cascade-delete behaviour in a relatively simple Grails project.
Say I have the following simple domain classes:
class Author {
String name
static constraints = {
}
}
and
class Book {
String title
Author author
static constraints = {
}
}
If I create an author, and then create a book written by that author, I am not able to delete the Author without first manually deleting the book. I get an "Integrity constraint violation". This isn't suprising as MySQL (my underlying database) is created by Grails with a "foreign key constraint" on the "author" column of the "book" table as "Restrict" (and this behaviour is consistent with expectations from the Grails documentation as I understand it).
Now, if I were to manually change the underlying database constraint on the "author" column of the book table from "Restrict" to "Cascade", I get the behaviour I want. Namely, that if you delete the Author, all their books are also deleted.
So, what I'd like to do is change my Grails "Book" class in a way that creates the "book" table with "on delete cascade" on the author column. I've been reading plenty of information about doing this sort of thing and GORM defaults using "belongsTo" and explicit "mappings".
One way to do this, based on the documentation for "belongsTo", seemed to be to change the line in the Book class from:
Author author
to
static belongsTo = [author: Author]
Thereby making it explicit that the Author is the "owning side" of the relationship. The documentation seems to suggest that this should generate the cascade-delete behaviour I'm after. However, it doesn't work unless I add an explicit "hasMany = [books:Book]" into the Author class. I don't wish to do this. (This wish makes more sense in my actual business domain, but even just as an exercise in understanding, I don't yet get why I have to have the Author domain class know about books explicitly).
I'd just like a grails setting to change the Book class to produce the "cascade delete" setting in the database, without having to change the Author class. I tried using an explicit mapping like:
static mapping = {
author cascade: 'all'
}
and combinations of this with other explicit mapping or "belongsTo" options. This didn't work.
Noting that a simple change to the "constraints" in the underlying SQL database provides the behaviour I want, is there a way to get this through Grails? If not, have I misunderstood something fundamental here or am I trying to do something stupid?
I think you are missing the required field of type Book in Author.
Here's the sample code, which is as per the documentation (tested and works)
class Author {
String name
Book book //you are probably missing this field
static constraints = {
}
}
class Book {
String name
static belongsTo = [author: Author]
static constraints = {
}
}
Test case:
#TestFor(Author)
#Mock([Book])
class AuthorTests {
#Test
void testAuthorBookCascades() {
Author a = new Author(name: "Douglas Adams")
Book b = new Book(name: "So Long, and Thanks for all the Fish")
a.book = b
a.save()
assert Author.count() == 1
assert Book.count() == 1
a.delete()
assert Author.count() == 0
assert Book.count() == 0
}
}
As you can see, you need the Book argument in Author. No need for the hasMany or hasOne clause.
Consider the following domain classes
class Business{
static hasMany = [contacts:ContactPerson]
}
class ContactPerson{
}
Given the following domain classes, say we use the following examples:
Alice is ACME's contact person. Bob and Carol are Calamity Corp's contact person.
Say I wanted to remove Bob in the ContactPerson table. Thus:
bob.delete(flush:true)
But the code will result to the server complaining about contraints:
ERROR: update or delete on table "contact_person" violates foreign key constraint
"fk4a69c6b329ef2fe1" on table "business_contact_person"
Detail: Key (id)=(174) is still referenced from table "business_contact_person".
In this context, the exception is thrown because Bob is still associated with Calamity Corp.
How do I delete Bob from the ContactPerson table? I wanted to remove Bob from the database altogether. I don't want to add belongsTo since I don't want to cascade the delete to Business' children (that is to say, if I delete Acme Corp from the database, I still want Alice to be in the system).
I've seen examples on disassociating the parent from the child but not the other way around.
I believe by simply adding a belongsTo, the cascade should work as expected...
class Business{
static hasMany = [ contacts:ContactPerson ]
}
class ContactPerson{
static belongsTo = [ business: Business ]
}
Ok. So with a little more digging, I finally got the codes that I wanted. John Rellis' post on Relationship Advice : Grails One-To-Many was especially helpful. In this article, he mentions how to query from children to parent (which is exactly what I am looking for).
Going back to the question, I wanted to remove Bob from the ContactPerson table but given the relationship between Business and ContactPerson simply calling bob.delete() simply would not do. What I need is to look for all businesses associated with Bob and remove the association. Thus the code below:
def bob = ContactPerson.get(params.id)
def criteria = Business.createCriteria()
def businesses = criteria.listDistinct{
createAlias("contactPersons","c")
eq("c.id", bob.id)
}
businesses.each{business->
business.removeFromContactPersons(bob)
business.save(flush:true)
}
bob.delete(flush:true)
I also added a new mapping to the Business domain model:
static mapping = {
children cascade:"all-delete-orphan"
}