๋ฐฐ๊ฒฝ
ํ์์๋ JPA ๊ด๋ จ ๋ฉ์๋๋ค์ ์ธ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ฒ๋ฆฌํ์๋ค.
User user = userRepository.findById(id).orElseThrow(...);
orElseThrow() ๋ Optional ํด๋์ค์ ๋ฉ์๋๋ก, ๋จ์ํ๊ฒ ๊ฐ์ด null์ด๋ผ๋ฉด Exception์ ๋์ ธ์ฃผ๊ณ , null์ด ์๋๋ผ๋ฉด ๋ฐํํ๋ ํจ์์ด๋ค.
์ด๋ ๋จ์ํ ๊ฒฐ๊ณผ๊ฐ null์์ ์ฒ๋ฆฌํ ์ ๋ฐ์ ์์๋ค.
์ง๊ธ๊น์ง JPA๋ฉ์๋์์ Exception์ด ๋์ ธ์ง๋ ์ผ์ ์์์ง๋ง,
JPA ๋๋ Hibernate์์ ๋์ ธ์ง๋ Exception ๋ํ ์ฒ๋ฆฌํ ์ ์์ด์ผ ํ๊ฒ ๋ค๊ณ ์์ฌํด ๋ณด์๋ค.
Repository Exception
์์ฃผ ์ฌ์ฉํ๋ findById์์๋ ๋ง์ฝ id๊ฐ null์ผ ๊ฒฝ์ฐ IllegalArgumentException์ ๋์ง๋ค.
๋ณดํต์ ์ํฐํฐ์ Id์ ์์ฑ ์ ๋ต์ ๋ช ์ํด ๋๊ธฐ ๋๋ฌธ์ ๋์ ธ์ง ์ผ์ ๋ง์ด ์์ ๊ฒ์ด๋ค.
save()๋ฉ์๋์์๋ ์ํฐํฐ๊ฐ null์ด๋ผ๋ฉด IllegalArgumentException์ ๋์ง๊ณ ,
OptimisticLockingFailureException (๋๊ด์ ๋กํน ์์ธ) ๋ ๋์ง๋ค.
DataAccessException
Spring์ JPA์ ์์ธ๋ฅผ ์์กดํ์ฌ ์ง์ ์ฌ์ฉํ์ง ์๊ฒ ํ ์ ์๋๋ก
๋๋ถ๋ถ์ JPA ์์ธ๋ฅผ DataAccessException์ผ๋ก ์๋ ๋ณํํ๋ค.
PersistenceExceptionTranslationPostProcessor๊ฐ
@Repository๊ฐ ์ ์ฉ๋ ํด๋์ค์ ์์ธ ๋ณํ AOP๋ฅผ ์ ์ฉํ์ฌ ๋ณํํด์ค๋ค.
์ด๋ฅผ ํตํด ํน์ ORM์ ์ข ์๋์ง ์๋ ์ผ๊ด๋ ์์ธ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํ๋ค.
@Transactional(rollbackFor = {DataAccessException.class})
public void test() {
// ๋ฐ์ดํฐ ์ ๊ทผ ๋ก์ง
}
ํธ๋์ญ์ ๋กค๋ฐฑ ์ต์ ์ ํตํด ์์ ํ ํธ๋์ญ์ ์ ๊ตฌ์ฑํ ์ ์๊ฒ ๋ค.
์ด์ Hibernate์์ ์ฌ์ฉํ๋ ์ฃผ์ ์์ธ๋ค๊ณผ ๊ทธ ์์๋ฅผ ์ดํด๋ณด๊ฒ ๋ค.
1. EntityNotFoundException
์ด๋ ์ํฐํฐ๋ฅผ ์ฐพ์ง ๋ชปํ ๊ฒฝ์ฐ์ด๋ค.
EntityManager์ getReference(), refresh(), lock() ์์ ๋์ ธ์ง ์ ์๋ ์์ธ์ด๋ค.
์คํ๋ง์ JpaObjectRetrievalFailureException๋ก ๋ณํํด์ค๋ค.
try {
User user = entityManager.getReference(User.class, 1L);
user.setName("New Name"); // ์์ธ ๋ฐ์ ๊ฐ๋ฅ ์์
} catch (EntityNotFoundException e) {
log.error("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: ", e);
// ์๋ฌ ์ฒ๋ฆฌ
throw new CustomNotFoundException("์์ฒญํ ์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.", e);
}
2. OptimistickLockException (๋๊ด์ ๋กํน ์์ธ)
๋๊ด์ ๋กํน์ ์ฌ์ฉํ์ ๋, ๋์์ ์์ ์ ์๋ํ๋ ๊ฒฝ์ฐ, ๋ฒ์ ์ถฉ๋์ด ๋ฐ์ํ์ ๋ ์ผ์ด๋ ์ ์๋ ์๋ฌ์ด๋ค.
์คํ๋ง์ JpaOptimisticLockingFailureException์ผ๋ก ๋ณํํด์ค๋ค.
@Transactional
public void updateUser(Long userId, String newName) {
try {
User user = userRepository.findById(userId).orElseThrow(...);
user.setName(newName); // ์์ ์๋
userRepository.save(user);
} catch (OptimisticLockException e) {
log.warn("๋์ ์์ ์ผ๋ก ์ธํ ์ถฉ๋ ๋ฐ์: ", e);
// ์๋ฌ ์ฒ๋ฆฌ
throw new CustomConcurrencyException("๋ค๋ฅธ ์ฌ์ฉ์๊ฐ ๋์์ ์์ ํ์ต๋๋ค. ๋ค์ ์๋ํด์ฃผ์ธ์.", e);
}
}
3. PessimisticLockException (๋น๊ด์ ๋กํน ์์ธ)
๋น๊ด์ ๋กํน์ ํ๋ํ์ง ๋ชปํ์ ๋ ๋ฐ์ํ๋ค.
์คํ๋ง์ PessimisticLockingFailureException์ผ๋ก ๋ณํํด์ค๋ค.
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
try {
//์ถ๊ธ ๊ณ์ข
Account fromAccount = accountRepository.findByIdWithLock(fromAccountId).orElseThrow(...);
//์
๊ธ ๊ณ์ข
Account toAccount = accountRepository.findByIdWithLock(toAccountId).orElseThrow();
fromAccount.withdraw(amount);
toAccount.deposit(amount);
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
} catch (PessimisticLockException e) {
log.error("๊ณ์ข ์ ๊ธ ์คํจ: ", e);
throw new CustomLockException("๊ณ์ข์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", e);
}
}
4. ConstraintViolationException
DB์ ์ ์ฝ์กฐ๊ฑด์ ์๋ฐํ์ ๋ ๋ฐ์ํ๋ค.
์คํ๋ง์ DataIntegrityViolationException์ผ๋ก ๋ณํํด์ค๋ค.
@Transactional
public void createUser(User user) {
try {
userRepository.save(user);
} catch (ConstraintViolationException e) {
log.error("์ ์ฝ์กฐ๊ฑด ์๋ฐ: ", e);
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
List<String> errorMessages = violations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
throw new CustomValidationException("์
๋ ฅ ๋ฐ์ดํฐ๊ฐ ์ ํจํ์ง ์์ต๋๋ค.", errorMessages, e);
}
}
5. QueryTimeoutException
์ฟผ๋ฆฌ ์คํ ์๊ฐ์ด Time Out ๋์์ ๋ ๋ฐ์ํ๋ค.
์คํ๋ง์ org.springframework.dao.QueryTimeoutException์ผ๋ก ๋ณํํด์ค๋ค.
@Transactional(readOnly = true)
public List<User> searchUsers(String keyword) {
try {
return userRepository.searchByKeyword(keyword);
} catch (QueryTimeoutException e) {
log.error("์ฌ์ฉ์ ๊ฒ์ ์ฟผ๋ฆฌ ํ์์์: ", e);
throw new CustomTimeoutException("์ฟผ๋ฆฌ ์๊ฐ์ด ์ด๊ณผ๋์์ต๋๋ค.", e);
}
}
์ง๊ธ๊น์ง ์ฌ๋ฌ JPA์ ์์ธ๋ค์ ๋ณด์๋ค.
ํ์ง๋ง, DataAcessException์๋ ๋ค์๊ณผ ๊ฐ์ ๋ด์ฉ์ด ์๋ค.
"์ด ํด๋์ค๋ ๋ฐํ์ ์์ธ์ด๊ธฐ ๋๋ฌธ์, ๋ชจ๋ ์ค๋ฅ๊ฐ ์น๋ช
์ ์ธ ๊ฒ์ผ๋ก ๊ฐ์ฃผ๋ ๊ฒฝ์ฐ(์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ)์๋ ์ฌ์ฉ์ ์ฝ๋๊ฐ ์ด ์์ธ๋ ๊ทธ ํ์ ํด๋์ค๋ฅผ ์ก์ ํ์๊ฐ ์์ต๋๋ค."
๊ตณ์ด ์ด ์์ธ๋ฅผ catchํ ํ์๋ ์๋ค๊ณ ํ๋๋ฐ.. ๐ค
๋ด๊ฐ ์ ํํ๊ฒ ํด์ํ๊ฑด์ง๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง..
์น๋ช ์ ์ค๋ฅ์ผ ๊ฒฝ์ฐ์๋ catch ํ๋ ๊ฒ ๋ณด๋ค ์ฑ์ ์ค๋จ์ํค๋๊ฒ ๋ ์ข์ ์๋ ์๋ค ..??
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DataAccessException.class)
public ResponseEntity<ErrorResponse> handleDataAccessException(DataAccessException ex) {
log.error("๋ฐ์ดํฐ ์ ๊ทผ ์์ธ ๋ฐ์", ex);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("์๋ฒ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."));
}
}
์์ ๊ฐ์ด Global๋ก Exception์ ์ก์์ ์๋ต์ ์ฃผ๋ ๊ฒ์ด ๊ฐ์ฅ ์ข์ ์์์ธ ๊ฒ ๊ฐ๋ค.
์ฐธ๊ณ
์๋ฐ ORM ํ์ค JPA ํ๋ก๊ทธ๋๋ฐ