Can I recover from association (joinTable) with reference to missing data - grails

I've read through a lot of posts and docs and must be missing something.
In my application (model below) I am having a data issue that seems to be out of my control where I have a categoryId in the join table JOBORDERCATEGORIES that has no corresponding row in the CATEGORY table. I am accessing the category data through getJobCategories() in the JobOrder. This is producing the following error when my Category table is missing a referenced row:
2012-03-07 08:02:10,223 [quartzScheduler_Worker-1] ERROR listeners.SessionBinderJobListener - Cannot flush Hibernate Sesssion, error will be ignored
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.matrixres.domain.Category#416191]
and my code is halting.
I have tried using ignoreNotFound but it is not helping me to get past the error above.
If I missed a post on the solution to this problem please link me to it otherwise thoughts are welcome on how to move forward. Perhaps there is a more direct route I will have to hammer in to achieve my goal of getting a good category list, but I am not familiar enough with the framework to know what is next. As a note, I cannot write to any of these tables.
thanks, rich
A simplified Version of my Model:
Job Order Object:
class JobOrder {
def getJobCategories() {
def cats = []
try {
def jocategories = this.categories
if(!jocategories.isEmpty() && jocategories!=null){
println "we got categories for ${this.id}"
jocategories.each { cat ->
if(cat?.parentCategoryID == 0){
if(cat.occupation != null){
cats << cat.occupation
} else {
cats << cat.name
}
}
}
}
} catch(e) {
cats << "Other Area(s)"
}
cats
}
static mapping = {
table 'dbo.JOBORDER'
version false
id generator: 'identity', column: 'JOBORDERID'
/*
* several other mapped columns deleted here
*/
categories joinTable:[name:'jobOrderCategories', column: 'categoryId', key:'jobOrderID']
}
/*
* several properties deleted here
*/
static hasMany = [categories: Category] //several other hasMany associations exist
}
Category Object:
class Category {
static mapping = {
table 'CATEGORY'
version false
id generator: 'identity', column: 'categoryID'
occupation column: 'OCCUPATION'
name column: 'NAME'
parentCategoryID column: 'PARENTCATEGORYID'
/*
* several other mapped columns deleted here
*/
jobOrders joinTable:[name:'jobOrderCategories', column: 'jobOrderID', key:'categoryId']
}
String name
String occupation
int parentCategoryID
/*
* several properties deleted here
*/
static belongsTo = [JobOrder]
static hasMany = [jobOrders:JobOrder]
}
Join Table:
class JobOrderCategories {
static mapping = {
table 'JOBORDERCATEGORIES'
version false
isDeleted column: 'ISDELETED'
jobOrderID column: 'JOBORDERID'
categoryId column: 'CATEGORYID'
}
Boolean isDeleted
Integer jobOrderID
Integer categoryId
}

These kinds of situations aren't the most fun, but I have had to deal with this kind of Roll-Your-Own ORM problems before ;) Basically what you're going to want to do here is store the object properties not typed as Object references, but as ints, and while you'll lose some of the dynamic finder things GORM makes so nifty, you'll have a fairly straight-forward means of accessing the data that doesn't involve tangling yourself up with Hibernate's innards.
Basically, this will involve ditching your hasMany and belongsTo properties on JobOrder and Category. Instead, you'll want to do things like
def myJobOrder = JobOrder.get(yourId);
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(myJobOrder.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
You can put variations of those traversals in helper methods on your classes, like for example, your getJobCategories method could become
class JobOrder {
//...
def getJobCategories() {
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(this.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
}
}
And so on. This is definitely not the prettiest thing in the world to deal with, and you lose your ability to traverse through things easily with GORM (ex a
jobOrder.withCriteria {
categories {
eq('name', blah)
}
}
becomes a executeQuery type of situation.)
But overall, its not too bad to deal with :)
Hope that helps!

Related

Duplicates to be allowed in the mapping table for many to many relationship with extra column

I have two domain classes with many to many relationship with extra column.I created below domain classes by following the logic from the forums and still face an issue in saving the data in additional domain class.Roylaty is the additional column to save the value in the mapping table.
Below are the 3 domain classes:
class AuthorBook implements Serializable {
Author author
Book book
String royalty
boolean equals(other) {
if (!(other instanceof AuthorBook)) {
return false
}
other.author?.id == author?.id &&
other.book?.id == book?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (author) builder.append(author.id)
if (book) builder.append(book.id)
builder.toHashCode()
}
static AuthorBook get(long authorId, long bookId) {
find 'from AuthorBook where author.id=:authorId and book.id=:bookId',
[authorId: authorId, bookId: bookId]
}
static AuthorBook create(Author author, Book book, boolean flush = false) {
new AuthorBook(author: author, book: book).save(flush: flush, insert: true)
}
}
class Author implements Serializable{
string name(nullable:false,unique:true)
Set<Book> getBooks() {
AuthorBook.findAllByAuthor(this).collect { it.book } as Set
}
}
class Book implements Serializable{
string title(nullable:false,unique:true)
Set<Author> getAuthors() {
AuthorBook.findAllByBook(this).collect { it.author } as Set
}
}
In one of my controllers i wrote the below logic:
def author1 = new Author("ABC")
author.save(flush:true)
def book1= new Book("GORM")
book.save(flush:true)
def authorBook = new AuthorBook(royalty:100,author:author1,book:book1)
authorBook.save(flush:true)
For both author and book, it works as expected i.e it won't allow duplicates and in the mapping table too. it won't allow duplicates but I want the output to be as below in the mapping table
Author AuthorBook Book
id Name id author_id book_id royalty id title
1 XYZ 1 1 1 500 1 Gorm
2 1 1 1000
It won't save this value as it is considering the combination of author_id and book_id to be unique even though I did not set any composite key on id's in the mapping table.
What should I change in the mapping table to allow duplicates?
Can you manually insert that row into the database? I suspect this is caused by your implementation of equals and hashcode on AuthorBook.
These two objects are the same:
author=1;book=1;royalty=100 and author=1;book=1;royalty=500 because your equality methods are only comparing author and book.

GORM or HSQL for left join exclusive

I have Course domain,
Course has one teacher or null
I want to find all courses which either has no teacher or teacher.id != :loginId
How can I write query using GORM dynamic find* methods
Or write it using HSQL
- My teacher property is User domain
Appreciate your help
hasOne Class Structure
class Course {
User teacher
static hasOne = [
teacher: User
]
}
class User {
// implicit id field
}
Using HQL
def getCourses(def loginId) {
return Course.executeQuery("""
SELECT
c
FROM
Course c
LEFT OUTER JOIN c.teacher as t
WHERE
(t.id = NULL OR t.id != :logIn)
""", [loginId: loginId])
}
Using CriteriaBuilder
import org.hibernate.criterion.CriteriaSpecification
def getCourses(def loginId) {
return Course.createCriteria().list{
createAlias(
"teacher",
"t",
CriteriaSpecification.LEFT_JOIN
)
or {
ne("t.id", loginId)
isNull('t.id')
}
}
}
I'm going off of past experiences so I haven't tested your exact scenario, but I believe both options should work. I'm under the impression that a grails dynamic finder would not work in this case because of the nested condition you need (course.teacher.id != loginId).

How to save addTo in order by given date/id?

I need some help on my API, when I'm on web, the order is saving correct, but when its on API, it goes all wrong:
def test = parseJSON.sort { a, b -> a.ID <=> b.ID } //or dateTime, will print the same
//order when I print each of them
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:1, ID:1, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:2, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:0, ID:3, ph:0, redoxPotential:0, temperature:0]
[IDWeb:0, conductivity:0, ReportId:2, dissolvedOxygen:0, levelWater:4, ID:4, ph:0, redoxPotential:0, temperature:0]
test.each{
def sample = new SampleWater()
sample.levelWater = it.levelWater
sample.conductivity = it.conductivity
sample.dissolvedOxygen = it.dissolvedOxygen
sample.redoxPotential = it.redoxPotential
sample.ph = it.ph
sample.temperature = it.temperature
water.addToSamples(sample)
}
return water
My problem is that addTo is not saving in order. How can I solve this?
Make sure you have defined the type of samples as a List in your Water domain class so that we can maintain the insertion order:
class Water {
static hasMany = [samples: Sample]
List<Sample> samples = []
}
class Sample {
def levelWater
}
By default implementation of hasMany is of type Set which does not maintain the insertion order but is responsible for uniqueness.
Since, now you samples will be saved in the same order as they are inserted.
You have to specify with order you want to apply to the list of SampleWater in the "water" domain class.
i.e:
class BlogCategory {
static hasMany = [
entries : BlogEntry
]
static mapping = {
entries: sort:'dateCreated', order:'desc'
}
}
In this example BlogEntry will be ordered respect dateCreated.

Criteria search

I ran into some problems while trying to count items.
Imagine the following domain classes
class Book {
String name
}
class Author {
String name
static hasMany = [books:Book]
}
How do I get a list of Authors sorted by number of Books?
here's my try:
def c = Author.createCriteris()
c.list {
projections {
count 'books', 'numBooks'
groupProperty 'id'
}
order 'numBooks', 'desc'
}
but somehow I get only unusable results... and I don't know how to join the Author objects to the rsult list.... :-(
Havent tried it, but couldn't you do something like:
class Author {
String name
static hasMany = [books:Book]
static namedQueries = {
sortByMostBooks {
books {
order('size', 'desc')
}
}
}
}
And then get access by the cleaner named query
Author.sortByMostBooks.list();
In addition, you may want to include a belongsTo in you Book domain class:
static belongsTo = Author;
or:
static belongsTo = [authors:Author];
if a book is likely to have multiple authors
got something!
I still don't know how to do it with a criteria, but by switching to HQL, I succeeded.
So if someone comes up with a criteria solution, he will still get the bonus for the correct answer :-)
here is my query:
Author.executeQuery("""
select
a, size(a.books) as numBooks
from
Author a
group by
id
order by
numBooks DESC
""",[max:20])
This query isn't efficient, since it fetches all Authors in a loop, but that's ok for now.

How to set uniqueness at DB level for a one-to-many association?

My problem is simple but I could not find any GORM syntax for this.
Consider the following class:
class Article {
String text
static hasMany = [tags: String]
static constraints= {
tags(unique: true) //NOT WORKING
}
}
I want to have one unique tag name per article defined in my constraints but I cannot make it with the above syntax.
Clearly I need in DB schema something like:
create table article_tags (article_id bigint, tags_string varchar(255), unique (article_id , tags_string))
How can I do that?
PS: I am also stuck for setting constraints on tag minimum and maximum size
FYI, you can also use a custom validator in domain classes:
static constraints = {
tags(validator: {
def valid = tags == tags.unique()
if (!valid) errors.rejectValue(
"tags", "i18n.message.code", "default message")
return valid
})
At the database level, you can customize DDL generation by having the following code in grails-app/conf/hibernate/hibernate.cfg.xml:
<hibernate-mapping>
<database-object>
<create>
ALTER TABLE article_tags
ADD CONSTRAINT article_tags_unique_constraint
UNIQUE(article_id, tags_string);
</create>
<drop>
ALTER TABLE article_tags
DROP CONSTRAINT article_tags_unique_constraint;
</drop>
</database-object>
</hibernate-mapping>
Initially I looked at the joinTable mapping to see if it would support a unique key, but it won't.
The best solution I can think of is the following combination:
Manually run the SQL statement to add the unique constraint. If you have some sort of database management tool (e.g. Liquibase), that would be the ideal place.
Explicitly declare the association as a Set. This should avoid Hibernate ever running into the unique constraint, anyway.
class Article {
static hasMany = [tags: String]
Set<String> tags = new HashSet<String>()
}
An alternate solution would be to explicitly declare your child domain (Tag) and set up a many-to-many relationship, adding the unique key to the join table there using constraints. But that's not really a great solution, either. Here's a primitive example:
class Article {
static hasMany = [articleTags: ArticleTag]
}
class Tag {
static hasMany = [articleTags: ArticleTag]
}
class ArticleTag {
Article article
Tag tag
static constraints = {
tag(unique: article)
}
}
With this, though, you have to explicitly manage the many-to-many relationship in your code. It's a bit inconvenient, but it gives you full control over the relationship as a whole. You can find out the nitty gritty details here (the Membership class in the linked example is akin to the ArticleTag in mine).
Perhaps one of the gurus more familiar with GORM will chime in with a more elegant solution, but I can't find anything in the docs.
EDIT: Note that this approach does not consider a unique(article_id , tags_id) constraint. It also raises an issue with two Articles having the same tags. - Sorry.
While this is not officially documented (see the relevant parts of the Grails Reference Documentation here and here) constraints on one-to-many associations are simply ignored by GORM. This includes unique and nullable constraints, and probably any.
This can be proved by setting dbCreate="create" and next, by looking at the database schema definition. For your Article sample and the PostgreSQL database, this would be:
CREATE TABLE article_tags
(
article_id bigint NOT NULL,
tags_string character varying(255),
CONSTRAINT fkd626473e45ef9ffb FOREIGN KEY (article_id)
REFERENCES article (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT article0_tags_article0_id_key UNIQUE (article_id)
)
WITH (
OIDS=FALSE
);
As can be seen above, there are no constraints for the tags_string column.
In contrast to constraints on association fields, constraints on "normal" instance fields of domain classes do work as expected.
Thus, we'll want to have some kind of Tag, or TagHolder, domain class, and we'd need to find a pattern that still provides the Article with a clean public API.
First, we're introducing the TagHolder domain class:
class TagHolder {
String tag
static constraints = {
tag(unique:true, nullable:false,
blank:false, size:2..255)
}
}
and associate it with the Article class:
class Article {
String text
static hasMany = [tagHolders: TagHolder]
}
In order to provide a clean public API, we're adding the methods String[] getTags(), void setTags(String[]. That way, we can also call the constructor with named parameters, like, new Article(text: "text", tags: ["foo", "bar"]). We're also adding the addToTags(String) closure, which mimicks GORM's corresponding "magic method".
class Article {
String text
static hasMany = [tagHolders: TagHolder]
String[] getTags() {
tagHolders*.tag
}
void setTags(String[] tags) {
tagHolders = tags.collect { new TagHolder(tag: it) }
}
{
this.metaClass.addToTags = { String tag ->
tagHolders = tagHolders ?: []
tagHolders << new TagHolder(tag: tag)
}
}
}
It's a workaround, but there's not too much coding involved.
A drawback, we're getting an additional JOIN table. Nevertheless, this pattern allows for applying any available constraints.
Finally, a test case could look like this one:
class ArticleTests extends GroovyTestCase {
void testUniqueTags_ShouldFail() {
shouldFail {
def tags = ["foo", "foo"] // tags not unique
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testUniqueTags() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testTagSize_ShouldFail() {
shouldFail {
def tags = ["f", "b"] // tags too small
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testTagSize() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testAddTo() {
def article = new Article(text: "text")
article.addToTags("foo")
article.addToTags("bar")
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
}
Try this:
http://johnrellis.blogspot.com/2009/09/grails-constraints-across-relationships.html
The only way I've found to do this is to write a custom constraint and do a database check for the duplication. I don't think there is a built-in way to use a GORM constraint to accomplish this.

Resources