Skip to content

Commit a10d1a7

Browse files
committed
docs: add Data Services multi-datasource section to GORM Hibernate5 docs
1 parent e42d820 commit a10d1a7

File tree

4 files changed

+202
-0
lines changed

4 files changed

+202
-0
lines changed

grails-data-hibernate5/docs/src/docs/asciidoc/multipleDataSources/dataSourceNamespaces.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,5 @@ def results = c.list {
6161
like('code','995%')
6262
}
6363
----
64+
65+
TIP: The namespace syntax (e.g., `ZipCode.auditing.get(42)`) uses dynamic dispatch and is not compatible with `@CompileStatic`. For statically compiled code, use `GormEnhancer.findStaticApi(ZipCode, 'auditing')` instead, which returns a `GormStaticApi` handle with the same methods routed to the specified datasource. See the <<dataServicesMultipleDataSources,Data Services and Multiple Datasources>> section for examples.

grails-data-hibernate5/docs/src/docs/asciidoc/multipleDataSources/index.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ include::mappingDomainsToDataSources.adoc[]
3131

3232
include::dataSourceNamespaces.adoc[]
3333

34+
=== Using Data Services with Multiple Datasources
35+
36+
When using GORM Data Services (`@Service` annotation) with secondary datasources, you must annotate the abstract class with `@Transactional(connection = 'connectionName')` to route all auto-implemented methods to the correct datasource. See the <<dataServicesMultipleDataSources,Data Services and Multiple Datasources>> section for the full pattern, including `GormEnhancer.findStaticApi()` for complex queries.
37+
3438
[[connectionSources]]
3539
=== The ConnectionSources API
3640

grails-data-hibernate5/docs/src/docs/asciidoc/services/index.adoc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ include::writeOperations.adoc[]
3737

3838
include::serviceValidation.adoc[]
3939

40+
[[dataServicesMultipleDataSources]]
41+
=== Data Services and Multiple Datasources
42+
43+
include::multipleDataSources.adoc[]
44+
4045
=== RxJava Support
4146

4247
include::rxServices.adoc[]
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
////
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
https://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
////
19+
20+
When using Data Services with <<multipleDataSources,multiple datasources>>, the service must declare which connection to use via the `connection` parameter of `@Transactional`.
21+
22+
==== Routing to a Secondary Datasource
23+
24+
Given a domain class mapped to a secondary datasource:
25+
26+
[source,groovy]
27+
----
28+
class Book {
29+
30+
String title
31+
String author
32+
33+
static mapping = {
34+
datasource 'books'
35+
}
36+
}
37+
----
38+
39+
Define an interface for your data access methods and an abstract class that declares the connection:
40+
41+
[source,groovy]
42+
----
43+
import grails.gorm.services.Service
44+
45+
interface BookDataService {
46+
47+
Book get(Serializable id)
48+
49+
Book save(Book book)
50+
51+
void delete(Serializable id)
52+
53+
List<Book> findAllByAuthor(String author)
54+
55+
Long count()
56+
}
57+
----
58+
59+
[source,groovy]
60+
----
61+
import grails.gorm.services.Service
62+
import grails.gorm.transactions.Transactional
63+
import groovy.transform.CompileStatic
64+
65+
@CompileStatic
66+
@Service(Book)
67+
@Transactional(connection = 'books')
68+
abstract class BookService implements BookDataService {
69+
// All interface methods are auto-implemented by GORM
70+
// and route to the 'books' datasource automatically.
71+
}
72+
----
73+
74+
The `@Transactional(connection = 'books')` annotation on the abstract class ensures that all auto-implemented methods (`get`, `save`, `delete`, `findBy*`, `countBy*`, etc.) route to the `books` datasource. Without this annotation, queries silently route to the default datasource.
75+
76+
IMPORTANT: The `connection` parameter is required when using Data Services with secondary datasources. The `@Service(Book)` annotation alone does not determine which datasource to use, even if the `Book` domain class declares `datasource 'books'` in its mapping block.
77+
78+
==== How Connection Routing Works
79+
80+
When GORM compiles a `@Service` abstract class, the `ServiceTransformation` AST transform:
81+
82+
1. Copies the `@Transactional(connection = 'books')` annotation from the abstract class to the generated implementation class
83+
2. For each auto-implemented method, resolves the connection identifier via `findConnectionId()`
84+
3. Generates method bodies that use the appropriate connection - `GormEnhancer.findStaticApi(Book, 'books')` for CRUD operations and `DetachedCriteria.withConnection('books')` for finder queries
85+
86+
This means auto-implemented methods like `get()`, `save()`, `delete()`, `findBy*()`, and `countBy*()` all respect the connection parameter without requiring manual implementations.
87+
88+
==== Complex Queries with GormEnhancer
89+
90+
Auto-implemented Data Service methods cover most query patterns, including dynamic finders with comparators, pagination, and property projections. For queries that require HQL, criteria builders, or aggregate functions, use `GormEnhancer.findStaticApi()` to obtain a statically compiled API handle for the target datasource:
91+
92+
[source,groovy]
93+
----
94+
import groovy.transform.CompileStatic
95+
import grails.gorm.services.Service
96+
import grails.gorm.transactions.Transactional
97+
import org.grails.datastore.gorm.GormEnhancer
98+
import org.grails.datastore.gorm.GormStaticApi
99+
100+
@CompileStatic
101+
@Service(Book)
102+
@Transactional(connection = 'books')
103+
abstract class BookService implements BookDataService {
104+
105+
// Auto-implemented methods from interface are inherited
106+
// and route to 'books' datasource automatically.
107+
108+
private GormStaticApi<Book> getBooksApi() {
109+
GormEnhancer.findStaticApi(Book, 'books')
110+
}
111+
112+
List getTopAuthors(int limit) {
113+
booksApi.executeQuery('''
114+
SELECT b.author, COUNT(b) as bookCount
115+
FROM Book b
116+
GROUP BY b.author
117+
ORDER BY bookCount DESC
118+
''', Collections.emptyMap(), [max: limit])
119+
}
120+
121+
List<Book> searchWithCriteria(String titlePattern, String author) {
122+
booksApi.createCriteria().list {
123+
like('title', "%${titlePattern}%")
124+
eq('author', author)
125+
order('title', 'asc')
126+
} as List<Book>
127+
}
128+
}
129+
----
130+
131+
The `GormStaticApi` returned by `findStaticApi()` provides these methods, all routed to the specified datasource:
132+
133+
[cols="1,2"]
134+
|===
135+
| Method | Description
136+
137+
| `executeQuery(String hql, Map params)`
138+
| HQL/JPQL queries
139+
140+
| `executeUpdate(String hql, Map params)`
141+
| Bulk UPDATE/DELETE statements
142+
143+
| `withCriteria(Closure criteria)`
144+
| Criteria query
145+
146+
| `createCriteria()`
147+
| Criteria builder for complex queries
148+
149+
| `where(Closure query)`
150+
| Where query
151+
152+
| `withTransaction(Closure action)`
153+
| Manual transaction management
154+
155+
| `count()`
156+
| Total record count
157+
158+
| `get(Serializable id)`
159+
| Find by primary key
160+
161+
| `list(Map params)`
162+
| Paginated list
163+
164+
| `save(Object instance)`
165+
| Persist an instance
166+
|===
167+
168+
NOTE: `GormEnhancer.findStaticApi()` is the statically compiled equivalent of the <<dataSourceNamespaces,namespace syntax>> (e.g., `Book.books.get(42)`). Unlike the namespace syntax, it works under `@CompileStatic` without requiring `@CompileDynamic` on individual methods.
169+
170+
==== Consuming Multi-Datasource Data Services
171+
172+
Other services inject the Data Service interface type. Spring resolves the abstract class bean automatically:
173+
174+
[source,groovy]
175+
----
176+
import groovy.transform.CompileStatic
177+
178+
@CompileStatic
179+
class LibraryService {
180+
181+
BookDataService bookDataService // injected automatically
182+
183+
Map getLibraryStats() {
184+
Long totalBooks = bookDataService.count()
185+
List<Book> recentBooks = bookDataService.findAllByAuthor('Tolkien')
186+
[total: totalBooks, tolkienBooks: recentBooks.size()]
187+
}
188+
}
189+
----
190+
191+
The consuming service does not need `@Transactional(connection = 'books')`. The Data Service handles datasource routing internally.

0 commit comments

Comments
 (0)