BackEnd/Spring Boot

JPA/Hibernate Exception๋“ค๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

ddonghyeo 2024. 8. 4. 23:29

๋ฐฐ๊ฒฝ

 

ํ‰์†Œ์—๋Š” 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 ํ”„๋กœ๊ทธ๋ž˜๋ฐ