intro doc for CRUD SQL and @Generated

This commit is contained in:
Gavin 2023-05-17 12:54:46 +02:00 committed by Christian Beikov
parent ccfb51c500
commit ecaa3f0d62
1 changed files with 70 additions and 0 deletions

View File

@ -81,9 +81,79 @@ Native SQL queries are _not_ automatically filtered by tenant id; you'll have to
[[custom-sql]]
=== Using custom-written SQL
We've already discussed how to run <<native-queries,queries written in SQL>>, but occasionally that's not enough.
Sometimes—but much less often than you might expect—we would like to customize the SQL used by Hibernate to perform basic CRUD operations for an entity or collection.
For this we can use `@SQLInsert` and friends:
[source,java]
----
@Entity
@SQLInsert(sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ", check = COUNT)
@SQLUpdate(sql = "UPDATE person SET name = ? where id = ? ")
@SQLDelete(sql = "UPDATE person SET valid = false WHERE id = ? ")
@SQLSelect(sql = "SELECT id, name FROM person WHERE id = ? and valid = true")
public static class Person { ... }
----
Any SQL statement specified by one of these annotations must have exactly the number of JDBC parameters that Hibernate expects, that is, one for each column mapped by the entity, in the exact order Hibernate expects. In particular, the primary key columns must come last.
However, the `@Column` annotation does lend some flexibility here:
- if a column should not be written as part of the custom `insert` statement, and has no corresponding JDBC parameter in the custom SQL, map it `@Column(insertable=false)`, or
- if a column should not be written as part of the custom `update` statement, and has no corresponding JDBC parameter in the custom SQL, map it `@Column(updatable=false)`.
In either of these cases, it's possible that the custom `insert` or `update` statement assigns a value to the mapped column as it is written, for example, by calling a SQL function.
But our entity instance won't be automatically populated with that value.
We may use the `@Generated` annotation to tell Hibernate to reread the state of the entity after each `insert` and/or `update`.
[[database-generated-columns]]
=== Handling database-generated columns
Sometimes, a column value is assigned or mutated by events that happen in the database, and aren't visible to Hibernate.
For example:
- a table might have a column value populated by a trigger,
- a mapped column might have a default value defined in DDL, or
- a custom SQL `insert` or `update` statement might assign a value to a mapped column, as we saw in the previous subsection.
One way to deal with this situation is to explicitly call `refresh()` at appropriate moments, forcing the session to reread the state of the entity.
But this is annoying.
The `@Generated` annotation relieves us of the burden of explicitly calling `refresh()`.
It specifies that the value of the annotated entity attribute is generated by the database, and that the generated value should be automatically retrieved using a SQL `returning` clause, or separate `select` after it is generated.
A useful example is the following mapping:
[source,java]
----
@Entity
class Entity {
@Generated @Id
@ColumnDefault("gen_random_uuid()")
UUID id;
}
----
The generated DDL is:
[source,sql]
----
create table Entity (
id uuid default gen_random_uuid() not null,
primary key (uuid)
)
----
So here the value of `id` is defined by the column default clause, by calling the PostgreSQL function `gen_random_uuid()`.
When a column value is generated during updates, use `@Generated(event=UPDATE)`.
When a value is generated by both inserts _and_ updates, use `@Generated(event={INSERT,UPDATE})`.
[TIP]
====
For columns which should be generated using a SQL `generated always as` clause, prefer the `@GeneratedColumn` annotation, so that Hibernate automatically generates the correct DDL.
====
[[naming-strategies]]
=== Naming strategies