This tutorial shows you how to use Hibernate's @HQLSelect
and @SQLSelect
annotations.
In version 6.2, Hibernate added some new annotations. Two of them are @HQLSelect
and @SQLSelect
. They can be used to set the default query when an entity is fetched by ID. In this tutorial, I'm going to explain the usages of those two annotations.
Using @HQLSelect
Annotation
@HQLSelect
is an annotation for specifying a custom query to be used when Hibernate fetches an entity or collection by ID. The query must be written in HQL/JPQL syntax. This can be useful if you want to use a custom criteria. Conditions that use the custom query include:
- When using
Session.get
orSession.find
to fetch an entity. - When an association to an entity is fetched lazily.
- When a collection is fetched lazily
The query must have exactly one parameter whose type is jakarta.persistence.Id
. You can include other fields as the criteria, but you cannot have any additional parameter. That makes sense because Hibernate will only pass one argument when running the query which is the ID. In addition, it must return a single value whose type matches the type of the entity or collection element.
Fetch Entity by ID
For example, there is an entity named Item
. When fetched by ID, we only want to return the record if the value of isActive
is true and the value of isHidden
is false. The solution is to add an @HQLSelect
annotation to specify the custom criteria.
@Entity
@Table(name = "items")
@HQLSelect(query = "SELECT i FROM Item i WHERE i.id = :id AND i.isActive = TRUE AND i.isHidden = FALSE")
public class Item {
@Id
@UuidGenerator
private UUID id;
private String name;
private Boolean isActive;
private Boolean isHidden;
private Category category;
}
Below is the code that fetches the Item
record by the ID.
@Service
public class ItemService {
private final EntityManager entityManager;
public ItemService(EntityManager entityManager) {
this.entityManager = entityManager;
}
public ItemDto getById(UUID id) {
Item item = this.entityManager.find(Item.class, id);
if (item == null) {
throw new RuntimeException("Not found");
}
ItemDto itemDto = ItemDto.builder()
.id(item.getId())
.name(item.getName())
.isActive(item.getIsActive())
.isHidden(item.getIsHidden())
.build();
return itemDto;
}
}
As you can see from the output below, the query defined in the @HQLSelect
annotation is used by Hibernate.
select i1_0.id,i1_0.category_id,i1_0.is_active,i1_0.is_hidden,i1_0.name from items i1_0 where i1_0.id=? and i1_0.is_active=true and i1_0.is_hidden=false
Fetch Entity Association Lazily
There is another entity named Category
which has a boolean field named isVisible
. An Item
belongs to a Category
. When fetching an Item
, we want to lazily fetch the Category
. But we only want to return the Category
if the isVisible
value is true.
@Entity
@Table(name = "categories")
@HQLSelect(query = "SELECT c FROM Category c WHERE c.id = :id AND c.isVisible = TRUE")
public class Category {
@Id
@UuidGenerator
private UUID id;
private String name;
private Boolean isVisible;
}
Then, define the relationship in the Item
class.
public class Item {
// Other fields
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id", nullable = false)
private Category category;
}
Let's assume we have a variable named item
which is a fetched Item
record. When accessing the getCategory()
method, Hibernate will fetch the Category
record by its ID using the query defined in the @HQLSelect
annotation.
Category category = item.getCategory();
Below is the generated query.
select c1_0.id,c1_0.is_visible,c1_0.name from categories c1_0 where c1_0.id=? and c1_0.is_visible=true
Fetch Entity Collection Lazily
We have another entity named Variant
which has a boolean column named isAvailable
. An Item
can have multiple Variant
s. When fetching an Item
, we want to fetch its Variant
s whose isAvailable
value is true.
@Entity
@Table(name = "variants")
public class Variant {
@Id
@UuidGenerator
private UUID id;
private String name;
private Boolean isAvailable;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "item_id", nullable = false)
private Item item;
}
Then, define the relationship in the Item
class.
public class Item {
// Other fields
@OneToMany(fetch = FetchType.LAZY, mappedBy = "item")
@HQLSelect(query = "FROM Variant v WHERE v.id = :id AND v.isAvailable = TRUE")
private List<Variant> variants;
}
Below is the generated query for fetching the variants.
select v1_0.id,v1_0.is_available,v1_0.item_id,v1_0.name from variants v1_0 where v1_0.id=? and v1_0.is_available=true
Another thing that you need to know is the query cannot have multiple selections. Below is the error that you'll get if you define a query with multiple selections.
org.hibernate.query.IllegalQueryOperationException: Query defined multiple selections, return cannot be typed (other that Object[] or Tuple)
Using @SQLSelect
Annotation
There is another annotation @SQLSelect
, which has a similar functionality. The main difference is the query must be written as a native query. Like the @HQLSelect
annotation, it works for the following conditions.
- When using
Session.get
orSession.find
to fetch an entity. - When an association to an entity is fetched lazily.
- When a collection is fetched lazily
For the number of parameters, the query must have one for each column of
- primary key (for an entity)
- foreign key (for a collection)
Fetch Entity by ID
Below is the same criteria for fetching the Item
by ID using the @SQLSelect
annotation.
@Entity
@Table(name = "items")
@SQLSelect(sql = "SELECT * FROM items i WHERE i.id = :id AND i.is_active = TRUE AND i.is_hidden = FALSE")
public class Item {
}
Fetch Entity Association Lazily
Below is the same criteria for fetching the Category
of an Item
using the @SQLSelect
annotation.
@Entity
@Table(name = "categories")
@SQLSelect(sql = "SELECT * FROM categories c WHERE c.id = :id AND c.is_visible = TRUE")
public class Category {
@Id
@UuidGenerator
private UUID id;
private String name;
private Boolean isVisible;
}
Fetch Entity Collection Lazily
Below is the same criteria for fetching the list of Variation
s of an Item
with the @SQLSelect
annotation.
public class Item {
// Other fields
@OneToMany(fetch = FetchType.LAZY, mappedBy = "item")
@SQLSelect(sql = "SELECT * FROM variants v WHERE v.id = :id AND v.is_available = TRUE")
private List<Variant> variants;
}
Summary
The @HqlSelect
and @SqlSelect
annotations can be used to set the query to be used when an entity is fetched by ID, replacing the default query generated by Hibernate. You have to make sure that the custom query has the correct number of parameters. Keep in mind that the annotations only have effect on the conditions mentioned above. In addition, it doesn't have any effect on native queries.
You can also read about: