This page is for learners building Hibernate inside Spring applications. The unique problem is transaction ownership, EntityManager lifecycle, repositories, DTO boundaries, and lazy loading in service methods.
Spring integration is not another Hibernate setup tutorial. It explains how Spring coordinates persistence infrastructure around business use cases.
In standalone Hibernate you open sessions manually. In Spring, EntityManager and transaction management are container-managed. That changes where code belongs.
A transaction should wrap a business action such as transfer employee, approve invoice, reserve inventory, or update profile. It should not be scattered across tiny helper methods.
Most Spring/Hibernate pain comes from returning entities after the transaction closes. Fetch what the response needs inside the service and map it to a DTO.
Spring integrates seamlessly with Hibernate. The most common approach is to use Spring Data JPA with Hibernate as the JPA provider. Spring manages the SessionFactory/EntityManagerFactory, transactions, and exception translation automatically.
# Spring Boot auto-configures Hibernate as JPA provider
spring.datasource.url=jdbc:mysql://localhost:3306/mydb?useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# JPA/Hibernate settings
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
# Connection pool (HikariCP - Spring Boot default)
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
# Hibernate statistics (for debugging)
spring.jpa.properties.hibernate.generate_statistics=false
<dependencies>
<!-- Spring Boot Starter Data JPA (includes Hibernate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional // All methods are transactional by default
public class OrderService {
private final OrderRepository orderRepository;
private final UserRepository userRepository;
private final ProductRepository productRepository;
public OrderService(OrderRepository orderRepository,
UserRepository userRepository,
ProductRepository productRepository) {
this.orderRepository = orderRepository;
this.userRepository = userRepository;
this.productRepository = productRepository;
}
// Read-only transaction (optimization)
@Transactional(readOnly = true)
public List<Order> getUserOrders(Long userId) {
return orderRepository.findByUserId(userId);
}
// Write transaction - rolls back on any RuntimeException
@Transactional
public Order placeOrder(Long userId, Long productId, int quantity) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RuntimeException("User not found"));
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() < quantity) {
throw new RuntimeException("Insufficient stock"); // Triggers rollback
}
// Deduct stock
product.setStock(product.getStock() - quantity);
productRepository.save(product);
// Create order
Order order = new Order();
order.setUser(user);
order.setProduct(product);
order.setQuantity(quantity);
order.setTotal(product.getPrice() * quantity);
return orderRepository.save(order);
// Transaction commits here if no exception
}
// Rollback only on specific exceptions
@Transactional(rollbackFor = {Exception.class},
noRollbackFor = {IllegalArgumentException.class})
public void processPayment(Long orderId) throws Exception {
// ...
}
}
import jakarta.persistence.*;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public class CustomUserRepository {
// Spring injects EntityManager (thread-safe proxy)
@PersistenceContext
private EntityManager em;
public User findById(Long id) {
return em.find(User.class, id);
}
public List<User> findByRole(String role) {
return em.createQuery("FROM User u WHERE u.role = :role", User.class)
.setParameter("role", role)
.getResultList();
}
public User save(User user) {
if (user.getId() == null) {
em.persist(user);
return user;
} else {
return em.merge(user);
}
}
public void delete(Long id) {
User user = em.find(User.class, id);
if (user != null) em.remove(user);
}
// Access Hibernate Session from EntityManager
public void hibernateSpecificOperation(Long id) {
org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
User user = session.get(User.class, id);
// Use Hibernate-specific features
}
}
@Transactional
public void transferEmployee(Long employeeId, Long departmentId) {
Employee employee = employees.findById(employeeId).orElseThrow();
Department department = departments.getReferenceById(departmentId);
employee.transferTo(department);
}
// Hibernate flushes the changed entity when the Spring transaction commits.
Spring commonly applies transactions through a proxy. A self-invocation does not pass through that proxy, so the called method’s transactional annotation may be ignored. Move the boundary to a separate bean, call through the proxied service, or redesign the public transaction entry point.
DTO mapping may read lazy associations.
By default, Spring rolls back for unchecked exceptions and Errors, not every checked exception.
Explore 500+ free tutorials across 20+ languages and frameworks.