17. Native SQL Queries

You may also express queries in the native SQL dialect of your database. This is useful if you want to utilize database-specific features such as window functions, Common Table Expressions (CTE) or the CONNECT BY option in Oracle. It also provides a clean migration path from a direct SQL/JDBC based application to Hibernate/JPA. Hibernate also allows you to specify handwritten SQL (including stored procedures) for all create, update, delete, and retrieve operations.

17.1. Creating a native query using JPA

Execution of native SQL queries is controlled via the NativeQuery interface, which is obtained by calling Session.createNativeQuery(). The following sections describe how to use this API for querying.

17.2. Scalar queries

The most basic SQL query is to get a list of scalars (column) values.

Example 570. JPA native query selecting all columns

  1. List<Object[]> persons = entityManager.createNativeQuery(
  2. "SELECT * FROM Person" )
  3. .getResultList();

Example 571. JPA native query with a custom column selection

  1. List<Object[]> persons = entityManager.createNativeQuery(
  2. "SELECT id, name FROM Person" )
  3. .getResultList();
  4. for(Object[] person : persons) {
  5. Number id = (Number) person[0];
  6. String name = (String) person[1];
  7. }

Example 572. Hibernate native query selecting all columns

  1. List<Object[]> persons = session.createNativeQuery(
  2. "SELECT * FROM Person" )
  3. .list();

Example 573. Hibernate native query with a custom column selection

  1. List<Object[]> persons = session.createNativeQuery(
  2. "SELECT id, name FROM Person" )
  3. .list();
  4. for(Object[] person : persons) {
  5. Number id = (Number) person[0];
  6. String name = (String) person[1];
  7. }

These will return a List of Object arrays ( Object[] ) with scalar values for each column in the PERSON table. Hibernate will use java.sql.ResultSetMetadata to deduce the actual order and types of the returned scalar values.

To avoid the overhead of using ResultSetMetadata, or simply to be more explicit in what is returned, one can use addScalar():

Example 574. Hibernate native query with explicit result set selection

  1. List<Object[]> persons = session.createNativeQuery(
  2. "SELECT * FROM Person" )
  3. .addScalar( "id", LongType.INSTANCE )
  4. .addScalar( "name", StringType.INSTANCE )
  5. .list();
  6. for(Object[] person : persons) {
  7. Long id = (Long) person[0];
  8. String name = (String) person[1];
  9. }

Although it still returns an Object arrays, this query will not use the ResultSetMetadata anymore since it explicitly gets the id and name columns as respectively a BigInteger and a String from the underlying ResultSet. This also means that only these two columns will be returned, even though the query is still using * and the ResultSet contains more than the three listed columns.

It is possible to leave out the type information for all or some of the scalars.

Example 575. Hibernate native query with result set selection that’s a partially explicit

  1. List<Object[]> persons = session.createNativeQuery(
  2. "SELECT * FROM Person" )
  3. .addScalar( "id", LongType.INSTANCE )
  4. .addScalar( "name" )
  5. .list();
  6. for(Object[] person : persons) {
  7. Long id = (Long) person[0];
  8. String name = (String) person[1];
  9. }

This is essentially the same query as before, but now ResultSetMetaData is used to determine the type of name, whereas the type of id is explicitly specified.

How the java.sql.Types returned from ResultSetMetaData is mapped to Hibernate types is controlled by the Dialect. If a specific type is not mapped, or does not result in the expected type, it is possible to customize it via calls to registerHibernateType in the Dialect.

17.3. Entity queries

The above queries were all about returning scalar values, basically returning the raw values from the ResultSet.

Example 576. JPA native query selecting entities

  1. List<Person> persons = entityManager.createNativeQuery(
  2. "SELECT * FROM Person", Person.class )
  3. .getResultList();

Example 577. Hibernate native query selecting entities

  1. List<Person> persons = session.createNativeQuery(
  2. "SELECT * FROM Person" )
  3. .addEntity( Person.class )
  4. .list();

Assuming that Person is mapped as a class with the columns id, name, nickName, address, createdOn, and version, the following query will also return a List where each element is a Person entity.

Example 578. JPA native query selecting entities with explicit result set

  1. List<Person> persons = entityManager.createNativeQuery(
  2. "SELECT id, name, nickName, address, createdOn, version " +
  3. "FROM Person", Person.class )
  4. .getResultList();

Example 579. Hibernate native query selecting entities with explicit result set

  1. List<Person> persons = session.createNativeQuery(
  2. "SELECT id, name, nickName, address, createdOn, version " +
  3. "FROM Person" )
  4. .addEntity( Person.class )
  5. .list();

17.4. Handling associations and collections

If the entity is mapped with a many-to-one or a child-side one-to-one to another entity, it is required to also return this when performing the native query, otherwise, a database-specific column not found error will occur.

Example 580. JPA native query selecting entities with many-to-one association

  1. List<Phone> phones = entityManager.createNativeQuery(
  2. "SELECT id, phone_number, phone_type, person_id " +
  3. "FROM Phone", Phone.class )
  4. .getResultList();

Example 581. Hibernate native query selecting entities with many-to-one association

  1. List<Phone> phones = session.createNativeQuery(
  2. "SELECT id, phone_number, phone_type, person_id " +
  3. "FROM Phone" )
  4. .addEntity( Phone.class )
  5. .list();

This will allow the Phone#person to function properly since the many-to-one or one-to-one association is going to use a proxy that will be initialized when being navigated for the first time.

It is possible to eagerly join the Phone and the Person entities to avoid the possible extra roundtrip for initializing the many-to-one association.

Example 582. Hibernate native query selecting entities with joined many-to-one association

  1. List<Object[]> tuples = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Phone ph " +
  4. "JOIN Person pr ON ph.person_id = pr.id" )
  5. .addEntity("phone", Phone.class )
  6. .addJoin( "pr", "phone.person")
  7. .list();
  8. for(Object[] tuple : tuples) {
  9. Phone phone = (Phone) tuple[0];
  10. Person person = (Person) tuple[1];
  11. assertNotNull( person.getName() );
  12. }
  1. SELECT
  2. *
  3. FROM
  4. Phone ph
  5. JOIN
  6. Person pr
  7. ON ph.person_id = pr.id

As seen in the associated SQL query, Hibernate manages to construct the entity hierarchy without requiring any extra database roundtrip.

By default, when using the addJoin() method, the result set will contain both entities that are joined. To construct the entity hierarchy, you need to use a ROOT_ENTITY or DISTINCT_ROOT_ENTITY ResultTransformer.

Example 583. Hibernate native query selecting entities with joined many-to-one association and ResultTransformer

  1. List<Person> persons = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Phone ph " +
  4. "JOIN Person pr ON ph.person_id = pr.id" )
  5. .addEntity("phone", Phone.class )
  6. .addJoin( "pr", "phone.person")
  7. .setResultTransformer( Criteria.ROOT_ENTITY )
  8. .list();
  9. for(Person person : persons) {
  10. person.getPhones();
  11. }

Because of the ROOT_ENTITY ResultTransformer, the query above will return the parent-side as root entities.

Notice that you added an alias name pr to be able to specify the target property path of the join. It is possible to do the same eager joining for collections (e.g. the Phone#calls one-to-many association).

Example 584. JPA native query selecting entities with joined one-to-many association

  1. List<Phone> phones = entityManager.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Phone ph " +
  4. "JOIN phone_call c ON c.phone_id = ph.id", Phone.class )
  5. .getResultList();
  6. for(Phone phone : phones) {
  7. List<Call> calls = phone.getCalls();
  8. }
  1. SELECT *
  2. FROM phone ph
  3. JOIN call c ON c.phone_id = ph.id

Example 585. Hibernate native query selecting entities with joined one-to-many association

  1. List<Object[]> tuples = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Phone ph " +
  4. "JOIN phone_call c ON c.phone_id = ph.id" )
  5. .addEntity("phone", Phone.class )
  6. .addJoin( "c", "phone.calls")
  7. .list();
  8. for(Object[] tuple : tuples) {
  9. Phone phone = (Phone) tuple[0];
  10. Call call = (Call) tuple[1];
  11. }
  1. SELECT *
  2. FROM phone ph
  3. JOIN call c ON c.phone_id = ph.id

At this stage, you are reaching the limits of what is possible with native queries, without starting to enhance the sql queries to make them usable in Hibernate. Problems can arise when returning multiple entities of the same type or when the default alias/column names are not enough.

17.5. Returning multiple entities

Until now, the result set column names are assumed to be the same as the column names specified in the mapping document. This can be problematic for SQL queries that join multiple tables since the same column names can appear in more than one table.

Column alias injection is needed in the following query which otherwise throws NonUniqueDiscoveredSqlAliasException.

Example 586. JPA native query selecting entities with the same column names

  1. List<Object> entities = entityManager.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Person pr, Partner pt " +
  4. "WHERE pr.name = pt.name" )
  5. .getResultList();

Example 587. Hibernate native query selecting entities with the same column names

  1. List<Object> entities = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Person pr, Partner pt " +
  4. "WHERE pr.name = pt.name" )
  5. .list();

The query was intended to return all Person and Partner instances with the same name. The query fails because there is a conflict of names since the two entities are mapped to the same column names (e.g. id, name, version). Also, on some databases, the returned column aliases will most likely be on the form pr.id, pr.name, etc. which are not equal to the columns specified in the mappings (id and name).

The following form is not vulnerable to column name duplication:

Example 588. Hibernate native query selecting entities with the same column names and aliases

  1. List<Object> entities = session.createNativeQuery(
  2. "SELECT {pr.*}, {pt.*} " +
  3. "FROM Person pr, Partner pt " +
  4. "WHERE pr.name = pt.name" )
  5. .addEntity( "pr", Person.class)
  6. .addEntity( "pt", Partner.class)
  7. .list();

There’s no such equivalent in JPA because the javax.persistence.Query interface does not define an addEntity method equivalent.

The {pr.**}** and {pt.} notation used above is shorthand for “all properties”. Alternatively, you can list the columns explicitly, but even in this case, Hibernate injects the SQL column aliases for each property. The placeholder for a column alias is just the property name qualified by the table alias.

17.6. Alias and property references

In most cases, the above alias injection is needed. For queries relating to more complex mappings, like composite properties, inheritance discriminators, collections etc., you can use specific aliases that allow Hibernate to inject the proper aliases.

The following table shows the different ways you can use the alias injection. Please note that the alias names in the result are simply examples, each alias will have a unique and probably different name when used.

Table 9. Alias injection names
DescriptionSyntaxExample

A simple property

{[aliasname].[propertyname]}

A_NAME as {item.name}

A composite property

{[aliasname].[componentname].[propertyname]}

CURRENCY as {item.amount.currency}, VALUE as {item.amount.value}

Discriminator of an entity

{[aliasname].class}

DISC as {item.class}

All properties of an entity

{[aliasname].}

{item.}

A collection key

{[aliasname].key}

ORGID as {coll.key}

The id of a collection

{[aliasname].id}

EMPID as {coll.id}

The element of a collection

{[aliasname].element}

XID as {coll.element}

property of the element in the collection

{[aliasname].element.[propertyname]}

NAME as {coll.element.name}

All properties of the element in the collection

{[aliasname].element.}

{coll.element.}

All properties of the collection

{[aliasname].}

{coll.}

17.7. Returning DTOs (Data Transfer Objects)

It is possible to apply a ResultTransformer to native SQL queries, allowing it to return non-managed entities.

Example 589. Hibernate native query selecting DTOs

  1. public class PersonSummaryDTO {
  2. private Number id;
  3. private String name;
  4. //Getters and setters are omitted for brevity
  5. public Number getId() {
  6. return id;
  7. }
  8. public void setId(Number id) {
  9. this.id = id;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. }
  18. List<PersonSummaryDTO> dtos = session.createNativeQuery(
  19. "SELECT p.id as \"id\", p.name as \"name\" " +
  20. "FROM Person p")
  21. .setResultTransformer( Transformers.aliasToBean( PersonSummaryDTO.class ) )
  22. .list();

There’s no such equivalent in JPA because the javax.persistence.Query interface does not define a setResultTransformer method equivalent.

The above query will return a list of PersonSummaryDTO which has been instantiated and injected the values of id and name into its corresponding properties or fields.

17.8. Handling inheritance

Native SQL queries which query for entities that are mapped as part of an inheritance must include all properties for the base class and all its subclasses.

Example 590. Hibernate native query selecting subclasses

  1. List<CreditCardPayment> payments = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Payment p " +
  4. "JOIN CreditCardPayment cp on cp.id = p.id" )
  5. .addEntity( CreditCardPayment.class )
  6. .list();

There’s no such equivalent in JPA because the javax.persistence.Query interface does not define an addEntity method equivalent.

17.9. Parameters

Native SQL queries support positional as well as named parameters:

Example 591. JPA native query with parameters

  1. List<Person> persons = entityManager.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Person " +
  4. "WHERE name like :name", Person.class )
  5. .setParameter("name", "J%")
  6. .getResultList();

Example 592. Hibernate native query with parameters

  1. List<Person> persons = session.createNativeQuery(
  2. "SELECT * " +
  3. "FROM Person " +
  4. "WHERE name like :name" )
  5. .addEntity( Person.class )
  6. .setParameter("name", "J%")
  7. .list();

17.10. Named SQL queries

Named SQL queries can also be defined during mapping and called in exactly the same way as a named HQL query. In this case, you do not need to call addEntity() anymore.

JPA defines the javax.persistence.NamedNativeQuery annotation for this purpose, and the Hibernate org.hibernate.annotations.NamedNativeQuery annotation extends it and adds the following attributes:

flushMode()

The flush mode for the query. By default, it uses the current Persistence Context flush mode.

cacheable()

Whether the query (results) is cacheable or not. By default, queries are not cached.

cacheRegion()

If the query results are cacheable, name the query cache region to use.

fetchSize()

The number of rows fetched by the JDBC Driver per database trip. The default value is given by the JDBC driver.

timeout()

The query timeout (in seconds). By default, there’s no timeout.

callable()

Does the SQL query represent a call to a procedure/function? The default is false.

comment()

A comment added to the SQL query for tuning the execution plan.

cacheMode()

The cache mode used for this query. This refers to entities/collections returned by the query. The default value is CacheModeType.NORMAL.

readOnly()

Whether the results should be read-only. By default, queries are not read-only so entities are stored in the Persistence Context.

17.10.1. Named SQL queries selecting scalar values

To fetch a single column of given table, the named query looks as follows:

Example 593. Single scalar value NamedNativeQuery

  1. @NamedNativeQuery(
  2. name = "find_person_name",
  3. query =
  4. "SELECT name " +
  5. "FROM Person "
  6. ),

Example 594. JPA named native query selecting a scalar value

  1. List<String> names = entityManager.createNamedQuery(
  2. "find_person_name" )
  3. .getResultList();

Example 595. Hibernate named native query selecting a scalar value

  1. List<String> names = session.getNamedQuery(
  2. "find_person_name" )
  3. .list();

Selecting multiple scalar values is done like this:

Example 596. Multiple scalar values NamedNativeQuery

  1. @NamedNativeQuery(
  2. name = "find_person_name_and_nickName",
  3. query =
  4. "SELECT " +
  5. " name, " +
  6. " nickName " +
  7. "FROM Person "
  8. ),

Without specifying an explicit result type, Hibernate will assume an Object array:

Example 597. JPA named native query selecting multiple scalar values

  1. List<Object[]> tuples = entityManager.createNamedQuery(
  2. "find_person_name_and_nickName" )
  3. .getResultList();
  4. for(Object[] tuple : tuples) {
  5. String name = (String) tuple[0];
  6. String nickName = (String) tuple[1];
  7. }

Example 598. Hibernate named native query selecting multiple scalar values

  1. List<Object[]> tuples = session.getNamedQuery(
  2. "find_person_name_and_nickName" )
  3. .list();
  4. for(Object[] tuple : tuples) {
  5. String name = (String) tuple[0];
  6. String nickName = (String) tuple[1];
  7. }

It’s possible to use a DTO to store the resulting scalar values:

Example 599. DTO to store multiple scalar values

  1. public class PersonNames {
  2. private final String name;
  3. private final String nickName;
  4. public PersonNames(String name, String nickName) {
  5. this.name = name;
  6. this.nickName = nickName;
  7. }
  8. public String getName() {
  9. return name;
  10. }
  11. public String getNickName() {
  12. return nickName;
  13. }
  14. }

Example 600. Multiple scalar values NamedNativeQuery with ConstructorResult

  1. @NamedNativeQuery(
  2. name = "find_person_name_and_nickName_dto",
  3. query =
  4. "SELECT " +
  5. " name, " +
  6. " nickName " +
  7. "FROM Person ",
  8. resultSetMapping = "name_and_nickName_dto"
  9. ),
  10. @SqlResultSetMapping(
  11. name = "name_and_nickName_dto",
  12. classes = @ConstructorResult(
  13. targetClass = PersonNames.class,
  14. columns = {
  15. @ColumnResult(name = "name"),
  16. @ColumnResult(name = "nickName")
  17. }
  18. )
  19. )

Example 601. JPA named native query selecting multiple scalar values into a DTO

  1. List<PersonNames> personNames = entityManager.createNamedQuery(
  2. "find_person_name_and_nickName_dto" )
  3. .getResultList();

Example 602. Hibernate named native query selecting multiple scalar values into a DTO

  1. List<PersonNames> personNames = session.getNamedQuery(
  2. "find_person_name_and_nickName_dto" )
  3. .list();

You can also use the @NamedNativeQuery Hibernate annotation to customize the named query using various configurations such as fetch mode, cacheability, time out interval.

Example 603. Multiple scalar values using ConstructorResult and Hibernate NamedNativeQuery

  1. @NamedNativeQueries({
  2. @NamedNativeQuery(
  3. name = "get_person_phone_count",
  4. query = "SELECT pr.name AS name, count(*) AS phoneCount " +
  5. "FROM Phone p " +
  6. "JOIN Person pr ON pr.id = p.person_id " +
  7. "GROUP BY pr.name",
  8. resultSetMapping = "person_phone_count",
  9. timeout = 1,
  10. readOnly = true
  11. ),
  12. })
  13. @SqlResultSetMapping(
  14. name = "person_phone_count",
  15. classes = @ConstructorResult(
  16. targetClass = PersonPhoneCount.class,
  17. columns = {
  18. @ColumnResult(name = "name"),
  19. @ColumnResult(name = "phoneCount")
  20. }
  21. )
  22. )

Example 604. Hibernate NamedNativeQuery named native query selecting multiple scalar values into a DTO

  1. List<PersonPhoneCount> personNames = session.getNamedNativeQuery(
  2. "get_person_phone_count")
  3. .getResultList();

17.10.2. Named SQL queries selecting entities

Considering the following named query:

Example 605. Single-entity NamedNativeQuery

  1. @NamedNativeQuery(
  2. name = "find_person_by_name",
  3. query =
  4. "SELECT " +
  5. " p.id AS \"id\", " +
  6. " p.name AS \"name\", " +
  7. " p.nickName AS \"nickName\", " +
  8. " p.address AS \"address\", " +
  9. " p.createdOn AS \"createdOn\", " +
  10. " p.version AS \"version\" " +
  11. "FROM Person p " +
  12. "WHERE p.name LIKE :name",
  13. resultClass = Person.class
  14. ),

The result set mapping declares the entities retrieved by this native query. Each field of the entity is bound to an SQL alias (or column name). All fields of the entity including the ones of subclasses and the foreign key columns of related entities have to be present in the SQL query. Field definitions are optional provided that they map to the same column name as the one declared on the class property.

Executing this named native query can be done as follows:

Example 606. JPA named native entity query

  1. List<Person> persons = entityManager.createNamedQuery(
  2. "find_person_by_name" )
  3. .setParameter("name", "J%")
  4. .getResultList();

Example 607. Hibernate named native entity query

  1. List<Person> persons = session.getNamedQuery(
  2. "find_person_by_name" )
  3. .setParameter("name", "J%")
  4. .list();

To join multiple entities, you need to use a SqlResultSetMapping for each entity the SQL query is going to fetch.

Example 608. Joined-entities NamedNativeQuery

  1. @NamedNativeQuery(
  2. name = "find_person_with_phones_by_name",
  3. query =
  4. "SELECT " +
  5. " pr.id AS \"pr.id\", " +
  6. " pr.name AS \"pr.name\", " +
  7. " pr.nickName AS \"pr.nickName\", " +
  8. " pr.address AS \"pr.address\", " +
  9. " pr.createdOn AS \"pr.createdOn\", " +
  10. " pr.version AS \"pr.version\", " +
  11. " ph.id AS \"ph.id\", " +
  12. " ph.person_id AS \"ph.person_id\", " +
  13. " ph.phone_number AS \"ph.number\", " +
  14. " ph.phone_type AS \"ph.type\" " +
  15. "FROM Person pr " +
  16. "JOIN Phone ph ON pr.id = ph.person_id " +
  17. "WHERE pr.name LIKE :name",
  18. resultSetMapping = "person_with_phones"
  19. )
  20. @SqlResultSetMapping(
  21. name = "person_with_phones",
  22. entities = {
  23. @EntityResult(
  24. entityClass = Person.class,
  25. fields = {
  26. @FieldResult( name = "id", column = "pr.id" ),
  27. @FieldResult( name = "name", column = "pr.name" ),
  28. @FieldResult( name = "nickName", column = "pr.nickName" ),
  29. @FieldResult( name = "address", column = "pr.address" ),
  30. @FieldResult( name = "createdOn", column = "pr.createdOn" ),
  31. @FieldResult( name = "version", column = "pr.version" ),
  32. }
  33. ),
  34. @EntityResult(
  35. entityClass = Phone.class,
  36. fields = {
  37. @FieldResult( name = "id", column = "ph.id" ),
  38. @FieldResult( name = "person", column = "ph.person_id" ),
  39. @FieldResult( name = "number", column = "ph.number" ),
  40. @FieldResult( name = "type", column = "ph.type" ),
  41. }
  42. )
  43. }
  44. ),

Example 609. JPA named native entity query with joined associations

  1. List<Object[]> tuples = entityManager.createNamedQuery(
  2. "find_person_with_phones_by_name" )
  3. .setParameter("name", "J%")
  4. .getResultList();
  5. for(Object[] tuple : tuples) {
  6. Person person = (Person) tuple[0];
  7. Phone phone = (Phone) tuple[1];
  8. }

Example 610. Hibernate named native entity query with joined associations

  1. List<Object[]> tuples = session.getNamedQuery(
  2. "find_person_with_phones_by_name" )
  3. .setParameter("name", "J%")
  4. .list();
  5. for(Object[] tuple : tuples) {
  6. Person person = (Person) tuple[0];
  7. Phone phone = (Phone) tuple[1];
  8. }

Finally, if the association to a related entity involves a composite primary key, a @FieldResult element should be used for each foreign key column. The @FieldResult name is composed of the property name for the relationship, followed by a dot (“.”), followed by the name or the field or property of the primary key. For this example, the following entities are going to be used:

Example 611. Entity associations with composite keys and named native queries

  1. @Embeddable
  2. public class Dimensions {
  3. private int length;
  4. private int width;
  5. //Getters and setters are omitted for brevity
  6. }
  7. @Embeddable
  8. public class Identity implements Serializable {
  9. private String firstname;
  10. private String lastname;
  11. //Getters and setters are omitted for brevity
  12. public boolean equals(Object o) {
  13. if ( this == o ) return true;
  14. if ( o == null || getClass() != o.getClass() ) return false;
  15. final Identity identity = (Identity) o;
  16. if ( !firstname.equals( identity.firstname ) ) return false;
  17. if ( !lastname.equals( identity.lastname ) ) return false;
  18. return true;
  19. }
  20. public int hashCode() {
  21. int result;
  22. result = firstname.hashCode();
  23. result = 29 * result + lastname.hashCode();
  24. return result;
  25. }
  26. }
  27. @Entity
  28. public class Captain {
  29. @EmbeddedId
  30. private Identity id;
  31. //Getters and setters are omitted for brevity
  32. }
  33. @Entity
  34. @NamedNativeQueries({
  35. @NamedNativeQuery(name = "find_all_spaceships",
  36. query =
  37. "SELECT " +
  38. " name as \"name\", " +
  39. " model, " +
  40. " speed, " +
  41. " lname as lastn, " +
  42. " fname as firstn, " +
  43. " length, " +
  44. " width, " +
  45. " length * width as surface, " +
  46. " length * width * 10 as volume " +
  47. "FROM SpaceShip",
  48. resultSetMapping = "spaceship"
  49. )
  50. })
  51. @SqlResultSetMapping(
  52. name = "spaceship",
  53. entities = @EntityResult(
  54. entityClass = SpaceShip.class,
  55. fields = {
  56. @FieldResult(name = "name", column = "name"),
  57. @FieldResult(name = "model", column = "model"),
  58. @FieldResult(name = "speed", column = "speed"),
  59. @FieldResult(name = "captain.lastname", column = "lastn"),
  60. @FieldResult(name = "captain.firstname", column = "firstn"),
  61. @FieldResult(name = "dimensions.length", column = "length"),
  62. @FieldResult(name = "dimensions.width", column = "width"),
  63. }
  64. ),
  65. columns = {
  66. @ColumnResult(name = "surface"),
  67. @ColumnResult(name = "volume")
  68. }
  69. )
  70. public class SpaceShip {
  71. @Id
  72. private String name;
  73. private String model;
  74. private double speed;
  75. @ManyToOne(fetch = FetchType.LAZY)
  76. @JoinColumns({
  77. @JoinColumn(name = "fname", referencedColumnName = "firstname"),
  78. @JoinColumn(name = "lname", referencedColumnName = "lastname")
  79. })
  80. private Captain captain;
  81. private Dimensions dimensions;
  82. //Getters and setters are omitted for brevity
  83. }

Example 612. JPA named native entity query with joined associations and composite keys

  1. List<Object[]> tuples = entityManager.createNamedQuery(
  2. "find_all_spaceships" )
  3. .getResultList();
  4. for(Object[] tuple : tuples) {
  5. SpaceShip spaceShip = (SpaceShip) tuple[0];
  6. Number surface = (Number) tuple[1];
  7. Number volume = (Number) tuple[2];
  8. }

Example 613. Hibernate named native entity query with joined associations and composite keys

  1. List<Object[]> tuples = session.getNamedQuery(
  2. "find_all_spaceships" )
  3. .list();
  4. for(Object[] tuple : tuples) {
  5. SpaceShip spaceShip = (SpaceShip) tuple[0];
  6. Number surface = (Number) tuple[1];
  7. Number volume = (Number) tuple[2];
  8. }

17.11. Resolving global catalog and schema in native SQL queries

When using multiple database catalogs and schemas, Hibernate offers the possibility of setting a global catalog or schema so that you don’t have to declare it explicitly for every entity.

Example 614. Setting global catalog and schema

  1. <property name="hibernate.default_catalog" value="crm"/>
  2. <property name="hibernate.default_schema" value="analytics"/>

This way, we can imply the global crm catalog and analytics schema in every JPQL, HQL or Criteria API query.

However, for native queries, the SQL query is passed as is, therefore you need to explicitly set the global catalog and schema whenever you are referencing a database table. Fortunately, Hibernate allows you to resolve the current global catalog and schema using the following placeholders:

{h-catalog}

resolves the current hibernate.default_catalog configuration property value.

{h-schema}

resolves the current hibernate.default_schema configuration property value.

{h-domain}

resolves the current hibernate.default_catalog and hibernate.default_schema configuration property values (e.g. catalog.schema).

With these placeholders, you can imply the catalog, schema, or both catalog and schema for every native query.

So, when running the following native query:

  1. @NamedNativeQuery(
  2. name = "last_30_days_hires",
  3. query =
  4. "select * " +
  5. "from {h-domain}person " +
  6. "where age(hired_on) < '30 days'",
  7. resultClass = Person.class
  8. )

Hibernate is going to resolve the {h-domain} placeholder according to the values of the default catalog and schema:

  1. SELECT *
  2. FROM crm.analytics.person
  3. WHERE age(hired_on) < '30 days'

17.12. Using stored procedures for querying

Hibernate provides support for queries via stored procedures and functions. A stored procedure arguments are declared using the IN parameter type, and the result can be either marked with an OUT parameter type, a REF_CURSOR or it could just return the result like a function.

Example 615. MySQL stored procedure with OUT parameter type

  1. statement.executeUpdate(
  2. "CREATE PROCEDURE sp_count_phones (" +
  3. " IN personId INT, " +
  4. " OUT phoneCount INT " +
  5. ") " +
  6. "BEGIN " +
  7. " SELECT COUNT(*) INTO phoneCount " +
  8. " FROM Phone p " +
  9. " WHERE p.person_id = personId; " +
  10. "END"
  11. );

To use this stored procedure, you can execute the following JPA 2.1 query:

Example 616. Calling a MySQL stored procedure with OUT parameter type using JPA

  1. StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_count_phones");
  2. query.registerStoredProcedureParameter( "personId", Long.class, ParameterMode.IN);
  3. query.registerStoredProcedureParameter( "phoneCount", Long.class, ParameterMode.OUT);
  4. query.setParameter("personId", 1L);
  5. query.execute();
  6. Long phoneCount = (Long) query.getOutputParameterValue("phoneCount");

Example 617. Calling a MySQL stored procedure with OUT parameter type using Hibernate

  1. Session session = entityManager.unwrap( Session.class );
  2. ProcedureCall call = session.createStoredProcedureCall( "sp_count_phones" );
  3. call.registerParameter( "personId", Long.class, ParameterMode.IN ).bindValue( 1L );
  4. call.registerParameter( "phoneCount", Long.class, ParameterMode.OUT );
  5. Long phoneCount = (Long) call.getOutputs().getOutputParameterValue( "phoneCount" );
  6. assertEquals( Long.valueOf( 2 ), phoneCount );

If the stored procedure outputs the result directly without an OUT parameter type:

Example 618. MySQL stored procedure without an OUT parameter type

  1. statement.executeUpdate(
  2. "CREATE PROCEDURE sp_phones(IN personId INT) " +
  3. "BEGIN " +
  4. " SELECT * " +
  5. " FROM Phone " +
  6. " WHERE person_id = personId; " +
  7. "END"
  8. );

You can retrieve the results of the aforementioned MySQL stored procedure as follows:

Example 619. Calling a MySQL stored procedure and fetching the result set without an OUT parameter type using JPA

  1. StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_phones");
  2. query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN);
  3. query.setParameter(1, 1L);
  4. List<Object[]> personComments = query.getResultList();

Example 620. Calling a MySQL stored procedure and fetching the result set without an OUT parameter type using Hibernate

  1. Session session = entityManager.unwrap( Session.class );
  2. ProcedureCall call = session.createStoredProcedureCall( "sp_phones" );
  3. call.registerParameter( 1, Long.class, ParameterMode.IN ).bindValue( 1L );
  4. Output output = call.getOutputs().getCurrent();
  5. List<Object[]> personComments = ( (ResultSetOutput) output ).getResultList();

For a REF_CURSOR result sets, we’ll consider the following Oracle stored procedure:

Example 621. Oracle REF_CURSOR stored procedure

  1. statement.executeUpdate(
  2. "CREATE OR REPLACE PROCEDURE sp_person_phones ( " +
  3. " personId IN NUMBER, " +
  4. " personPhones OUT SYS_REFCURSOR ) " +
  5. "AS " +
  6. "BEGIN " +
  7. " OPEN personPhones FOR " +
  8. " SELECT *" +
  9. " FROM phone " +
  10. " WHERE person_id = personId; " +
  11. "END;"
  12. );

REF_CURSOR result sets are only supported by some relational databases (e.g. Oracle and PostgreSQL), and other database systems JDBC drivers might not support this feature.

This function can be called using the standard Java Persistence API:

Example 622. Calling an Oracle REF_CURSOR stored procedure using JPA

  1. StoredProcedureQuery query = entityManager.createStoredProcedureQuery( "sp_person_phones" );
  2. query.registerStoredProcedureParameter( 1, Long.class, ParameterMode.IN );
  3. query.registerStoredProcedureParameter( 2, Class.class, ParameterMode.REF_CURSOR );
  4. query.setParameter( 1, 1L );
  5. query.execute();
  6. List<Object[]> postComments = query.getResultList();

Example 623. Calling an Oracle REF_CURSOR stored procedure using Hibernate

  1. Session session = entityManager.unwrap(Session.class);
  2. ProcedureCall call = session.createStoredProcedureCall( "sp_person_phones");
  3. call.registerParameter(1, Long.class, ParameterMode.IN).bindValue(1L);
  4. call.registerParameter(2, Class.class, ParameterMode.REF_CURSOR);
  5. Output output = call.getOutputs().getCurrent();
  6. List<Object[]> postComments = ( (ResultSetOutput) output ).getResultList();
  7. assertEquals(2, postComments.size());

If the database defines an SQL function:

Example 624. MySQL function

  1. statement.executeUpdate(
  2. "CREATE FUNCTION fn_count_phones(personId integer) " +
  3. "RETURNS integer " +
  4. "DETERMINISTIC " +
  5. "READS SQL DATA " +
  6. "BEGIN " +
  7. " DECLARE phoneCount integer; " +
  8. " SELECT COUNT(*) INTO phoneCount " +
  9. " FROM Phone p " +
  10. " WHERE p.person_id = personId; " +
  11. " RETURN phoneCount; " +
  12. "END"
  13. );

Because the current StoredProcedureQuery implementation doesn’t yet support SQL functions, we need to use the JDBC syntax.

This limitation is acknowledged and will be addressed by the HHH-10530 issue.

Example 625. Calling a MySQL function

  1. final AtomicReference<Integer> phoneCount = new AtomicReference<>();
  2. Session session = entityManager.unwrap( Session.class );
  3. session.doWork( connection -> {
  4. try (CallableStatement function = connection.prepareCall(
  5. "{ ? = call fn_count_phones(?) }" )) {
  6. function.registerOutParameter( 1, Types.INTEGER );
  7. function.setInt( 2, 1 );
  8. function.execute();
  9. phoneCount.set( function.getInt( 1 ) );
  10. }
  11. } );

Stored procedure queries cannot be paged with setFirstResult()/setMaxResults().

Since these servers can return multiple result sets and update counts, Hibernate will iterate the results and take the first result that is a result set as its return value, so everything else will be discarded.

For SQL Server, if you can enable SET NOCOUNT ON in your procedure it will probably be more efficient, but this is not a requirement.

17.13. Using named queries to call stored procedures

Just like with SQL statements, you can also use named queries to call stored procedures. For this purpose, JPA defines the @NamedStoredProcedureQuery annotation.

Example 626. Oracle REF_CURSOR named query stored procedure

  1. @NamedStoredProcedureQueries(
  2. @NamedStoredProcedureQuery(
  3. name = "sp_person_phones",
  4. procedureName = "sp_person_phones",
  5. parameters = {
  6. @StoredProcedureParameter(
  7. name = "personId",
  8. type = Long.class,
  9. mode = ParameterMode.IN
  10. ),
  11. @StoredProcedureParameter(
  12. name = "personPhones",
  13. type = Class.class,
  14. mode = ParameterMode.REF_CURSOR
  15. )
  16. }
  17. )
  18. )

Calling this stored procedure is straightforward, as illustrated by the following example.

Example 627. Calling an Oracle REF_CURSOR stored procedure using a JPA named query

  1. List<Object[]> postComments = entityManager
  2. .createNamedStoredProcedureQuery( "sp_person_phones" )
  3. .setParameter( "personId", 1L )
  4. .getResultList();

17.14. Custom SQL for CRUD (Create, Read, Update and Delete)

Hibernate can use custom SQL for CRUD operations. The SQL can be overridden at the statement level or individual column level. This section describes statement overrides. For columns, see Column transformers: read and write expressions.

The following example shows how to define custom SQL operations using annotations. @SQLInsert, @SQLUpdate, and @SQLDelete override the INSERT, UPDATE, DELETE statements of a given entity. For the SELECT clause, a @Loader must be defined along with a @NamedNativeQuery used for loading the underlying table record.

For collections, Hibernate allows defining a custom @SQLDeleteAll which is used for removing all child records associated with a given parent entity. To filter collections, the @Where annotation allows customizing the underlying SQL WHERE clause.

Example 628. Custom CRUD

  1. @Entity(name = "Person")
  2. @SQLInsert(
  3. sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
  4. check = ResultCheckStyle.COUNT
  5. )
  6. @SQLUpdate(
  7. sql = "UPDATE person SET name = ? where id = ? "
  8. )
  9. @SQLDelete(
  10. sql = "UPDATE person SET valid = false WHERE id = ? "
  11. )
  12. @Loader(namedQuery = "find_valid_person")
  13. @NamedNativeQueries({
  14. @NamedNativeQuery(
  15. name = "find_valid_person",
  16. query = "SELECT id, name " +
  17. "FROM person " +
  18. "WHERE id = ? and valid = true",
  19. resultClass = Person.class
  20. )
  21. })
  22. public static class Person {
  23. @Id
  24. @GeneratedValue
  25. private Long id;
  26. private String name;
  27. @ElementCollection
  28. @SQLInsert(
  29. sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
  30. @SQLDeleteAll(
  31. sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
  32. @Where( clause = "valid = true" )
  33. private List<String> phones = new ArrayList<>();
  34. //Getters and setters are omitted for brevity
  35. }

In the example above, the entity is mapped so that entries are soft-deleted (the records are not removed from the database, but instead, a flag marks the row validity). The Person entity benefits from custom INSERT, UPDATE, and DELETE statements which update the valid column accordingly. The custom @Loader is used to retrieve only Person rows that are valid.

The same is done for the phones collection. The @SQLDeleteAll and the SQLInsert queries are used whenever the collection is modified.

You can also call a store procedure using the custom CRUD statements. The only requirement is to set the callable attribute to true.

To check that the execution happens correctly, Hibernate allows you to define one of those three strategies:

  • none: no check is performed; the store procedure is expected to fail upon constraint violations.

  • count: use of row-count returned by the executeUpdate() method call to check that the update was successful.

  • param: like count but using a CallableStatement output parameter.

To define the result check style, use the check parameter.

The parameter order is important and is defined by the order Hibernate handles properties. You can see the expected order by enabling debug logging, so Hibernate can print out the static SQL that is used to create, update, delete entities.

To see the expected sequence, remember to not include your custom SQL through annotations or mapping files as that will override the Hibernate generated static SQL.

Overriding SQL statements for secondary tables is also possible using @org.hibernate.annotations.Table and the sqlInsert, sqlUpdate, sqlDelete attributes.

Example 629. Overriding SQL statements for secondary tables

  1. @Entity(name = "Person")
  2. @Table(name = "person")
  3. @SQLInsert(
  4. sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) "
  5. )
  6. @SQLDelete(
  7. sql = "UPDATE person SET valid = false WHERE id = ? "
  8. )
  9. @SecondaryTable(name = "person_details",
  10. pkJoinColumns = @PrimaryKeyJoinColumn(name = "person_id"))
  11. @org.hibernate.annotations.Table(
  12. appliesTo = "person_details",
  13. sqlInsert = @SQLInsert(
  14. sql = "INSERT INTO person_details (image, person_id, valid) VALUES (?, ?, true) ",
  15. check = ResultCheckStyle.COUNT
  16. ),
  17. sqlDelete = @SQLDelete(
  18. sql = "UPDATE person_details SET valid = false WHERE person_id = ? "
  19. )
  20. )
  21. @Loader(namedQuery = "find_valid_person")
  22. @NamedNativeQueries({
  23. @NamedNativeQuery(
  24. name = "find_valid_person",
  25. query = "SELECT " +
  26. " p.id, " +
  27. " p.name, " +
  28. " pd.image " +
  29. "FROM person p " +
  30. "LEFT OUTER JOIN person_details pd ON p.id = pd.person_id " +
  31. "WHERE p.id = ? AND p.valid = true AND pd.valid = true",
  32. resultClass = Person.class
  33. )
  34. })
  35. public static class Person {
  36. @Id
  37. @GeneratedValue
  38. private Long id;
  39. private String name;
  40. @Column(name = "image", table = "person_details")
  41. private byte[] image;
  42. //Getters and setters are omitted for brevity
  43. }

The SQL is directly executed in your database, so you can use any dialect you like. This will, however, reduce the portability of your mapping if you use database-specific SQL.

You can also use stored procedures for customizing the CRUD statements.

Assuming the following stored procedure:

Example 630. Oracle stored procedure to soft-delete a given entity

  1. statement.executeUpdate(
  2. "CREATE OR REPLACE PROCEDURE sp_delete_person ( " +
  3. " personId IN NUMBER ) " +
  4. "AS " +
  5. "BEGIN " +
  6. " UPDATE person SET valid = 0 WHERE id = personId; " +
  7. "END;"
  8. );}

The entity can use this stored procedure to soft-delete the entity in question:

Example 631. Customizing the entity delete statement to use the Oracle stored procedure= instead

  1. @SQLDelete(
  2. sql = "{ call sp_delete_person( ? ) } ",
  3. callable = true
  4. )

You need to set the callable attribute when using a stored procedure instead of an SQL statement.