Note: The default JPA provider for Spring boot is Hibernate
open-source ORM framework, simplifies db interactions/portability.
Feature | Hibernate | JDBC |
---|---|---|
Abstraction Level | Higher-level ORM | Lower-level database interaction API |
Query Language | HQL (Hibernate Query Language), Criteria API | Plain SQL |
Data Mapping | Automatically maps Java objects to database tables | Manual mapping via SQL and result sets |
Caching | Built-in caching mechanisms | No built-in caching |
Transaction Management | Built-in, integrated with JTA and Spring | Needs to be manually handled |
Lazy Loading | Supported | Not supported |
Hql has entity name in query syntax, sql will have db table name
Cust cust = new Cust();
session.clear()
, session.close();
, session.evict(entity);
, session.detach(entity);
)Note: session.contain(entity);
will check entity is in persistent stage or not
Annotation Name | Definition |
---|---|
@Entity |
Marks a class as an entity |
@Table |
db table details. name,catalogue,schema,unique constraints |
@Id |
Marks primary key field |
@GeneratedValue |
Defines primary key generation strategy |
@Column |
Maps field to column |
mappings |
@OneToMany, @ManyToOne, @ManyToMany, @ManyToMany |
@JoinColumn |
Defines join column for relationships and properties like name |
@JoinTable |
Defines join table for many-to-many relationships |
@Transient |
Excludes field/methods from database mapping |
@NamedQuery |
query expressed as alias in metadata of entity class |
@DynamicUpdate |
class level, while preparing update query, it takes only changed fields |
@DynamicInsert |
class level, while preparing insert query, it takes only non-null fields |
configures entity primary key generation, conjunction with @Id
@GeneratedValue(strategy=GenerationType.AUTO)
GenerationType | Description | Use Case |
---|---|---|
AUTO |
handled by hibernate, table:hibernate_sequence | General use, database-agnostic |
IDENTITY |
handled by db identity column | Databases that support identity columns |
SEQUENCE |
assign primarykey using db sequence | sequence support (e.g., Oracle, PostgreSQL) |
TABLE |
assign primarykey using underlying DB to ensure uniqueness | Databases without sequence or identity support, legacy systems |
static query expressed as alias in metadata of entity class.
Adv: For reusability by alias, maintainability, performance
@NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c WHERE c.name = :name")
@NamedQueries(value={
@NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c WHERE c.name = :name")
})
class Customer {}
Using namedquery in Jpa repository:
@Query(name = "Customer.findByName")
List<Customer> findByName(@Param("name") String name);
Using namedquery in session
List<Customer> customers = session.createNamedQuery("Customer.findByName", Customer.class)
.setParameter("name", name)
.getResultList();
Annotation | Description |
---|---|
@Repository |
Marks class as spring Data repository. |
@Transactional |
rollback behavior for exceptions |
@Query() |
using in jparepository, Specifies custom query |
@Modifying |
Mark method that performs a non-read operation. for @Transactional |
@Param |
Names query method parameter. |
@Lock |
controls concurrent access to data |
@Cacheable |
Caches the method result. |
@PersistenceContext |
Injects an EntityManager. |
@EnableJpaRepositories |
not required in spring boot |
@CacheEvict |
Evicts entries from cache. |
@CachePut |
Updates cache with result. |
@EntityGraph |
Defines entity fetching graph. |
@Procedure(procedureName="") |
Calls a stored procedure. |
@NoRepositoryBean |
Excludes parent interface as repository, when we extends interface. |
@EnableTransactionManagement |
not required in springboot, used in configuration |
@Audited |
track changes and able to query historical data |
@SQLDelete(sql="") |
soft delete, Custom SQL query while we perform delete() |
@Where(clause = "deleted = false") |
works alone with @SQLDelete |
@OptimisticLocking |
dont lock entire row, only lock @version column |
public interface ProductRepository extends JpaRepository<Product, Long> {
@Modifying
@Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select p from Product p where p.id = :id", nativeQuery=false)
@Audited
Product findByIdAndNameWithLock(Long id, @Param("name") String name);
}
Transactional(rollbackFor = { SQLException.class })
Note: Transaction is alterntive for session.begin, session.commit, session.rollback
Syntax: @Transactional(propagation = Propagation.REQUIRED)
Propagation Type | Description | Use Case |
---|---|---|
REQUIRED |
Joins or starts a transaction. | Default, shared transactions. |
REQUIRES_NEW |
Always starts a new transaction. | Independent transactions, logging. |
MANDATORY |
Requires an existing transaction. | Must run within a transaction. |
NESTED |
Nested within an existing one. | Savepoints, nested transactions. |
SUPPORTS |
Joins if exists, else none. | Optional transaction context. |
NOT_SUPPORTED |
Runs outside of transactions. | Ensure no transaction context. |
NEVER |
Fails if a transaction exists. | Must not run within a transaction. |
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/yourdb</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">password</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
//even if we didnt give the xml, in default it will pick hibernate-cfg.xml
Configuration configuration = new Configuration().configure("hibernate-cfg.xml");
configuration.addAnnotatedClass(customerentity.class);
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
Configuration configuration = new Configuration();
configuration.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/yourdb");
configuration.setProperty("hibernate.connection.username", "root");
configuration.setProperty("hibernate.connection.password", "password");
configuration.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
configuration.setProperty("hibernate.show_sql", "true");
configuration.setProperty("hibernate.hbm2ddl.auto", "update");
// Add annotated classes
configuration.addAnnotatedClass(YourEntity.class);
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
JpaRepository Method | Description | Alternative in Hibernate Session |
---|---|---|
save(S entity) |
Saves an entity. | save(Object entity) |
saveAll(Iterable<S> entities) |
Saves all entities. | save(Object entity) (repeat for each entity) |
findById(ID id) |
Retrieves entity by ID. | get(Class<T> entityClass, Serializable id) |
findAll() |
Returns all entities. | createQuery("from Entity") or createCriteria() |
findAllById(Iterable<ID> ids) |
Returns entities by IDs. | Use createQuery or createCriteria with IN clause |
deleteById(ID id) |
Deletes entity by ID. | delete(Object entity) (find first, then delete) |
delete(T entity) |
Deletes an entity. | delete(Object entity) |
deleteAll() |
Deletes all entities. | createQuery("delete from Entity") |
deleteAll(Iterable<? extends T> entities) |
Deletes given entities. | delete(Object entity) (repeat for each entity) |
count() |
Counts all entities. | createQuery("select count(*) from Entity") |
existsById(ID id) |
Checks if ID exists. | findById(ID id) (check if result is present) |
flush() |
Flushes changes to database. | flush() |
saveAndFlush(S entity) |
Saves and flushes entity. | save(Object entity) , followed by flush() |
findAll(Sort sort) |
Returns sorted entities. | createQuery("from Entity order by property") |
findAll(Pageable pageable) |
Returns paginated entities. | Use setFirstResult() and setMaxResults() in query |
findOne(Specification<T> spec) |
Finds one by specification. | Use createQuery with criteria for one result |
findAll(Specification<T> spec) |
Finds all by specification. | Use createQuery with criteria |
findAll(Specification<T> spec, Sort sort) |
Finds and sorts by specification. | Use createQuery with criteria and order by clause |
findAll(Specification<T> s, Pageable p) |
Finds and paginates by specification. | Use createQuery with criteria, and paging methods |
count(Specification<T> spec) |
Counts by specification. | Use createQuery("select count(*) from Entity where criteria") |
exists(Specification<T> spec) |
Checks if exists by specification. | Use createQuery with criteria and check if result is present |
@PersistenceContext
private EntityManager entityManager;
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> employee = cq.from(Employee.class);
Predicate salaryPredicate = cb.equal(employee.get("salary"), salary);
Predicate namePredicate = cb.like(employee.get("firstName"), firstNamePattern);
Predicate orPredicate = cb.or(salaryPredicate, namePredicate);
cq.where(orPredicate);
return entityManager.createQuery(cq).getResultList();
Query query = entityManager.createQuery("SELECT e FROM Entity e WHERE e.name = :name");
query.setParameter("name", "example");
List<Entity> results = query.getResultList();
SessionFactory sessionFactory = new Configuration().configure("hibernate-cfg.xml");
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.applySettings(configuration.getProperties())
.build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.getTransaction().commit();
session.close();
Method | Description | Alternative in JpaRepository |
---|---|---|
beginTransaction() |
Starts a new transaction. | - |
getTransaction() |
Retrieves the current transaction. | - |
save(Object entity) |
Saves an entity. | save(S entity) |
update(Object entity) |
Updates an entity. | save(S entity) |
saveOrUpdate(Object entity) |
Saves or updates an entity. | save(S entity) |
delete(Object entity) |
Deletes an entity. | deleteById(ID id) , delete(S entity) |
get(Class<T> entityClass, Serializable id) |
Hits DB, returns null if not found. |
findById(ID id) |
load(Class<T> entityClass, Serializable id) |
Won’t hit DB until object is used;throws exception if not found. | getReferenceById(ID id) |
createQuery(String hql) |
Creates an HQL query. | @Query("HQL query") |
createSQLQuery(String sql) |
Creates a SQL query. | @Query("SQL query") |
createCriteria(Class<T> entityClass) |
Creates a Criteria query. | @Query with Criteria API or QueryDSL |
flush() |
Syncs changes to the database. | - |
clear() |
Clears the persistence context. | - |
evict(Object entity) |
Detaches an entity. | - |
close() |
Closes the session. | - |
isOpen() |
Checks if session is open. | - |
contains(Object entity) |
Checks if entity is managed. | - |
refresh(Object entity) |
Refreshes entity from database. | - |
merge(Object entity) |
Merges detached entity into session. | save(S entity) (merges implicitly) |
getSessionFactory() |
Gets the session factory. | EntityManagerFactory |
setFlushMode(FlushMode flushMode) |
Sets the flush mode. | @Transactional (flush mode can be managed via transaction settings) |
getFlushMode() |
Gets the current flush mode. | - |
getEntityName(Object object) |
Gets the entity name. | - |
CreateCriteria - programmatically constructing queries for retrieving data
Criteria cr = session.createCriteria(Employee.class).list();
Adding restriction: Criterion salary = cr.add(Restrictions.eq("salary", 2000));
//eq,lt,gt,like,ilike,between,isNull,isNotNull,isEmpty,isNotEmpty
Criterion name = Restrictions.ilike(“firstNname”,”zara%”);
LogicalExpression orExp = Restrictions.or(salary, name); //eq. or,and
cr.add( orExp );
cr.setFirstResult(1);
cr.setMaxResults(10);
cr.addOrder(Order.asc("salary"));
cr.setProjection(Projections.rowCount());
cr.setProjection(Projections.countDistinct("firstName"));
cr.setProjection(Projections.sum("salary"));
FROM clause: String hql = “FROM Employee”; (or) String hql = “FROM com.hibernatebook.criteria.Employee”;
AS clause: String hql = “FROM Employee AS E”; (or) String hql = “FROM Employee E”;
SELECT clause: String hql = “SELECT E.firstName FROM Employee E”;
WHERE clause: String hql = “FROM Employee E WHERE E.id = 10”;
ORDERBY clause: String hql = “FROM Employee E WHERE E.id > 10 ORDER BY E.salary DESC”;
GROUPBY clause: String hql = “SELECT SUM(E.salary), E.firtName FROM Employee E GROUP BY E.firstName”;
UPDATE clause: String hql = “UPDATE Employee set salary = :salary WHERE id = :employee_id”;
DELETE clause: String hql = “DELETE FROM Employee WHERE id = :employee_id”;
INSERT clause: String hql = “INSERT INTO Employee(firstName, lastName, salary)”+”SELECT firstName, lastName, salary FROM old_employee”;
class Customer{
@OneToOne(cascade=CascadeType.ALL)
@JoinColumn(name="customerCart_id") //foreign key column name
private CustomerCart customerCart;
}
class CustomerCart{
private int customerCartId;
@OneToOne(mappedBy="customerCart",
cascade={CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
private Customer customer;
}
CREATE TABLE `customer`( `customer_id` int NOT NULL AUTO_INCREMENT, `customer_name` varchar(22) DEFAULT NULL, PRIMARY KEY (`customer_id`),
`cart_id` int, FOREIGN KEY (`cart_id`) REFERENCES cart(`cart_id`));
CREATE TABLE `cart` ( `cart_id` int NOT NULL AUTO_INCREMENT,`cart_name` varchar NULL, PRIMARY KEY(`cart_id`));
class Cart{
@OneToMany(mappedBy = "cart", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Product> products;
}
class Products{
@ManyToOne
@JoinColumn(name = "cart_id", nullable = false)
private Cart cart;
}
CREATE TABLE Cart (cart_id BIGINT PRIMARY KEY AUTO_INCREMENT);
CREATE TABLE Product (product_id BIGINT PRIMARY KEY AUTO_INCREMENT,
cart_id BIGINT NOT NULL, FOREIGN KEY (cart_id) REFERENCES Cart(cart_id));
public class Student {
@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
public class Course {
@ManyToMany(mappedBy = "courses", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<Student> students = new HashSet<>();
}
related entities are loaded only when they are explicitly accessed.
@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
default for @OneToMany and @ManyToMany:
Note: LazyInitializationException: occurs if we try to use get related entities after session close
related entities are loaded immediately with their parent entity.
default for @ManyToOne and @OneToOne
apply same operation to related entities or joined tables.
Defalt value :No cascade // need to explicitly call save() for related entity
Ex: @OneToOne(cascade=CascadeType.PERSIST) -> save()
session-level cache, stores data specific to single session. default cannot be disabled
Adv: avoid database round-trips.
hold by session factory, shared across multiple sessions.
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
@Entity
@Cacheable
@Cache(region = "authors", usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee { }
Region- logical grouping of cached data. Each region has its own settings ex:eviction policies, time-to-live, concurrency strategies.
CacheConcurrencyStrategy: defines how cached data is accessed and managed
i). READ_ONLY: work for readonly operation
ii). NONSTRICT-READ-WRITE: work for readwrite but one at a time
iii). READ-WRITE: work for readwrite, can be used simultaneously
iv). TRANSACTIONAL: work for transaction
data stored as hashmap where query text param is key, result is value
<propertyerty name="hibernate.cache.use_query_cache">true</property>
Sort sort= Sort.by(Sort.Direction.fromString(sortDirection), sortBy);
PageRequest pageRequest = PageRequest.of(pageNumber-1, pageSize, Sort.by("name").ascending());
return itemRepository.findAll(pageRequest);
Note: url is GET /api/items?page=2&size=10&sort=name,asc&sort=date,desc
Locks are mechanisms used in Hibernate to manage concurrent access to data in a database. They help maintain data integrity and consistency when multiple transactions are trying to access or modify the same entity simultaneously.
Types of Locks Optimistic Locking:
Definition: Optimistic locking assumes that multiple transactions can complete without affecting each other. Instead of locking the data when it is read, it checks for data integrity only when the transaction is about to be committed. Implementation: This is typically achieved using a version field in the entity. When an entity is updated, the version number is incremented. If the version number in the database does not match the one in the entity during an update, a StaleObjectStateException is thrown. Use Case: Suitable for scenarios with low contention and where conflicts are rare. Pessimistic Locking:
Definition: Pessimistic locking assumes that conflicts will occur and locks the data as soon as it is read, preventing other transactions from accessing it until the lock is released. Implementation: Pessimistic Read Lock: Allows multiple transactions to read the data but prevents them from modifying it. Pessimistic Write Lock: Prevents any other transaction from reading or writing the data. Use Case: Suitable for high contention scenarios where conflicts are expected. Lock Modes in Hibernate Hibernate provides several lock modes that can be specified when retrieving an entity:
LockMode.NONE:
No locking is applied. This is the default behavior. LockMode.READ:
A shared lock is acquired for reading the entity. Multiple transactions can read the data concurrently, but no updates are allowed. LockMode.UPGRADE:
An exclusive lock is acquired, preventing other transactions from acquiring any locks on the entity. This is useful when you plan to update the entity. LockMode.OPTIMISTIC:
Used for optimistic locking. Hibernate checks the version of the entity before committing changes. LockMode.OPTIMISTIC_FORCE_INCREMENT:
Similar to OPTIMISTIC, but forces the version number to increment even if no actual changes are made to the entity. LockMode.PESSIMISTIC_READ:
Acquires a pessimistic read lock. Other transactions can read the data but cannot modify it. LockMode.PESSIMISTIC_WRITE:
Acquires a pessimistic write lock, preventing any other transaction from reading or writing the data.
Use Transactions: Always enclose your operations in a transaction block. Begin a transaction, perform operations, and commit or roll back as necessary.
Exception Handling: Use try-catch blocks to catch exceptions related to transactions. Common exceptions include HibernateException, RollbackException, and TransactionRequiredException.
Rollback the Transaction: If an exception occurs, rollback the transaction to revert any changes made during that transaction.
Close the Session: Always ensure that the Hibernate session is closed to free up resources.