Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ trait HibernateEntity<D> extends GormEntity<D> {
*/
@Generated
static List<D> findAllWithSql(CharSequence sql) {
currentHibernateStaticApi().findAllWithNativeSql(sql, Collections.emptyMap())
currentHibernateStaticApi().findAllWithSql(sql, Collections.emptyMap())
}

/**
Expand All @@ -61,7 +61,7 @@ trait HibernateEntity<D> extends GormEntity<D> {
*/
@Generated
static D findWithSql(CharSequence sql) {
currentHibernateStaticApi().findWithNativeSql(sql, Collections.emptyMap())
currentHibernateStaticApi().findWithSql(sql, Collections.emptyMap())
}

/**
Expand All @@ -74,7 +74,7 @@ trait HibernateEntity<D> extends GormEntity<D> {
*/
@Generated
static List<D> findAllWithSql(CharSequence sql, Map args) {
currentHibernateStaticApi().findAllWithNativeSql(sql, args)
currentHibernateStaticApi().findAllWithSql(sql, args)
}

/**
Expand All @@ -87,7 +87,7 @@ trait HibernateEntity<D> extends GormEntity<D> {
*/
@Generated
static D findWithSql(CharSequence sql, Map args) {
currentHibernateStaticApi().findWithNativeSql(sql, args)
currentHibernateStaticApi().findWithSql(sql, args)
}

@Generated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,12 @@ class HibernateGormStaticApi<D> extends GormStaticApi<D> {
doListInternal(query, namedParams, [], args, false)
}

D findWithNativeSql(CharSequence sql, Map args = Collections.emptyMap()) {
D findWithSql(CharSequence sql, Map args = Collections.emptyMap()) {
doSingleInternal(sql, [:], [], args, true) as D
}

List<D> findAllWithNativeSql(CharSequence query, Map args = Collections.emptyMap()) {
doListInternal(query, [:], [], args, true)
List<D> findAllWithSql(CharSequence sql, Map args = Collections.emptyMap()) {
doListInternal(sql, [:], [], args, true)
}
Comment thread
jamesfredley marked this conversation as resolved.
Comment thread
jamesfredley marked this conversation as resolved.

// The single-argument CharSequence overloads accept a plain String (executed as written, as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Key features and changes relative to GORM for Hibernate 5:
* **Hibernate ORM 7** — Full support for Hibernate 7, including Jakarta Persistence 3.2 and the Apache License 2.0.
* **Spring Boot 4 / Spring Framework 7** — Grails {grailsMajorVersion} vendors the removed `org.springframework.orm.hibernate5` classes into `grails-data-hibernate7-spring-orm` under the `org.grails.orm.hibernate.support.hibernate7` package.
* **HQL injection safety** — Single-argument `find`, `findAll`, `executeQuery`, and `executeUpdate` accept a plain `String` as on Hibernate 5; when a Groovy GString is passed, its `${value}` interpolations are bound as named parameters instead of being interpolated into the query text, so the idiomatic interpolated form is injection-safe by binding.
* **Native SQL queries** — New `findWithNativeSql` / `findAllWithNativeSql` methods added.
* **SQL query helpers** - `findWithSql` / `findAllWithSql` are available with the same names as Hibernate 5.
* **Native query temporal types** — Native SQL queries return `java.time` types by default instead of legacy `java.sql` types.
* **Removed deprecated Session API** — Hibernate's `save()`, `update()`, `delete()`, `load()`, and `get()` are replaced by JPA equivalents (`persist`, `merge`, `remove`, `getReference`, `find`). GORM's own dynamic methods are unaffected.
* **`CascadeType.SAVE_UPDATE` removed** — GORM's ORM DSL `cascade: 'save-update'` string continues to work; direct use of the Hibernate `CascadeType` enum requires migration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,12 @@ Book.executeQuery("from Book where title like ?1", [params.title + '%'])

As in any ORM, manually concatenating untrusted input into a plain `String` (for example `"... where title = '" + userInput + "'"`) remains an injection risk and should be avoided in favour of the GString or parameterized forms above.

===== `findWithSql` / `findAllWithSql` renamed

`findWithSql` and `findAllWithSql` are deprecated. Use `findWithNativeSql` and `findAllWithNativeSql` instead. The old names remain as delegating aliases for backwards compatibility.
===== SQL query helpers

[source,groovy]
----
// Before (deprecated)
Book.findWithSql("select * from book where id = ${params.id}")
Book.findAllWithSql("select * from book where ...")

// After
Book.findAllWithNativeSql("select * from book where ...")
----

==== Schema-per-Tenant — Schema Names Are Now Quoted
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,41 +18,41 @@ under the License.
////

[[querying-native-sql]]
== Native SQL Queries
== SQL Queries

GORM provides `findWithNativeSql` and `findAllWithNativeSql` for executing raw SQL when HQL or the Criteria API cannot express the query you need (e.g. database-specific functions, complex joins, or legacy SQL).
GORM provides `findWithSql` and `findAllWithSql` for executing raw SQL when HQL or the DetachedCriteria API cannot express the query you need (e.g. database-specific functions, complex joins, or legacy SQL).

WARNING: Native SQL bypasses Hibernate's type system and object mapping. Prefer HQL, the Criteria API, or dynamic finders wherever possible. Use native SQL only when there is no higher-level alternative.
WARNING: Raw SQL bypasses Hibernate's type system and object mapping. Prefer HQL, the DetachedCriteria API, or dynamic finders wherever possible. Use SQL only when there is no higher-level alternative.

=== Methods

[cols="1,2"]
|===
| Method | Description

| `findWithNativeSql(CharSequence sql)`
| `findWithSql(CharSequence sql)`
| Returns the first result mapped to the domain class

| `findWithNativeSql(CharSequence sql, Map args)`
| `findWithSql(CharSequence sql, Map args)`
| Returns the first result; `args` controls pagination (`max`, `offset`, `cache`)

| `findAllWithNativeSql(CharSequence sql)`
| `findAllWithSql(CharSequence sql)`
| Returns all results mapped to the domain class

| `findAllWithNativeSql(CharSequence sql, Map args)`
| `findAllWithSql(CharSequence sql, Map args)`
| Returns all results; `args` controls pagination
|===

=== Safe Usage GString Value Parameters
=== Safe Usage - GString Value Parameters

When a query contains user-supplied **values** (not identifiers), use Groovy GString interpolation. GORM extracts each `${expression}` and binds it as a named JDBC parameter, preventing injection.

[source,groovy]
----
String nameFilter = params.name // user input

// SAFE ${nameFilter} is bound as :p0, never inlined into the SQL string
List results = Club.findAllWithNativeSql(
// SAFE - ${nameFilter} is bound as :p0, never inlined into the SQL string
List results = Club.findAllWithSql(
"select * from club c where c.name like ${nameFilter} order by c.name")
----

Expand All @@ -62,36 +62,23 @@ A plain `String` constant with no user data is safe and accepted directly.

[source,groovy]
----
// SAFE no user input, static SQL
List results = Club.findAllWithNativeSql(
// SAFE - no user input, static SQL
List results = Club.findAllWithSql(
"select * from club c order by c.name")
----

=== What Cannot Be Parameterized

SQL identifiers table names, column names, schema names **cannot** be bound as JDBC parameters. Do not interpolate them from user input under any circumstances.
SQL identifiers - table names, column names, schema names - **cannot** be bound as JDBC parameters. Do not interpolate them from user input under any circumstances.

[source,groovy]
----
// UNSAFE table name from user input, cannot be made safe via GString
// UNSAFE - table name from user input, cannot be made safe via GString
String table = params.table
Club.findAllWithNativeSql("select * from ${table}") // DO NOT DO THIS
Club.findAllWithSql("select * from ${table}") // DO NOT DO THIS

// UNSAFE string concatenation, no protection at all
Club.findAllWithNativeSql("select * from club where name = '" + userInput + "'")
// UNSAFE - string concatenation, no protection at all
Club.findAllWithSql("select * from club where name = '" + userInput + "'")
----

If you need dynamic identifiers (e.g. schema-per-tenant), use the JDBC identifier quoting API (`connection.metaData.identifierQuoteString`) to quote and sanitize the name before use — the same mechanism used internally by `DefaultSchemaHandler`.

=== Deprecated Names

`findWithSql` and `findAllWithSql` are deprecated aliases for `findWithNativeSql` and `findAllWithNativeSql`. They remain functional for backwards compatibility but will be removed in a future release.

[source,groovy]
----
// Deprecated — replace with findAllWithNativeSql
Club.findAllWithSql("select * from club")

// Preferred
Club.findAllWithNativeSql("select * from club")
----
If you need dynamic identifiers (e.g. schema-per-tenant), use the JDBC identifier quoting API (`connection.metaData.identifierQuoteString`) to quote and sanitize the name before use - the same mechanism used internally by `DefaultSchemaHandler`.
Loading