Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
DataAccessException в Spring Framework
DataAccessException — это корневое unchecked исключение в Spring Framework для обработки ошибок доступа к данным. Это главное средство абстрагирования специфических SQL исключений от конкретных БД.
Проблема без DataAccessException
Разные БД выбрасывают разные исключения при одних и тех же ошибках:
// ❌ Без Spring — зависимость от конкретной БД
try {
// JDBC код
statement.executeUpdate(query);
} catch (SQLException ex) {
// SQLState код зависит от БД:
// PostgreSQL: 23505 для UNIQUE violation
// MySQL: 1062 для duplicate entry
// Oracle: ORA-00001
if (ex.getErrorCode() == 23505) {
// обработка duplicate
}
}
Это затрудняет переход между БД и усложняет код.
Решение: Spring DataAccessException
Spring преобразует database-specific исключения в hierarchical runtime исключения:
// ✅ С Spring — независимо от БД
@Repository
public class UserRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public void createUser(String email) {
try {
String sql = "INSERT INTO users (email) VALUES (?)";
jdbcTemplate.update(sql, email);
} catch (DataIntegrityViolationException ex) {
// Сработает для любой БД при UNIQUE violation
throw new UserEmailAlreadyExistsException(
"Email already registered", ex
);
}
}
}
Иерархия DataAccessException
Spring предоставляет специализированные исключения для разных сценариев:
Основные типы:
-
DataIntegrityViolationException — нарушение целостности:
- UNIQUE constraint violation
- FOREIGN KEY violation
- NOT NULL violation
-
DataAccessResourceFailureException — проблема с ресурсом:
- Нет соединения с БД
- Connection pool истощен
-
InvalidDataAccessApiUsageException — неправильное использование API:
- Неправильный SQL синтаксис
- Неправильное количество параметров
-
PermissionDeniedDataAccessException — отсутствие прав доступа
-
BadSqlGrammarException — синтаксическая ошибка в SQL
-
OptimisticLockingFailureException — конфликт при оптимистичной блокировке
Практические примеры
Пример 1: Обработка UNIQUE constraint
@Service
public class UserService {
@Autowired
private UserRepository repository;
public UserDto createUser(CreateUserRequest request) {
try {
User user = new User(request.getEmail());
repository.save(user);
return UserDto.from(user);
} catch (DataIntegrityViolationException ex) {
// Spring преобразовал SQLException в специализированное исключение
throw new ApplicationException(
"User with this email already exists",
ErrorCode.EMAIL_ALREADY_EXISTS
);
}
}
}
Пример 2: Обработка проблемы соединения
@Repository
public class OrderRepository extends JpaRepository<Order, Long> {
public List<Order> findActiveOrders() {
try {
return findByStatusActive();
} catch (DataAccessResourceFailureException ex) {
// БД недоступна — можно запросить fallback сервис
log.error("Database unavailable, using cache", ex);
return cacheService.getActiveOrders();
}
}
}
Пример 3: JPA и DataAccessException
@Service
public class ProductService {
@Autowired
private ProductRepository repository;
@Autowired
private PlatformTransactionManager transactionManager;
@Transactional
public void updateProduct(Long id, UpdateProductRequest request) {
Product product = repository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("Product not found"));
try {
product.setName(request.getName());
product.setPrice(request.getPrice());
repository.save(product);
} catch (DataIntegrityViolationException ex) {
throw new ValidationException("Invalid product data", ex);
} catch (DataAccessException ex) {
// Generic обработка любой BD ошибки
throw new DataAccessException("Failed to update product", ex);
}
}
}
Пример 4: Custom SQLExceptionTranslator
@Configuration
public class DataAccessConfig {
@Bean
public SQLExceptionTranslator customExceptionTranslator() {
return (task, sql, ex) -> {
// Custom логика преобразования SQLException
if (ex.getMessage().contains("deadlock")) {
return new DeadlockLoserDataAccessException(
"Deadlock detected", ex
);
}
return null; // Использовать default транслятор
};
}
}
Когда используется DataAccessException
JdbcTemplate:
@Repository
public class UserJdbcRepository {
@Autowired
private JdbcTemplate jdbcTemplate;
public User findById(Long id) {
try {
return jdbcTemplate.queryForObject(
"SELECT * FROM users WHERE id = ?",
new UserRowMapper(),
id
);
} catch (EmptyResultDataAccessException ex) {
return null; // не найден
}
}
}
Spring Data JPA:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// JPA также использует DataAccessException
// RuntimeException от Hibernate оборачивается в DataAccessException
}
Глобальная обработка
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDataAccessException(
DataAccessException ex) {
ErrorResponse response = new ErrorResponse();
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
if (ex instanceof DataIntegrityViolationException) {
response.setMessage("Data integrity violation");
} else if (ex instanceof DataAccessResourceFailureException) {
response.setMessage("Database temporarily unavailable");
} else {
response.setMessage("Database error occurred");
}
log.error("DataAccessException", ex);
return ResponseEntity
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(response);
}
}
Best Practices
1. Не ловите общее DataAccessException без причины
// ❌ Плохо
try {
repository.save(entity);
} catch (DataAccessException ex) {
// слишком общая обработка
}
// ✅ Хорошо
try {
repository.save(entity);
} catch (DataIntegrityViolationException ex) {
// Специфичная обработка
}
2. Логируй ошибки
catch (DataAccessException ex) {
log.error("Database operation failed", ex); // with stacktrace
throw new ApplicationException("Operation failed", ex);
}
3. Используй @Transactional для управления транзакциями
@Transactional
public void complexOperation() {
// Spring автоматически откатит при DataAccessException
}
DataAccessException — это критичный механизм Spring для портируемости и надёжности database операций.