In this section, we will discuss annotations that modify the SQL or the DDL used by Hibernate to perform a query or generate schema objects. The DDL modifying annotations are important if you use Hibernate to generate your tables.
Using the @Check
Hibernate annotation, you can add the CHECK
constraints to your table. The constraint defined in the @Check
annotation uses the syntax that is supported by the database of your choice.
The following listing adds a constraint to the price
column:
@Entity @Check(constraints = "price >= 0") public class Item { @Id @GeneratedValue private long id; private String description; private double price; // setters and getters }
The DDL generated using PostgreSQL, is shown here:
create table Item ( id int8 not null, description varchar(255), price float8 not null, primary key (id), check (price >= 0) )
Another DDL modifier is the Hibernate annotation @ColumnDefault
that adds a default value to a column when the DDL is generated:
@Entity public class Address { @Id @GeneratedValue private long id; private String street; private String city; @ColumnDefault(value="'US'") private String country; // getters and setters }
This results in the following DDL:
create table Address ( id int8 not null, city varchar(255), country varchar(255) default 'US', street varchar(255), primary key (id) )
The @ColumnTransformer
Hibernate annotation is very useful for manipulating the SQL to perform additional transformation on a column, both at the time of read and write. It modifies the question mark placeholders in the prepared statement of JDBC per the values in this annotation.
In the following example, both the first and last names are converted to uppercase letters at the time of read from and write to the database. (This is useful for implementing case-insensitive text):
@Entity public class Person { @Id @GeneratedValue private long id; @ColumnTransformer(read=("upper(firstname)"), write=("upper(?)")) private String firstname; @ColumnTransformer(read=("upper(lastname)"), write=("upper(?)")) private String lastname; // getters and setters }
Note that both the read
and write
must follow your database syntax. With this annotation, you can perform many clever tricks using low-level SQL syntax.
The @DynamicInsert
and @DynamicUpdate
annotations only exist in Hibernate. When used, Hibernate will generate the SQL only for the columns that are not null. This offers an improvement for tables that have many nullable columns that don't always get set at insert or update time.
Consider the following listing:
@Entity @DynamicInsert @DynamicUpdate public class Person { @Id @GeneratedValue private long id; private String firstname; private String lastname; // getters and setters }
If you only populate the value of firstname
, Hibernate will create the SQL for non-null columns:
insert into Person (firstname, id) values (?, ?)
Otherwise, the SQL will look like this:
insert into Person (firstname, lastname, id) values (?, ?, ?)
It is highly recommended to use these two annotations for tables that have many columns that don't get updated often.
This @Formula
Hibernate annotation is very similar to @ColumnTransformer
, except that it only works on read
, and doesn't have write
capabilities. If you don't need the write
capabilities, you should use @Formula
.
This @SelectBeforeUpdate
annotation is only available in Hibernate and its purpose is to prevent unnecessary updates to the database. If you enable this on an entity, before any update operation, Hibernate performs a select
and compares what's in the session to. what's in the database. If they are different, Hibernate will perform the update. Obviously, this will cost you an extra trip to the database, but it might be useful in certain cases. For example, if your update statement fires a database trigger or an event, it may be undesirable if no updates are made to the values.
There are times when we don't wish to delete an entity by removing the row from the table. In such cases, we may just want to mark the record as deleted. This is called a soft delete. The @SQLDelete
Hibernate annotation can help you achieve this. It is typically accompanied with a @Where
annotation that's needed to filter out the non-deleted rows using the provided SQL clause.
The deleted
column is declared as Boolean
in PostgreSQL, the database used to demonstrate this example, and Hibernate was incorrectly thinking that FALSE
was a column name and adding table alias prefix to it, as in person0_.deleted = person0_.FALSE
, and that's why single quotes are used to prevent Hibernate from doing that. Luckily, PostgreSQL is generous enough to accept a Boolean value quoted or unquoted. Your mileage may vary depending on your choice of the database.
A sample usage is shown in the following listing:
@Entity @SQLDelete(sql="UPDATE person SET deleted = TRUE WHERE id = ?") @Where(clause="deleted = 'FALSE'") public class Person { @Id @GeneratedValue private long id; private String firstname; private String lastname; private boolean deleted; @UpdateTimestamp private Date updateTime; // getters and setters }
Note that this entity has a field called updateTime
, which is annotated with @UpdateTimestamp
. This was intentionally added to demonstrate that in this case Hibernate would not update the timestamp column for you automatically, because when you use this annotation, you effectively force Hibernate to execute the SQL that you provide. So, if you wish to update the timestamp column, you would have to add that to your update statement.
Similar to the SQL Delete
statement, Hibernate also offers other annotations to support custom Insert
and Update
SQL statements. These two annotations are worth considering in some cases. Their usage is simple, so we will not provide a sample listing. However, they are included here to bring them to your attention.
The @SubSelect
and @Synchronize
Hibernate annotations and are very useful to create an immutable entity, typically a collection, from a custom SQL statement. The easiest way to think about this is to think of it as a view from one or more tables. The idea is that you map your entity to an SQL statement, a Subselect, and just perform fetch operations on that entity.
Suppose you have two tables, each of which has its own corresponding JPA entity. If you need an entity that is a summary
view of the joined relationship between these two tables, you can do this using a Sub-Select entity. To demonstrate this, consider the following relations that keep track of a person information as well as their experience with programming languages and when they used it:
As the figure suggests, these two relations may have many columns that might not be interesting in some cases. One thing you can do is to create a VIEW
in the database and map an entity to that view. But in most cases, database schema changes are very difficult to make due to governance and policies.
You can create your own view
in Hibernate and just select the columns from these two tables in which you are interested, as demonstrated in the following listing:
@Entity public class Person { @Id @GeneratedValue private long id; private String firstname; private String lastname; @UpdateTimestamp private Date updateTime; // getters and setters } @Entity public class Language { @Id @GeneratedValue private long id; private String name; private long year; @ManyToOne private Person person; // getters and setters } @Entity @Subselect("select row_number() over() as id, " + " p.firstname, p.lastname, l.name as language, " + " max(l.year) as lastused, count(*) as numyears " + "from person p join language l " + "on p.id = l.person_id " + "group by p.firstname, p.lastname, l.name") @Synchronize({ "person", "language" }) public class Experience { @Id private long id; private String firstname; private String lastname; private String language; private long lastUsed; private long numYears; // getters and setters }
There are a few interesting things to note. First, the column names of the resulting relation match those of the class fields. Second, this uses a built-in PostgreSQL row_number()
to generate a row ID and that is mapped to the entity ID. You don't have to do this, you could designate one or more columns to uniquely identify a row. If you use more than one column for ID, you need to use a composite ID in your entity. Next, note how the SQL includes aggregate functions such as max, count, group by, and so on. This indicates that you have a lot of freedom in composing this query; after all, it is native query.
Finally, let's talk a bit about @Synchronize
. This tells Hibernate that before executing this query, any entity in the session that maps to those tables must be synchronized with the database, that is, flushed. That's because if Hibernate doesn't flush the entities, you may not get the correct result.
To use this entity in your main code, you simply use an HQL statement similar to the following:
List<Experience> resume = session.createQuery("from Experience").list();
The @WhereJoinTable
Hibernate annotation adds the where
clause to the @JoinTable
annotation, which is a JPA annotation. We covered join tables, which is a relation that maps the ID from one table to the ID of another table to create the association. In some cases, besides the two ID columns, you may have other columns in the join table. You can use this annotation to further limit the query results.
We mentioned in an earlier chapter that new versions of Hibernate require that each entity has an ID to uniquely identify a row in a table, even if this identification is meaningless to your problem domain. Your ID column will have a unique and not-null constraint.
While the entity ID is considered to be a surrogate key for the most part, you can still designate other fields to be natural keys for your entity/record. This will have the benefit of adding a unique and not-null constraint to the DDL to further strengthen your data integrity. And you can do this by annotating the appropriate fields with @Naturalld
annotation.
52.15.56.12