Tuesday, April 7, 2009

Eager fetching and searchable plugin in Grails

What came as surprise to me is that eager fetching even when specified on the level of the domain class does not work with the searchable plugin. I hit this problem while working on the software books reviews and statement and description why I found on one blog post. Unfortunately I cannot find the mentioned post again so I am not able to give a credit to the original author by linking to his/her post.

So for example lets have Book domain class that among other properties has publisher property specified like this:

class Book {
static searchable = true
Publisher publisher

static fetchMode = [publisher:"eager"]
}

Using searchable plugin you can search for the books like this:

 Map<String,String> options = new HashMap<String, String>()
if (params.offset) {
options.put('offset',new Long(params.offset))
}
options.put('escape', true)
options.put('max',10)
def searchResult = Book.search(params.searchTerm, options)

After executing given query searchResult.result will be populated with the list of found Book instances. And in this moment you hope that everything is fine. You send the found list of books to the view but to your surprise you see that publisher is not displayed for each of the book. You check if the book has publisher and it is in the database. The reason for the missing publisher is that the domain classes are created differently when loaded from the lucene index file.

To resolve this problem I have actually reloaded all the found books from the database. This way all the books are of course loaded as specified in the domain object. So code for searching looks like this:

Map<String,String> options = new HashMap<String, String>()
if (params.offset) {
options.put('offset',new Long(params.offset))
}
options.put('escape', true)
options.put('max',10)
def searchResult = Book.search(params.searchTerm, options)

// create new results because those current one are actually not loaded eagery
def newResults = new ArrayList()
searchResult.results.each {book ->
def bookToAdd = Book.get(book.id)
newResults.add(bookToAdd)
}

searchResult.results = newResults

So if you have integrated searching into your grails application and expects that some parts of the domain object are loaded eagerly you should probably use approach similar to this one.

P.S. Don't forget to write some review or post summary on the software books reviews.

6 comments:

Anonymous said...

Instead of iterating in a loop to reload the books, would the new "inList" dynamic finder work?

jan said...

I think it should work.

John said...

Jan I read an earlier post of yours regarding your grails hosting for the tutorials site with eapp. I was wondering if you could elaborate more on the setup you have there. My concern about grails is JVM memory needs. your tutorial site is performs well however. are you running the DB on the same VPS? And can you discuss yout tomcat tuning? that would be a helpful post. thanks

jan said...

@John
Thanks for the advice. I will see to write some post about that in coming days. Although I don't know if it will be much interesting because I was just following manual of the eApps.

Daniel Rinser said...

Hi Jan,

the fetchMode is a GORM setting, and as such only affects direct GORM queries. When issuing queries via the searchable plugin, the results are retrieved completely from the Lucene index rather than from the database. This is why the fetchMode has no effect on the searchable plugin (which shouldn't be a surprise actually). However, you can easily avoid having to re-fetch all the instances from the database by configuring the searchable mapping accordingly. There are two alternatives for that:

1) Map your associations as searchable references:

static searchable = {
publisher reference: true
}
(see this)

2) Map your associations as searchable components:

static searchable = {
publisher component: true
}
(see this)

Both will cause the searchable plugin to include the associated class (Publisher) in the index, however there are differences wrt. which properties are searched when you issue a query. You should read the Compass concepts docs for further details.

Hope this helps.

Cheers,
Daniel

Surya said...

Hi

Thanks for writing wonderfully on so many different grails topics. While searchable plugin has been great to use during development and testing, deploying it with tomcat has not been easy. Tomcat would not deploy the application. Could you please take a few minutes to look at the errors and share your thoughts.

Thank you so much.


SEVERE: Exception sending context initialized event to listener instance of class org.codehaus.groovy.grails.web.context.GrailsContextLoaderListener

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compassGps': Cannot resolve reference to bean 'compass' while setting bean property 'compass'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compass': FactoryBean threw exception on object creation; nested exception is java.lang.NoClassDefFoundError: org/codehaus/groovy/grails/plugins/searchable/compass/mapping/DefaultSearchableCompassClassMappingXmlBuilder$_buildClassMappingXml_closure1_closure4_closure12_closure13

Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'searchableService': Cannot create inner bean '(inner bean)' while setting bean property 'target'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#2': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compass': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException

Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'searchableService': Cannot create inner bean '(inner bean)' while setting bean property 'target'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#2': Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'compass': FactoryBean threw exception on object creation; nested exception is java.lang.NullPointerException
at java.security.AccessController.doPrivileged(Native Method)
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3843)
at org.apache.catalina.core.StandardContext.start(StandardContext.java:4350)
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:791)
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:771)

Full log file is at
http://www.nabble.com/Tomcat-deploy-error---Searchable-plugin--td25981971.html