Hibernate
Hibernate is a popular implementation (provider) of the JPA (Java Persistence API) specification.
JPA is only the interface.
Hibernate is one of the tools that implements that interface
Further information
Link: Hibernate
Non JPA Annotations
Some additional useful annotations for Hibernate
Formula
Let's define a computed property on an entity, based on a SQL expression. It’s a read-only virtual column whose value is computed when the entity is fetched from the database. The formula SQL will be included in the main query. So no additional query call.
- Read-only: You can’t write to a @Formula field — it’s computed on the fly.
- It executes in SQL, not in Java — so you can use DB functions, subqueries, CASE statements, etc.
- Can be used for custom calculations, aggregations, or even pulling data from other tables.
Cons:
- Because it's SQL-based, it can reduce portability across different databases.
- It’s not lazy — the formula is always evaluated with the entity’s main query.
- It doesn’t support parameters — so no dynamic content.
@Entity
public class Person {
...
@Formula("CONCAT(first_name, ' ', last_name)")
private String fullName;
}
Where
It is used to add a SQL clause to all queries involving an entity or a collection. It's especially helpful in things like soft deletes or filtering related entities.
@Entity
@Where(clause = "deleted = false")
public class Author {
@Id
private Long id;
private String name;
...
}
Every time Hibernate runs, a query on Author WHERE delted = falsewill be added to the SQL. Like a global filter.
SQLDelete / SQLInsert
It allows you to override the default DELETE / INSERT SQL command used by Hibernate when you delete/adding an entity. It’s part of Hibernate-specific extensions (not standard JPA).
Soft Deletes
Instead of actually removing a row from the database, you might just want to mark it as deleted by setting a column like deleted = true.
@Entity
@SQLDelete(sql = "UPDATE user SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class User {
...
}
Cache
Tells Hibernate which entities or associations to store in the second-level cache and how.
JPA (and Hibernate) has two levels of cache:
- First-level cache: Built-in, per EntityManager session. Always enabled.
- Second-level cache: Optional, shared across sessions, and can significantly improve performance by avoiding database hits for frequently accessed entities.
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {
...
}
- It caches the entity.
- Updates to it also update the cache to keep things in sync.
Strategy | Description |
---|---|
READ_ONLY | Best for data that never changes. Fastest. |
NONSTRICT_READ_WRITE | Allows stale data; no strict guarantees, good for mostly-read data. |
READ_WRITE | Ensures cache is in sync with DB. Slightly slower, but safe. |
TRANSACTIONAL | Integrates with JTA transactions. |
DynamicUpdate
It optimizes SQL UPDATE statements by generating them dynamically at runtime, ignoring all not modified columns. Normally, if only one property changes, JPA created an update with all values.
Pros
- Performance boost.
- Reduces unnecessary DB writes.
- Helps in avoiding overwrite issues with DB triggers or audit fields.
Cons
- Slight overhead: Hibernate must track dirty fields more carefully.
- Only works with Hibernate (not portable JPA).
- Doesn’t help with @Version fields in optimistic locking (those still get updated).
SelectBeforeUpdate
Issue a SELECT before doing an UPDATE to check whether the entity has actually changed.
Can be used with @DynamicUpdate
Pros
- Prevents unnecessary updates
- Useful with complex dirty checking
- Avoids overwriting unchanged data
Cons
- Adds extra SELECT overhead
- Slower for large update batches
- Not needed in most basic CRUD scenarios
Query Hints
Query hints are instructions to give the JPA provider information how to execute a query. This information will make some adjustments, which might improve the performance or memory usage.
ReadOnly
Using org.hibernate.readonly
tells hibernate that the data will only be read. So there is no need for dirty checking. This will increase the speed of the query
FetchSize
org.hibernate.fetchSize
Tells hibernate how many rows should get fetched with the query.
Cacheable
org.hibernate.cacheable
turns on the 2nd-level cache. This will improve the speed for repeated queries.
To activate the cache, you have to turn it on in the application.properties
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
then activate it on the entity class
@Entity
@Table(name = "customer")
@Cacheable
public class Customer {}
Timeout
jakarta.persistence.query.timeout
defines when to cancel a long-running query.
Example with all query hints
@Repository
public interface CustomerRepository extends JpaRepository<Product, Long> {
@Query("SELECT c FROM Customer c WHERE ...")
@QueryHints({
@QueryHint(name = "org.hibernate.readOnly", value = "true"),
@QueryHint(name = "org.hibernate.fetchSize", value = "50"),
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
@QueryHint(name = "jakarta.persistence.query.timeout", value = "2000")
})
List<Product> findCustomers(@Param("type") String type);
}