SQL/DDL modifier

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.

@Check

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)
    )

@ColumnDefault

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)
    )

@ColumnTransformer

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.

@DynamicInsert and @DynamicUpdate

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.

@Formula

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.

@SelectBeforeUpdate

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.

@SQLDelete and @Where

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.

Note

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.

@SQLInsert and @SQLUpdate

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.

@SubSelect and @Synchronize

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:

@SubSelect and @Synchronize

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();  

@WhereJoinTable

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.

@NaturalId

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.56.12