Grails object relational mapping: GORM


Published on

GORM: All about relationships in between domain objects in Grails, and how GROM handle them

Grails object relational mapping: GORM

  1. 1. By: Saurabh Dixit
  2. 2.  Automatically maps domain objects todatabase Provides query and update facility1. Finding objects in persistence store(findby methods)2. Persistence life cycle methods3. Criteria and HQL query facilities
  3. 3.  Grails injects GORM functionality into objectsin run time Declare properties for the domain class1.The id and version properties are injectedautomatically.2. All properties are not null/required bydefault.
  4. 4.  Associations One‐to‐one, one‐to‐many, many‐to‐many Uni‐directional and bi‐directional Owners defined by using belongsToproperty
  5. 5.  Many-to-one relationship: A Many-to-one relationship is thesimplest kind, and is defined with aproperty of the type of another domainclass. Consider this example: A unidirectional many-to-onerelationship from Face to Nose: class Face {Nose nose}class Nose {}
  6. 6.  To make this relationship bidirectional definethe other side as follows:class Face {Nose nose}class Nose {static belongsTo = [face:Face]}
  7. 7.  The result of this is that we can create a Face,attach a Nose instance to it and when wesave or delete the Face instance, GORM willsave or delete the Nose. In other words, savesand deletes will cascade fromFace to the associated Nose:Face f = new Face()f.nose = new Nose()
  8. 8.  one-to-one Relationship: use the hasOne property on the owningside, e.g. Face:classAuthor {static hasOne = [book:Book]}class Book {Author author} Note that using this property puts theforeign key on the inverse table to theprevious example, so in this case theforeign key column is stored in the nosetable inside a column called face_id.
  9. 9.  hasOne only works with bidirectionalrelationships. its a good idea to add a unique constraint onone side of the one-to-one relationship:classAuthor {static hasOne = [book:Book]static constraints = {book unique: true}}class Book {Author author}
  10. 10.  One-to-many Relationship:classAuthor {static hasMany = [books: Book]String name}class Book {String title} In this case we have a unidirectional one-to-many. Grails will, by default, map this kind ofrelationship with a join table.
  11. 11.  Grails will automatically inject a property oftype java.util.Set into the domain class basedon the hasMany setting.This can be used toiterate over the collection:def a = Author.get(1)for (book in a.books) {println book.title}
  12. 12.  The default fetch strategy used by Grails is"lazy", which means that the collection will belazily initialized on first access. The default cascading behaviour is to cascadesaves and updates, but not deletes unless abelongsTo is also specified:classAuthor {static hasMany = [books: Book]String name}class Book {static belongsTo = [author: Author]String title}
  13. 13.  If you have two properties of the same typeon the many side of a one-to-many you haveto use mappedBy to specify which thecollection is mapped:classAirport {static hasMany = [flights: Flight]static mappedBy = [flights: "departureAirport"]}class Flight {Airport departureAirportAirport destinationAirport}
  14. 14.  Many-to-many Relationship: Grails supports many-to-many relationships bydefining a hasMany on both sides of the relationshipand having a belongsTo on the owned side of therelationship:class Book {static belongsTo = Authorstatic hasMany = [authors:Author]String title}classAuthor {static hasMany = [books:Book]String name}
  15. 15.  Grails maps a many-to-many using a jointable at the database level.The owning sideof the relationship, in this case Author, takesresponsibility for persisting the relationshipand is the only side that can cascade savesacross.
  16. 16.  Relationship with the basic collection type: As well as associations between differentdomain classes, GORM also supportsmapping of basic collection types. For example, the following class creates anicknames association that is a Set of Stringinstances:class Person {static hasMany = [nicknames: String]}
  17. 17.  GORM will map an association like the aboveusing a join table. You can alter various aspects of how the jointable is mapped using the joinTableargument:class Person {static hasMany = [nicknames: String]static mapping = {hasMany joinTable: [name: bunch_o_nicknames,key: person_id,column: nickname,type: "text"]}}
  18. 18.  As well as association, Grails supports the notionof composition. In this case instead of mapping classes ontoseparate tables a class can be "embedded" withinthe current table. For example:class Person {Address homeAddressAddress workAddressstatic embedded = [homeAddress, workAddress]}classAddress {String numberString code}
  19. 19.  The resulting mapping would looking likethis:If you define the Address class in a separateGroovy file in the grails-app/domaindirectory you will also get an address table.
  20. 20.  Custom mappings are defined using a staticmapping block defined within your domainclass:class Person {…static mapping = {version falseautoTimestamp false}}
  21. 21.  You can also configure global mappings inConfig.groovy (or an external config file)using this setting:grails.gorm.default.mapping = {version falseautoTimestamp false}
  22. 22. class Person {String firstNameSting addressstatic mapping = {version falseautoTimestamp falsetable people //Table namefirstName column: First_Name // column nameaddress type:text}} for the type in mapping you can create userdefined types using hibernateorg.hibernate.usertype Interface UserType.
  23. 23.  GORM supports inheritance both fromabstract base classes and concrete persistentGORM entities. Inheritance hierarchies Table‐per‐hierarchy:All classes map to asingle table Table‐per‐subclass: Each class maps to itsown table and JOINs are used
  24. 24.  For example:classContent {String author}class BlogEntry extends Content {URL url}class Book extends Content {String ISBN}class PodCast extends Content {byte[] audioStream} In the above example we have a parentContent class and then various child classeswith more specific behavior.
  25. 25.  At the database levelGrails by default usestable-per-hierarchy mapping with adiscriminator column called class so theparent class (Content) and its subclasses(BlogEntry, Book etc.), share the same table. By default same table, forTable-per-hierarchy.
  26. 26.  Table-per-hierarchy mapping has a down sidein that you cannot have non-nullableproperties with inheritance mapping. To have two different tables for them: usetable-per-hierarchy=false To use table per subclass:▪ tablePerHierarchy false
  27. 27.  Retrieving Objects: get(), getAll(), read() Listing objects: list(), listOrderBy*() methods order, sort, offset, and max namedarguments
  28. 28.  Sets: Sets of Objects By default when you define a relationshipwithGORM it is a java.util.Set which is anunordered collection that cannot containduplicates. In other words when you have:classAuthor {static hasMany = [books: Book]}
  29. 29.  The books property that GORM injects is ajava.util.Set. Sets guarantee uniquenes butnot order, which may not be what you want.To have custom ordering you configure theSet as a SortedSet:class Author {SortedSet booksstatic hasMany = [books: Book]}
  30. 30.  In the above case a java.util.SortedSetimplementation is used which means youmust implement java.lang.Comparable inyour Book class:class Book implements Comparable {String titleDate releaseDate = new Date()int compareTo(obj) {releaseDate.compareTo(obj.releaseDate)}}
  31. 31.  Lists of Objects: To keep objects in the order which they wereadded and to be able to reference them by indexlike an array you can define your collection typeas a List:classAuthor {List booksstatic hasMany = [books: Book]} In this case when you add new elements to thebooks collection the order is retained in asequential list indexed from 0 so you can do:author.books[0] // get the first book
  32. 32.  Bags of Objects: If ordering and uniqueness arent a concern(or if you manage these explicitly) then youcan use the Hibernate Bag type to representmapped collections. The only change required for this is to definethe collection type as a Collection:
  33. 33. classAuthor {Collection booksstatic hasMany = [books: Book]} Since uniqueness and order arent managedby Hibernate, adding to or removing fromcollections mapped as a Bag dont trigger aload of all existing instances from thedatabase, so this approach will performbetter and require less memory than using aSet or a List.
  34. 34.  Maps of Objects: If you want a simple map of string/value pairsGORM can map this with the following:classAuthor {Map books // map of ISBN:book names}def a = newAuthor()a.books = ["1590597583":"Grails Book"] In this case the key and value of the mapMUST be strings.
  35. 35.  If you want a Map of objects then you can dothis:class Book {Map authorsstatic hasMany = [authors: Author]}def a = new Author(name:"Stephen King")def book = new Book()book.authors = [stephen:a]
  36. 36.  CollectionTypes and Performance: The Java Set type doesnt allow duplicates.Toensure uniqueness when adding an entry to aSet association Hibernate has to load theentire associations from the database. If you have a large numbers of entries in theassociation this can be costly in terms ofperformance.
  37. 37.  The same behavior is required for List types,since Hibernate needs to load the entireassociation to maintain order.Therefore it isrecommended that if you anticipate a largenumbers of records in the association thatyou make the association bidirectional sothat the link can be created on the inverseside.
  38. 38.  For example consider the following code:def book = new Book(title:"New Grails Book")def author =Author.get(1) = In this example the association link is beingcreated by the child (Book) and hence it is notnecessary to manipulate the collectiodirectlyresulting in fewer queries and more efficientcode.
  39. 39.  Given an Author with a large number ofassociated Book instances if you were towrite code like the following you would seean impact on performance:def book = new Book(title:"New Grails Book")def author =Author.get(1)author.addToBooks(book) You could also model the collection as aHibernate Bag as described above.
  40. 40.  By default Lazy fetching, object will be loadedonly when it will be accessed. Lazy can be set to false to enable eager fetching:classAirport {String namestatic hasMany = [flights: Flight]static mapping = {flights lazy: false}} In this case the flights association will beloaded at the same time as its Airportinstance
  41. 41.  Although eager fetching is appropriate forsome cases, it is not always desirable. If you made everything eager you could quitepossibly load your entire database intomemory resulting in performance andmemory problems. An alternative to eager fetching is to usebatch fetching.You can configure Hibernateto lazily fetch results in "batches".
  42. 42.  For example:classAirport {String namestatic hasMany = [flights: Flight]static mapping = {flights batchSize: 10}} In this case, due to the batchSize argument,when you iterate over the flights association,Hibernate will fetch results in batches of 10.
  43. 43.  For example: If you had an Airport that had 30 flights, if you didnt configure batch fetching youwould get 1 query to fetch the Airport andthen 30 queries to fetch each flight: With batch fetching you get 1 query to fetchthe Airport and 3 queries to fetch eachFlight in batches of 10.
  44. 44.  In other words:batch fetching is an optimization of thelazyfetching strategy. Batch fetching can also be configured at theclass level as follows:class Flight {…static mapping = {batchSize 10}}
  45. 45.  Optimistic Locking: By default GORM classes are configured foroptimistic locking. Optimistic locking is a feature of Hibernatewhich involves storing a version value in aspecial version column in the database that isincremented after each update.
  46. 46.  The version column gets read into a versionproperty that contains the current versionedstate of persistent instance which you canaccess:def airport = Airport.get(10)println airport.version When you perform updates Hibernate willautomatically check the version property againstthe version column in the database and if theydiffer will throw a StaleObjectException.This willrollback the transaction if one is active. The version will only be updated afterflushing the session.
  47. 47.  Dealing with the locking through exceptionhandling:def airport = Airport.get(10)try { = "Heathrow" true)}catch (org.springframework.dao.OptimisticLockingFailureException e) {// deal with exception} The way you deal with the exception dependson the application.You could attempt aprogrammatic merge of the data or go backto the user and ask them to resolve theconflict.
  48. 48.  Pessimistic Locking: Pessimistic locking is equivalent to doing aSQL "SELECT * FOR UPDATE" statementand locking a row in the database. This has the implication that other readoperations will be blocking until the lock isreleased.
  49. 49.  In Grails pessimistic locking is performed on anexisting instance with the lock method:def airport = Airport.get(10)airport.lock() // lock for = "Heathrow" Grails will automatically deal with releasing thelock for you once the transaction has beencommitted. In the above case what we are doing is"upgrading" from a regular SELECT to aSELECT..FOR UPDATE and another thread couldstill have updated the record in between the call toget() and the call to lock().
  50. 50.  To get around this problem you can use thestatic lock method that takes an id just likeget:def airport = Airport.lock(10) // lock for = "Heathrow"
  51. 51.  Using pessimistinc locking in dynamic findersand criteria: As well as the lock method you can also obtain apessimistic locking using queries. For example using a dynamic finder:def airport = Airport.findByName("Heathrow", [lock: true]) Or using criteria:def airport = Airport.createCriteria().get {eq(name, Heathrow)lock true}
  52. 52.  You can use the isDirty method to check ifany field has been modified:def airport = Airport.get(10)assert !airport.isDirty() = paramsif (airport.isDirty()) {// do something based on changed state} isDirty() does not currently check collectionassociations, but it does check all otherpersistent properties and associations.
  53. 53.  You can also check if individual fields havebeen modified:def airport = Airport.get(10)assert !airport.isDirty() = paramsif (airport.isDirty(name)) {// do something based on changed name}
  54. 54.  getDirtyPropertyNames: You can use the getDirtyPropertyNamesmethod to retrieve the names of modifiedfields; this may be empty but will not benull:def airport = Airport.get(10)assert !airport.isDirty() = paramsdef modifiedFieldNames = airport.getDirtyPropertyNames()for (fieldName in modifiedFieldNames) {// do something based on changed value}
  55. 55.  getPersistentValue: You can use the getPersistentValue methodto retrieve the value of a modified field:def airport = Airport.get(10)assert !airport.isDirty() = paramsdef modifiedFieldNames = airport.getDirtyPropertyNames()for (fieldName in modifiedFieldNames) {def currentValue = airport."$fieldName"def originalValue = airport.getPersistentValue(fieldName)if (currentValue != originalValue) {// do something based on changed value}}
  56. 56.  Use property names of the class Support for many expressions in findermethoddef book = Book.findByTitle("The Stand")book = Book.findByTitleLike("Harry Pot%") Like is comparator, due to the Likecomparator, above finder is equivalent to aSQL like expression.
  57. 57.  The possible comparators include:InList In the list of given valuesLessThan less than a given valueLessThanEquals less than or equal a give valueGreaterThan greater than a given valueGreaterThanEquals greater than or equal a given valueLike Equivalent to a SQL like expressionIlike Similar to a Like, except case insensitiveNotEqual Negates equalityBetween Between two values (requires twoarguments)IsNotNull Not a null value (doesnt take an argument)IsNull Is a null value (doesnt take an argument)
  58. 58.  Between, IsNotNull and IsNull requiredifferent numbers of method argumentscompared to the rest, as demonstrated in thefollowing example:def now = new Date()def lastWeek = now - 7def book = Book.findByReleaseDateBetween(lastWeek, now)books = Book.findAllByReleaseDateIsNull()books = Book.findAllByReleaseDateIsNotNull()
  59. 59.  Boolean logic (AND/OR): Method expressions can also use a booleanoperator to combine two or more criteria:def books = Book.findAllByTitleLikeAndReleaseDateGreaterThan("%Java%", new Date() - 30) In this case were using And in the middle ofthe query to make sure both conditions aresatisfied, but you could equally use Or:def books = Book.findAllByTitleLikeOrReleaseDateGreaterThan("%Java%", new Date() - 30)
  60. 60.  Built on Hibernate’s CriteriaAPI Groovy builder is used to build up the criteria▪ Hierarchy of method calls and closures forbuilding tree‐like structures Each domain class has a createCriteria()method▪ Call get, list, scroll, or count on criteria▪ The criteria builder is used to restrict thequery Supports associations and projections
  61. 61.  Some Criteria queries:def c = Account.createCriteria()def results = c.list {like("holderFirstName", "Fred%")and {between("balance", 500, 1000)eq("branch", "London")}maxResults(10)order("holderLastName", "desc")}
  62. 62.  Below is a node reference for each criterionmethod:Node Description Examplebetween Where the property value isbetween two distinct valuesbetween("balance", 500, 1000)eq Where a property equals aparticular value.eq("branch", "London")eq(caseinsensitive)A version of eq that supports anoptional 3rd Map parameter tospecify that the query becaseinsensitive.eq("branch", "london",[ignoreCase: true])eqProperty Where one property must equalanothereqProperty("lastTx", "firstTx")
  63. 63. Node Description Examplegt Where a property is greater than a particularvaluegt("balance",1000)gtProperty Where a one property must be greater thananothergtProperty("balance","overdraft")ge Where a property is greater than or equal to aparticular valuege("balance", 1000)geProperty Where a one property must be greater than orequal to anothergeProperty("balance","overdraft")idEq Where an objects id equals the specified value idEq(1)ilike A case-insensitive like expression ilike("holderFirstName","Steph%")in where a property is not contained within thespecified list of values.Note: in is a Groovyreserve word,so it must be escaped by"age",[18..65]) or not{in("age",[18..65])}
  64. 64. isEmpty Where a collection property is empty isEmpty("transactions")isNotEmpty Where a collection property is notemptyisNotEmpty("transactions")isNotEmpty Where a collection property is notemptyisNotEmpty("transactions")isNull Where a property is null isNull("holderGender")isNotNull Where a property is not null isNotNull("holderGender")lt Where a property is less than aparticular valuelt("balance", 1000)ltProperty Where a one property must be lessthan anotherltProperty("balance","overdraft")le Where a property is less than orequal to a particular valuele("balance", 1000)leProperty Where a one property must be lessthan or equal to anotherleProperty("balance","overdraft")
  65. 65. like Equivalent to SQL like expression like("holderFirstName","Steph%")ne Where a property does not equal aparticular valuene("branch", "London")neProperty Where one property does not equal another neProperty("lastTx", "firstTx")order Order the results by a particular property order("holderLastName","desc")rlike Similar to like, but uses a regex.Onlysupported on Oracle and MySQL.rlike("holderFirstName",/Steph.+/)sizeEq Where a collection propertys size equals aparticular valuesizeEq("transactions", 10)sizeGt Where a collection propertys size isgreater than a particular valuesizeGt("transactions", 10)sizeGe Where a collection propertys size isgreater than or equal to a particular valuesizeGe("transactions", 10)
  66. 66. sizeLt Where a collection propertys size isless than a particular valuesizeLt("transactions", 10)sizeLe Where a collection propertys size isless than or equal to a particular valuesizeLe("transactions", 10)sizeNe Where a collection propertys size isnot equal to a particular valuesizeNe("transactions", 10)sqlRestriction Use arbitrary SQL to modify theresultsetsqlRestriction"char_length(first_name) = 4"
  67. 67.  With dynamic finders, you have access tooptions such as max, sort, etc. These are available to criteria queries as well,but they have different names:Name Description Exampleorder(String,String)Specifies both the sort column (thefirst argument) and the sort order(either asc or desc).order "age", "desc"firstResult(int) Specifies the offset for the results. Avalue of 0 will return all records up tothe maximum specified.firstResult 20maxResults(int) Specifies the maximum number ofrecords to return.maxResults 10cache(boolean) Indicates if the query should becached (if the query cacheis enabled).cache true
  68. 68.  Criteria also support the notion of projections. A projection is used to change the nature of theresults. For example the following query uses aprojection to count the number of distinctbranch names that exist for each Account:def c = Account.createCriteria()def branchCount = c.get {projections {countDistinct "branch"}}
  69. 69.  The following table summarizes the differentprojections and what they do:Name Description Exampleproperty Returns the given property inthe returned resultsproperty("firstName")distinct Returns results using a singleor collection of distinctproperty namesdistinct("fn") ordistinct([fn, ln])avg Returns the average value ofthe given propertyavg("age")count Returns the count of the givenproperty namecount("branch")countDistinct Returns the count of the givenproperty name for distinctrowscountDistinct("branch")
  70. 70. groupProperty Groups the results by the givenpropertygroupProperty("lastName")max Returns the maximum value of thegiven propertymax("age")min Returns the minimum value of thegiven propertymin("age")sum Returns the sum of the givenpropertysum("balance")rowCount Returns count of the number ofrows returnedrowCount()
  71. 71.  Criteria can be written as:def query = {and {eq(survey, survey)eq(user,user)}}FormSubmission.createCriteria().list(query)orFormSubmission.createCriteria().list{and {eq(survey, survey)eq(user,user)}}
  72. 72.  Adding SQLRestrictions in a criteria: Find all the users with first name, lastnamedob, but their status should not be null ortest_user, and their role should beROLE_USER in userrole table. User UserRole classes are created byspringSecurityPlugin:
  73. 73. Role userRole =Role.findByAuthority(ROLE_USER);UserList = User.createCriteria().list{and{if(params.firstName){ilike(firstName,params.firstName+%)}if(params.lastName){ilike(lastName, params.lastName+%)}if(birthDate){eq(birth, birthDate)}sqlRestriction("({alias}.status IS NULLORCAST({alias}.statusAS nvarchar) !=test_user)AND ${} in (Selectur.role_id from user_role ur whereur.user_id={alias}.id)")}if (!params.offset) {params.offset = 0}if (!params.max) {params.max = 10}maxResults(params.max.toInteger())firstResult(params.offset.toInteger())}
  74. 74.  Support for HibernateQuery Language: find(hql), findAll(hql), and executeQuery(hql) forqueries executeUpdate(hql) for DML‐style opera3ons(sets of updates or deletes) Support for positional and named parametersEAVEntity.executeQuery("SELECT FROM EAVEntity eWHEREe.initialEntryBy=:entryFor AND e.attribute=:attrId",[entryFor:+entryFor+, attrId: this.parentAttribute])
  75. 75.  // simple queryAccount.executeQuery("select distinct a.number from Account a") // using with list of parametersAccount.executeQuery("select distinct a.number from Account a " +"where a.branch = ? and a.created > ?",[London, lastMonth]) // using with a single parameter andpagination paramsAccount.executeQuery("select distinct a.number from Account a " +"where a.branch = ?", [London], [max: 10, offset: 5])
  76. 76.  // using with a single parameter andpagination paramsAccount.executeQuery("select distinct a.number from Account a " +"where a.branch = ?", [London], [max: 10, offset: 5]) // using with Map of named parametersAccount.executeQuery("select distinct a.number from Account a " +"where a.branch = :branch", [branch: London]) // same as previousAccount.executeQuery("select distinct a.number from Account a "+“where a.branch = :branch", [branch: London], [max: 10, offset: 5])
  77. 77.  // tell underlying Hibernate Query object tonot attach newly retrieved // objects to the session, will only save withexplicit saveAccount.executeQuery("select distinct a.number from Account a",null, [readOnly: true]) // time request out after 18 secondsAccount.executeQuery("select distinct a.number from Account a",null, [timeout: 18])
  78. 78.  // have Hibernate Query object return 30 rowsat a timeAccount.executeQuery("select distinct a.number from Account a", null,[fetchSize: 30]) // modify the FlushMode of the Query(default is FlushMode.AUTO)Account.executeQuery("select distinct a.number from Account a",null, [flushMode: FlushMode.MANUAL])
  79. 79.  The executeQuery method allows theexecution of arbitrary HQL queries. HQL queries can return domain classinstances, or Arrays of specified data whenthe query selects individual fields orcalculated values.
  80. 80.  The basic syntax is:Book.executeQuery(String query)Book.executeQuery(String query, List positionalParams)Book.executeQuery(String query, List positionalParams, Map metaParams)Book.executeQuery(String query, Map namedParams)Book.executeQuery(String query, Map namedParams, Map metaParams) query- An HQL query positionalParams - A List of parameters for a positional parameterizedquery namedParams - A Map of named parameters for a namedparameterized query metaParams - A Map of pagination parameters max or/and offset, aswell as Hibernate query parameters readOnly, fetchSize, timeout, and flushMode
  81. 81.  Support forWriting native queries: Defining new SQL instance: creating a new sqlinstance, basically it will create a new instance ofthe connection:Sql sql = Sql.newInstance(dataSource) Then we can execute the methods on the sql.sql.rows("SELECT id FROM eavchoice_entity_valueWHEREentity_id=:id",[id:newEntityId]);String entityInsertQuery = "insert into eaventity (version, attribute_id,initial_entry_by)VALUES (0, $attributeId, $entryFor)";def newEntity = sql.executeInsert(entityInsertQuery);sql.close()
  82. 82.  Using ExplicitTransaction with Native sqlqueries:▪ // Creating sql instanceSql sql = Sql.newInstance(dataSource)▪ // Instantiating transaction managerDataSourceTransactionManager txm = newDataSourceTransactionManager(dataSource)
  83. 83.  // instantiating transaction throughtransaction managerdef status = txm.getTransaction( new DefaultTransactionDefinition() )try{/*Write sql queries*/sql.close()txm.commit(status)}catch(Exception e){log.error("Exception in saving data", e)//Transaction roll backtxm.rollback(status)}finally(){sql.close()//Cleanup resources after transaction completion.txm.cleanupAfterCompletion( status )}
  84. 84.  Where Queries: The where method, introduced in Grails 2.0,builds on the support for Detached Criteria byproviding an enhanced, compile-time checkedquery DSL for common queries. The where method is more flexible than dynamicfinders, less verbose than criteria and provides apowerful mechanism to compose queries.
  85. 85.  Basic Querying: The where method accepts a closure that looksvery similar to Groovys regular collectionmethods. The closure should define the logical criteria inregular Groovy syntax, for example:def query = Person.where {firstName == "Bart"}Person bart = query.find()
  86. 86.  The returned object is a DetachedCriteriainstance, which means it is not associated withany particular database connection or session. This means you can use the where method todefine common queries at the class level:class Person {static simpsons = where {lastName == "Simpson"}…}
  87. 87.  Query execution is lazy and only happens uponusage of the DetachedCriteria instance. If you want to execute a where-style queryimmediately there are variations of the findAlland find methods to accomplish this:def results = Person.findAll {lastName == "Simpson"}def results = Person.findAll(sort:"firstName") {lastName == "Simpson"}Person p = Person.find { firstName == "Bart" }
  88. 88.  Each Groovy operator maps onto a regular criteria method. The following table provides a map of Groovy operators tomethods:Operator Criteria Method Description== eq Equal to!= ne Not Equal to> gt Greater than< lt Less than>= ge Greater than or equal to<= le Less than or equal toin inList Contained within the given list==~ like Like a given string=~ ilike Case insensitive like
  89. 89.  It is possible use regular Groovy comparisonoperators and logic to formulate complex queries:def query = Person.where {(lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" &&age > 9)}def results = query.list(sort:"firstName") The Groovy regex matching operators map onto likeand ilike queries unless the expression on the right handside is a Pattern object, in which case they map onto anrlike query:def query = Person.where {firstName ==~ ~/B.+/} ** Note that rlike queries are only supported if the underlying database supports regularexpressions
  90. 90.  A between criteria query can be done by combiningthe in keyword with a range:def query = Person.where {age in 18..65} Finally, you can do isNull and isNotNull style queriesby using null with regular comparison operators:def query = Person.where {middleName == null}
  91. 91.  Query Composition: Since the return value of the where method is aDetachedCriteria instance you can compose newqueries from the original query:def query = Person.where {lastName == "Simpson"}def bartQuery = query.where {firstName == "Bart"}Person p = bartQuery.find()
  92. 92.  Note that you cannot pass a closure defined as avariable into the where method unless it has beenexplicitly cast to a DetachedCriteria instance. In other words the following will produce an error:def callable = {lastName == "Simpson"}def query = Person.where(callable)
  93. 93.  The above must be written as follows:import grails.gorm.DetachedCriteriadef callable = {lastName == "Simpson"} as DetachedCriteria<Person>def query = Person.where(callable) As you can see the closure definition is cast (usingthe Groovy as keyword) to a DetachedCriteriainstance targeted at the Person class.
  94. 94.  Conjunction, Disjunction and Negation: As mentioned previously you can combine regularGroovy logical operators (|| and &&) to formconjunctions and disjunctions:def query = Person.where {(lastName != "Simpson" && firstName != "Fred") || (firstName == "Bart" &&age > 9)} You can also negate a logical comparison using !:def query= Person.where {firstName == "Fred" && !(lastName == Simpson)}
  95. 95.  Property Comparison Queries: If you use a property name on both the left handand right side of a comparison expression thenthe appropriate property comparison criteria isautomatically used:def query = Person.where {firstName == lastName}
  96. 96.  The following table described how each comparisonoperator maps onto each criteria propertycomparison method:Operator Criteria Method Description== eqProperty Equal to!= neProperty Not equal to> gtProperty Greater than< ltProperty Less than>= geProperty Greater than or equalto<= leProperty Less than or equal to
  97. 97.  Querying Associations: Associations can be queried by using the dotoperator to specify the property name of theassociation to be queried:def query = Pet.where {owner.firstName == "Joe" || owner.firstName == "Fred"}
  98. 98.  You can group multiple criterion inside a closuremethod call where the name of the methodmatches the association name:def query = Person.where {pets { name == "Jack" || name == "Joe" }} This technique can be combined with other top-level criteria:def query = Person.where {pets { name == "Jack" } || firstName == "Ed"}
  99. 99.  For collection associations it is possible toapply queries to the size of the collection:def query = Person.where {pets.size() == 2}
  100. 100.  Subqueries: It is possible to execute subqueries within wherequeries. For example to find all the people olderthan the average age the following query can beused:final query = Person.where {age > avg(age)}
  101. 101.  The following table lists the possiblesubqueries:Method Descriptionavg The average of all valuessum The sum of all valuesmax The maximum valuemin The minimum valuecount The count of all valuesproperty Retrieves a property of the resultingentities
  102. 102.  You can apply additional criteria to any subquery byusing the of method and passing in a closure containingthe criteria:def query = Person.where {age > avg(age).of { lastName == "Simpson" } && firstName == "Homer"} Since the property subquery returns multiple results,the criterion used compares all results. For example the following query will find all peopleyounger than people with the surname "Simpson":Person.where {age < property(age).of { lastName == "Simpson" }}
  103. 103.  There are several functions available to you within thecontext of a query.These are summarized in the table below:Method Descriptionsecond The second of a date propertyminute The minute of a date propertyhour The hour of a date propertyday The day of the month of a date propertymonth The month of a date propertyyear The year of a date propertyupper Converts a string property to upper caselower Converts a string property to lower caselength The length of a string propertytrim Trims a string property
  104. 104.  **Currently functions can only be applied to propertiesor associations of domain classes.You cannot, forexample, use a function on a result of a subquery. For example the following query can be used to findall pets born in 2011:def query = Pet.where {year(birthDate) == 2011} You can also apply functions to associations:def query = Person.where {year(pets.birthDate) == 2009}
  105. 105.  Batch Updates and Deletes:▪ Since each where method call returns aDetachedCriteria instance, you can use where queries toexecute batch operations such as batch updates anddeletes.▪ For example, the following query will update all peoplewith the surname "Simpson" to have the surname"Bloggs":def query = Person.where {lastName == Simpson}int total = query.updateAll(lastName:"Bloggs")
  106. 106.  **Note that one limitation with regards to batchoperations is that join queries (queries that queryassociations) are not allowed. To batch delete records you can use the deleteAllmethod:def query = Person.where {lastName == Simpson}int total = query.deleteAll()