Introduction

GORM 6.1 builds on the concepts introduced in GORM 6 around Multi-Tenancy and includes a number of notable improvements.

To see all of the features introduced in GORM 6, see the What’s New guide for GORM 6.0.

The following sections cover the new features in GORM 6.1.

New General Features

Multi-Tenancy Transformations

There are new transformations that can be applied to any class that simplify greatly the development of Multi-Tenant applications. These include:

  • @CurrentTenant - Resolve the current tenant for the context of a class or method

  • @Tenant - Use a specific tenant for the context of a class or method

  • @WithoutTenant - Execute logic without a specific tenant (using the default connection)

For example:

import grails.gorm.multitenancy.*

// resolve the current tenant for every method
@CurrentTenant
class TeamService {

    // execute the countPlayers method without a tenant id
    @WithoutTenant
    int countPlayers() {
        Player.count()
    }

    // use the tenant id "another" for all GORM logic within the method
    @Tenant({"another"})
    List<Team> allTwoTeams() {
        Team.list()
    }

    List<Team> listTeams() {
        Team.list(max:10)
    }

    @Transactional
    void addTeam(String name) {
        new Team(name:name).save(flush:true)
    }
}

More Flexible Transactional Transforms

The grails.transaction.Transactional transform from Grails core has been ported to GORM and can now be found in the grails.gorm.transactions package.

These are replacements for Grails' core transforms and can be used outside of Grails. In addition they are more flexible and allow specifying the target connection in multiple datasource scenarios.

A new grails.gorm.transactions.ReadOnly transformation has also been added to make it easier to encapsulate the default semantics for a read-only transaction.

Common GORM Services

A new set of common services has been added to GORM for dealing with Multi-Tenancy and Transactions:

  • TenantService - methods for working with Multi-Tenancy, including obtaining the current tenant and switching tenants

  • TransactionService - methods for aiding with the creation of programmatic transactions.

Both of these services can be obtained via the Datastore.getService(..) method or via dependency injection.

GORM Data Services

GORM now features a Data Services concept that adds the ability to generate implementations for Groovy interfaces using conventions that GORM developers are already familiar with.

For example given the following interface:

import grails.gorm.multitenancy.CurrentTenant
import grails.gorm.services.Service

@Service(Book)
@CurrentTenant
interface BookService {

    Book find(Serializable id)

    List<Book> findBooks(Map args)

    Number count()

    Book saveBook(String title)

    Book updateBook(Serializable id, String title)

    Book deleteBook(Serializable id)
}

An implementation for the above interface will be generated at compile time and made available to your application.

See the documentation on Data Services for more information.

Support for Bean Validation API

If you prefer to use the standard Bean Validation API for validation, this is now possible with GORM. If you are using GORM for Hibernate simply add the necessary annotations to your entity:

import javax.validation.constraints.*

class Product {
    String name
    @Digits
    String price
}

If you are using another implementation you will need to add the hibernate-validator dependency to your build.gradle:

compile('org.hibernate:hibernate-validator:5.2.4.Final')

Improved Mapping DSL

The GORM Mapping DSL has been improved for all implementations, allowing code completion in IDEs and static compilation. For example for Hibernate:

import static grails.gorm.hibernate.mapping.MappingBuilder.*

class Book {
    String title

    static final mapping = orm {
        table {
            schema "library"
            name "books"
        }
        cache {
            enabled true
            usage 'read'
        }
        title = property {
            nullable true
            column {
                length 250
            }
        }
    }
}

For Neo4j and MongoDB there are equivalent classes called grails.neo4j.mapping.MappingBuilder and grails.mongodb.mapping.MappingBuilder respectively.

Support for Scanning Packages

The constructors for HibernateDatastore, MongoDatastore and Neo4jDatastore have been updated to support an array of Package instances representing the packages to scan for entities. This makes it easier to setup GORM outside of Grails or within unit tests. For example for Hibernate:

import org.grails.orm.hibernate.*

HibernateDatastore datastore = new HibernateDatastore(Package.getPackage("foo.bar"))

New Hibernate Features

Hibernate 5.2 Support

Support for Hibernate 5.2 has been added (requires Java 8 minimum) and can be enabled by declaring an explicit dependency on Hibernate 5.2:

compile("org.hibernate:hibernate-core:5.2.0.Final")

JPA Mapping Support

Support for using JPA-annotated entites instead of GORM’s DSL for entities written in Groovy has been added. For example:

import javax.persistence.*
import javax.validation.constraints.*

@Entity
class Product {
    @Id
    @GeneratedValue
    Long id
    String name

    @Digits(integer = 6, fraction = 2)
    String price
}

Automatic Escaping for HQL Queries

You can now pass Groovy’s GString instances directly as HQL queries and these will be automatically escaped avoiding HQL-injection attacks:

String name = 'coffee'
Product p = Product.find("from Product as p where p.name = $name")

Support for Native SQL Queries

By implementing the HibernateEntity trait you can gain additional methods for using native SQL queries that feature the same automatic escaping for GString instances to avoid SQL-injection attacks:

import grails.gorm.hibernate.*

class Product implements HibernateEntity<Product> {
    ...
}

String name = 'coffee'
Product p = Product.findWithSql("select * from product p where p.name = $name")

Smart Hibernate Dirty Checking

GORM’s DirtyCheckable AST transformations have been integrated with Hibernate’s custom dirty checking API improving performance and increasing efficiency when doing large updates.

Hibernate Managed Entity Transform

An optional @ManagedEntity transformation has been added which can be applied to Hibernate entities which performs the same enhancements as Hibernate’s Build time byte code enhancement without the need for an additional Gradle plugin.

By applying @ManagedEntity to a class the class benefits from:

  1. Lazy state initialization

  2. Dirtiness tracking

  3. Automatic bi-directional association management

  4. Performance optimizations

This includes eliminating the need to generate proxies for the entity.

There are some behavioural differences in lazy loading between @ManagedEntity enhanced entities and normal entities, hence why these enhancements are not applied by default and are opt-in.

New MongoDB Features

Decimal128 Support

Support for MongoDB 3.4’s new Decimal128 type for representing BigDecimal values in Java has been added.

New findOneAndDelete Method

A new method to simplify using findOneAndDelete with native queries has been added:

import static com.mongodb.client.model.Filters.*

Product p = Product.findOneAndDelete(eq("title", "coffee"))

ReadConcern supported in Queries

You can now pass the ReadConcern to use to GORM queries:

Person.findAllByFirstName("Bob", [readConcern: ReadConcern.MAJORITY])

New Neo4j Features

The following new features have been added to GORM for Neo4j:

  • Support for mapping entities to Neo4j Relationships

  • Support for assigned identifiers

  • Support for querying Neo4j Paths

  • Support for lists and maps of basic types

  • Batch inserts with UNWIND and FOREACH when using assigned ids

  • Upgrade to Neo4j Bolt Driver 1.2

See the following sections for more details.

Relationship Entity Support

In addition to being able to map a domain class to a Neo4j Node, since 6.1 you are able to map a domain class to a Neo4j Relationship.

For example consider the following domain model:

import grails.neo4j.*

class Movie {
    String title
    static hasMany = [cast:CastMember]
}

class CastMember implements Relationship<Person, Movie> {
    List<String> roles = []
}

class Person {
    String name
    static hasMany = [appearances:CastMember]
}

The CastMember class implements the Relationship trait which takes two generic arguments: The class that represents the start of the relationship and the class that represents the end.

You can then use regular GORM methods to query the CastMember relationship. In addition because Neo4j relationships are dynamic you can assign additional properties to them at runtime. For example:

def castMember = new CastMember(
    from: new Person(name: "Keanu"),
    to: new Movie(title: "The Matrix"),
    roles: ["Neo"])

castMember['realName'] = "Thomas Anderson"
castMember.save(flush:true)

Path and Relationship Query Support

It is not possible to query for Neo4j paths:

import grails.neo4j.Path
...
Path<Person, Person> path = Person.findShortestPath(fred, joe, 15)
for(Path.Segment<Person, Person> segment in path) {
    println segment.start().name
    println segment.end().name
}

And relationships:

import grails.neo4j.Relationship
...
List<Relationship<Person, Person>> rels = Person.findRelationships(Person, Person, [max:10])

Assigned Identifier Support

It is now possible to use assigned identifiers:

import static grails.neo4j.mapping.MappingBuilder.*
class Person implements Node<Person> {
    String name
    static mapping = node {
        id(generator:'assigned', name:'name')
    }
}

Relationship Mapping Support

More control over how relationships are mapped has been added via the mapping block. For example:

import static grails.neo4j.Direction.*
import static grails.neo4j.mapping.MappingBuilder.*

class Owner {
    String name
    static hasMany = [pets:Pet]

    static mapping = node {
         pets type:"PETZ", direction:BOTH
    }
}

The type and direction settings can be used to specify the relationship type and direction.