CRUD Operations with Hibernate (Create, Read, Update, Delete)
This is a hands-on lesson demonstrating how to perform basic CRUD operations using Hibernate. You'll learn how to save new objects to the database (Create), retrieve objects (Read), modify existing objects (Update), and delete objects (Delete) using Hibernate's API.
Hibernate Caching
What is Caching in Hibernate?
Caching in Hibernate is a mechanism to store frequently accessed data in a temporary storage area (the cache) to minimize database access. This reduces the number of database queries, leading to significant performance improvements, especially for read-heavy applications. Instead of always querying the database, Hibernate first checks the cache. If the data is found in the cache (a "cache hit"), it's retrieved from there, avoiding a costly database round trip. If the data isn't found in the cache (a "cache miss"), Hibernate retrieves it from the database, updates the cache, and then returns the data to the application.
Hibernate's Caching Mechanisms
Hibernate provides two levels of caching:
First-Level Cache (Session Cache)
The first-level cache is associated with a Session
object. It's a mandatory, single-instance cache that's enabled by default. Hibernate always uses the first-level cache.
- Scope: The first-level cache is tied to the current
Session
. Data stored in the cache is only available within that session. - Automatic: Hibernate manages it automatically. You don't need to explicitly configure it.
- How it works: When you load an entity using
session.get()
orsession.load()
, Hibernate first checks if the entity is already present in the session's cache. If it is, the entity is retrieved from the cache. Otherwise, Hibernate retrieves the entity from the database and stores it in the cache. Subsequent requests for the same entity within the same session will retrieve it from the cache. - Disabling: You can clear the session cache using
session.evict(entity)
(to remove a specific entity) orsession.clear()
(to remove all entities from the session cache). However, generally, it is not recommended to disable or heavily manipulate the first-level cache unless you have specific performance reasons to do so, as it's a fundamental part of Hibernate's identity management.
Second-Level Cache (SessionFactory Cache)
The second-level cache is associated with a SessionFactory
. It's an optional cache that can be shared by multiple sessions and even multiple applications using the same database. This provides a significant performance boost when the same data is frequently accessed by different sessions or users.
- Scope: The second-level cache is shared across multiple
Session
objects, all associated with the sameSessionFactory
. - Configuration Required: It is not enabled by default and needs to be explicitly configured. You'll need to choose a cache provider (like Ehcache, Hazelcast, or Infinispan) and configure Hibernate to use it.
- How it works: When a session retrieves an entity, Hibernate first checks the first-level cache. If the entity isn't found there, Hibernate checks the second-level cache. If it's found in the second-level cache, it's retrieved and stored in the first-level cache. If it's not found in either cache, Hibernate retrieves the entity from the database, stores it in both the first-level and second-level caches. Subsequent requests for the same entity by any session associated with the same
SessionFactory
will retrieve it from the second-level cache (after checking the first-level cache). - Concurrency Strategies: Because the second-level cache is shared, Hibernate provides different concurrency strategies to manage concurrent access to cached data. Common strategies include:
read-only
: Suitable for entities that never change.nonstrict-read-write
: Optimistic locking suitable for entities with infrequent updates.read-write
: Provides more robust concurrency control using locking.transactional
: Suitable for transactional environments where strict isolation is required.
Configuring and Using Caching to Improve Performance
1. Choosing a Cache Provider (Second-Level Cache)
First, you need to choose a suitable cache provider and add its dependency to your project. For example, to use Ehcache:
<!-- Maven dependency for Ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version> <!-- Use the latest version -->
</dependency>
2. Configuring Hibernate to use the Second-Level Cache
You configure Hibernate to use the second-level cache in your hibernate.cfg.xml
file (or programmatically using Configuration
). You also specify the cache provider class:
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/your_database</property>
<property name="connection.username">your_username</property>
<property name="connection.password">your_password</property>
<!-- Hibernate properties -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<!-- Enable Second-Level Cache -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- Cache provider class -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
<!-- Optional: Configuration file for Ehcache (ehcache.xml) -->
<property name="hibernate.cache.ehcache.configurationResourceName">/ehcache.xml</property>
<!-- Mappings -->
<mapping class="com.example.model.YourEntity"/>
</session-factory>
</hibernate-configuration>
3. Configuring Cacheable Entities
You need to mark your entities as cacheable using the @Cache
annotation (or equivalent XML configuration). You also specify the concurrency strategy.
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import javax.persistence.*;
@Entity
@Table(name = "your_entity")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // Choose the appropriate strategy
public class YourEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name")
private String name;
// Getters and setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
4. Ehcache Configuration (ehcache.xml - Example)
Create an ehcache.xml
file (if you're using Ehcache) to configure the cache regions. Place it in your classpath.
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true"> <diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<cache name="com.example.model.YourEntity"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
5. Code Example (Using the Cache)
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import com.example.model.YourEntity;
public class Main {
public static void main(String[] args) {
Configuration configuration = new Configuration().configure();
SessionFactory sessionFactory = configuration.buildSessionFactory();
try (Session session1 = sessionFactory.openSession()) {
// First time retrieving the entity - will be loaded from the database
YourEntity entity1 = session1.get(YourEntity.class, 1L);
System.out.println("Entity 1: " + entity1.getName());
}
try (Session session2 = sessionFactory.openSession()) {
// Second time retrieving the entity (different session) - will be retrieved from the second-level cache
YourEntity entity2 = session2.get(YourEntity.class, 1L);
System.out.println("Entity 2: " + entity2.getName());
}
sessionFactory.close();
}
}
6. Important Considerations
- Cache Invalidation: It's crucial to ensure that the cache is invalidated when data changes in the database. Hibernate handles this automatically for basic CRUD operations, but you might need to manually invalidate the cache for more complex scenarios. This often involves evicting entities or entire cache regions.
- Data Consistency: When using a second-level cache, you need to be aware of potential data consistency issues. If multiple applications are accessing and modifying the same data, you need to carefully choose a concurrency strategy that ensures data integrity.
- Cache Size: Appropriately sizing the cache is important. A cache that is too small will result in frequent cache misses, negating the benefits of caching. A cache that is too large can consume excessive memory.
- Query Cache: Hibernate also provides a query cache, which caches the results of queries. To use the query cache, you need to enable it in your configuration file (
hibernate.cache.use_query_cache=true
) and explicitly mark queries as cacheable (query.setCacheable(true)
). Be aware that query cache stores the *identifiers* of the resulting entities, not the entities themselves. The entities are then retrieved from the entity cache (first or second level). Therefore, query cache is useful only in conjunction with entity cache.