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:
-
Lazy state initialization
-
Dirtiness tracking
-
Automatic bi-directional association management
-
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.