Spring Object/Relational Mapping integration for JPA and Hibernate with transaction management
Exception translation from JPA and Hibernate exceptions to Spring's DataAccessException hierarchy for consistent error handling across persistence technologies.
Spring automatically translates JPA/Hibernate exceptions to DataAccessException:
@Service
@Transactional
public class UserService {
@PersistenceContext
private EntityManager entityManager;
public User findById(Long id) {
try {
User user = entityManager.find(User.class, id);
if (user == null) {
throw new EntityNotFoundException("User not found: " + id);
}
return user;
} catch (EntityNotFoundException ex) {
// Automatically translated to JpaObjectRetrievalFailureException
throw ex;
}
}
public void updateUser(User user) {
try {
entityManager.merge(user);
} catch (OptimisticLockException ex) {
// Automatically translated to JpaOptimisticLockingFailureException
throw ex;
}
}
}// Entity not found
class JpaObjectRetrievalFailureException extends ObjectRetrievalFailureException {
JpaObjectRetrievalFailureException(EntityNotFoundException ex);
}
// Optimistic locking failure
class JpaOptimisticLockingFailureException extends ObjectOptimisticLockingFailureException {
JpaOptimisticLockingFailureException(OptimisticLockException ex);
}
// Uncategorized errors
class JpaSystemException extends UncategorizedDataAccessException {
JpaSystemException(RuntimeException ex);
}// Optimistic locking violation
class ObjectOptimisticLockingFailureException extends OptimisticLockingFailureException {
ObjectOptimisticLockingFailureException(Class<?> persistentClass, Object identifier);
ObjectOptimisticLockingFailureException(String persistentClassName, Object identifier);
ObjectOptimisticLockingFailureException(
Class<?> persistentClass, Object identifier, String msg, Throwable cause);
Class<?> getPersistentClass();
String getPersistentClassName();
Object getIdentifier();
}
// Object not found
class ObjectRetrievalFailureException extends DataRetrievalFailureException {
ObjectRetrievalFailureException(Class<?> persistentClass, Object identifier);
ObjectRetrievalFailureException(String persistentClassName, Object identifier);
ObjectRetrievalFailureException(
Class<?> persistentClass, Object identifier, String msg, Throwable cause);
Class<?> getPersistentClass();
String getPersistentClassName();
Object getIdentifier();
}import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(JpaObjectRetrievalFailureException.class)
public ResponseEntity<ErrorResponse> handleObjectNotFound(
JpaObjectRetrievalFailureException ex) {
ErrorResponse error = new ErrorResponse();
error.setMessage("Entity not found: " +
ex.getPersistentClassName() + " with id " + ex.getIdentifier());
error.setStatus(HttpStatus.NOT_FOUND.value());
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(JpaOptimisticLockingFailureException.class)
public ResponseEntity<ErrorResponse> handleOptimisticLocking(
JpaOptimisticLockingFailureException ex) {
ErrorResponse error = new ErrorResponse();
error.setMessage("The entity was modified by another transaction. " +
"Please reload and try again.");
error.setStatus(HttpStatus.CONFLICT.value());
return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
}
@ExceptionHandler(JpaSystemException.class)
public ResponseEntity<ErrorResponse> handleJpaSystemException(
JpaSystemException ex) {
ErrorResponse error = new ErrorResponse();
error.setMessage("A system error occurred: " + ex.getMessage());
error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDataAccessException(
DataAccessException ex) {
ErrorResponse error = new ErrorResponse();
error.setMessage("Data access error: " + ex.getMessage());
error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}@Service
@Transactional
public class OrderService {
@PersistenceContext
private EntityManager entityManager;
public Order createOrder(OrderRequest request) {
try {
// Verify product exists
Product product = entityManager.find(Product.class, request.getProductId());
if (product == null) {
throw new ObjectRetrievalFailureException(
Product.class, request.getProductId());
}
// Create order
Order order = new Order();
order.setProduct(product);
order.setQuantity(request.getQuantity());
entityManager.persist(order);
return order;
} catch (EntityNotFoundException ex) {
// Translated to JpaObjectRetrievalFailureException
throw ex;
} catch (PersistenceException ex) {
// Translated to JpaSystemException
throw ex;
}
}
public void updateOrder(Long orderId, int newQuantity) {
try {
Order order = entityManager.find(Order.class, orderId);
if (order == null) {
throw new ObjectRetrievalFailureException(Order.class, orderId);
}
order.setQuantity(newQuantity);
entityManager.merge(order);
} catch (OptimisticLockException ex) {
// Translated to JpaOptimisticLockingFailureException
throw new ObjectOptimisticLockingFailureException(
Order.class, orderId,
"Order was modified by another user", ex
);
}
}
}For Hibernate-specific exception translation:
import org.springframework.orm.hibernate5.HibernateExceptionTranslator;
import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator;
import org.springframework.jdbc.support.SQLExceptionTranslator;
@Configuration
public class HibernateConfig {
@Bean
public HibernateExceptionTranslator hibernateExceptionTranslator(
DataSource dataSource) {
HibernateExceptionTranslator translator = new HibernateExceptionTranslator();
// Use custom JDBC exception translator
SQLExceptionTranslator jdbcTranslator =
new SQLErrorCodeSQLExceptionTranslator(dataSource);
translator.setJdbcExceptionTranslator(jdbcTranslator);
return translator;
}
}class HibernateExceptionTranslator implements PersistenceExceptionTranslator {
// Set JDBC exception translator
void setJdbcExceptionTranslator(SQLExceptionTranslator translator);
// Translate exception
DataAccessException translateExceptionIfPossible(RuntimeException ex);
}
@FunctionalInterface
interface PersistenceExceptionTranslator {
// Returns null if exception should not be translated
DataAccessException translateExceptionIfPossible(RuntimeException ex);
}| Exception | Description |
|---|---|
DataIntegrityViolationException | Constraint violations (unique, foreign key, etc.) |
DataAccessResourceFailureException | Resource failures (connection lost, etc.) |
InvalidDataAccessApiUsageException | Invalid use of persistence API |
OptimisticLockingFailureException | Optimistic locking violations |
PessimisticLockingFailureException | Pessimistic locking failures |
DataRetrievalFailureException | Failed to retrieve data |
UncategorizedDataAccessException | Uncategorized or unknown errors |
These exceptions allow consistent error handling regardless of the underlying persistence technology (JPA, Hibernate, JDBC, etc.).
Install with Tessl CLI
npx tessl i tessl/maven-org-springframework--spring-orm