ํธ๋์ญ์ (Transaction)์ ์์ธ(Exception)์ ๋ฐ๋ฅธ ๋กค๋ฐฑ ์ฒ๋ฆฌ
์๋ก
์ด์ ์ Java์ Checked Exception๊ณผ UnChecked Exception์ ๋ํด ์ ๋ฆฌํ ์ ์ด ์์ต๋๋ค. ๋ค์ ์์ฝํ๋ฉด RuntimeException์ ์์ํ์ง ์๋ ํด๋์ค๋ Checked Exception, ์์ํ ํด๋์ค๋ Unchecked Exception์ด๋ฉฐ, Checked Exception์ try-catch์ ํตํด ์์ธ๋ฅผ ๊ผญ ์ฒ๋ฆฌํด ์ฃผ์ด์ผ ์ปดํ์ผ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.
์ด๋ฒ ํฌ์คํ ์์๋ ์ด๋ฌํ ๊ฐ๊ฐ์ ์์ธ๋ค์ ๋ํ์ฌ Transaction์์ ๋กค๋ฐฑ์ด ์ด๋ป๊ฒ ๋ฐ์๋๋์ง ํ๋ฒ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
๊ณตํต ์ฝ๋
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
@Getter
@NoArgsConstructor
public class UserRequestDto {
private String email;
private String password;
}
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@PostMapping("/user")
public void signUp(@RequestBody UserRequestDto userRequestDto) {
userService.signUp(userRequestDto);
}
}
public interface UserRepository extends JpaRepository<User, Long> {
}
๊ณตํต์ ์ธ Entity, Dto, Controller ๋ก์ง์ ์์ ๊ฐ์ต๋๋ค.
Checked Exception์์์ Transaction ์ฒ๋ฆฌ
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public void signUp(UserRequestDto userRequestDto) {
try {
User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
userRepository.save(user);
throw new IOException("Throw Force Exception");
} catch (Exception e) {
log.error("message: {}", e.getMessage());
}
}
}
๋จผ์ Chekced Exception์ ํธ๋์ญ์ ์์ ์ ๋๋ค. ์์ ๊ฒฝ์ฐ๋ ๋กค๋ฐฑ์ด ์ ์ฉ๋์ง ์์ต๋๋ค.
๋ํ ๋น์ฐํ๊ฒ๋ throw๊ฐ ๋ก์ง ์๋ก ๊ฐ๊ฒ ๋๋ฉด ์ปดํ์ผ ๋จ๊ณ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public void signUp(UserRequestDto userRequestDto) {
try {
User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
userRepository.save(user);
throw new RuntimeException("Throw Force Exception");
} catch (Exception e) {
log.error("message: {}", e.getMessage());
}
}
}
๊ทธ๋ผ ๋ค์๊ณผ ๊ฐ์ด ์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ง ์์๋ ๋๋ Unchecked Exception ํ์ ์ธ RuntimeException์ try-catch๋ฅผ ์ ์ฉํด ์์ธ๋ฅผ ์ฒ๋ฆฌ๋ฅผ ํ๋ฉด์ ๋์ก์ ๋๋ ์ด๋จ๊น์?
๊ฒฐ๊ณผ๋ ๋กค๋ฐฑ์ด ์ ์ฉ๋์ง ์์ต๋๋ค. RuntimeException์ ์์ธ๋ก ๋์ก์ง๋ง ์ค์ง์ ์ผ๋ก try-catch๋ฅผ ํตํด ์์ธ๋ฅผ ์ฒ๋ฆฌ๋ฅผ ํ์ธํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ๋ ์์ธํ ์์๋ณด๋ฉด @Transaction์ ํตํ ์ ์ธ์ ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฐฉ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋ก์ ๋ฐฉ์์ AOP๊ฐ ์ ์ฉ๋๊ธฐ ๋๋ฌธ์ ๋๋ค.
TransactionStatus status = transactionManager.getTransaction(..);
try {
target.logic();
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw new IllegalStateException(e);
}
์ ์ฝ๋๋ ํธ๋์ญ์ ํ๋ก์์ ๊ฐ๋จํ ์์ ์ฝ๋์ ๋๋ค. ํธ๋์ญ์ ํ๋ก์๊ฐ ํธ๋์ญ์ ์ฒ๋ฆฌ ๋ก์ง์ ์ํํ ๋ ์ ํด๋์ค๋ฅผ ์๋ก ๋ง๋ค์ด ๋ก์ง์ ์ํํ๊ฒ ๋ฉ๋๋ค. ๋ฐ๋ผ์ try-catch๋ฅผ ํตํด catch์์ ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ์ธํ๊ธฐ ๋๋ฌธ์ ํ๋ก์๊น์ง ์์ธ๊ฐ ์ ๋ฌ๋์ง ์์ ํ๋ก์ ๊ฐ์ฒด๊ฐ RuntimeException์ ์ธ์งํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
UnChecked Exception์์์ Transaction ์ฒ๋ฆฌ
@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
@Transactional
public void signUp(UserRequestDto userRequestDto) {
User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
userRepository.save(user);
throw new RuntimeException("Throw Force Exception");
}
}
๋ค์๊ณผ ๊ฐ์ด RuntimeException์ try-catch๋ฅผ ํตํด ์์ธ ์ฒ๋ฆฌ ํ์ง ์์์ ์์๋ ๋กค๋ฐฑ์ด ์ ์ฉ๋๋ฉฐ ๋ณ๊ฒฝ์ฌํญ์ด ์ ์ฅ๋์ง ์์ต๋๋ค.
์ Checked Exception์ ๋กค๋ฐฑ๋์ง ์์๊น?
๊ทธ๋ ๋ค๋ฉด ์ Checked Exception์ ๋กค๋ฐฑ์ด ๋์ง ์์๊น์?
์ด์ ๋ Spring์ @Transaction ์ด๋ ธํ ์ด์ ์ ๋ํด Runtime Exception๊ณผ Error์ ๋ํด์๋ง default๋ก ๋กค๋ฐฑ์ด ์ ์ฉ๋๋๋ก ์ค์ ๋์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด์ ๋ํด ์ค๋ช ํ๋ฉด ์๋์ ์ฝ๋๋ ์๋ก ๋์ผํฉ๋๋ค.
@Transactional
@Transactional(rollbackFor = {RuntimeException.class, Error.class})
์ฆ, ๊ธฐ๋ณธ์ ์ผ๋ก UnChecked Exception(RuntimeException)์์๋ง ๋กค๋ฐฑ์ด ๋๋ฉฐ, Checked Exception(IOException, SQLException ๋ฑ)์์๋ ๋กค๋ฐฑ์ด ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ AOP์ ์ค์ ์ ํ๋ spring์ Unchecked Exception(RuntimeException)๊ณผ Error ๋ฐ์ ์ ๋กค๋ฐฑ ์ ์ฉ์ ๊ธฐ๋ณธ์ผ๋ก ํ๋๋ก ์ค์ ๋ฉ๋๋ค.
[๋กค๋ฐฑ ์ ์ฉ]
@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
[๋กค๋ฐฑ ์ ์ธ]
@Transactional(noRollbackFor = {RuntimeException.class})
์ปค์คํ ํ๊ธฐ ์ํด์๋ ์์ ๊ฐ์ด ์ง์ ๋กค๋ฐฑ์ ์ ์ฉํ๊ฑฐ๋ ์ ์ธํ ์์ธ๋ฅผ ์ง์ ๋ช ์ํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์ ์ค์ํ ์ ์ ์์์ ์ ๊น ์ค๋ช ํ์ง๋ง try-catch๋ฅผ ํตํด ์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ ๊ฒฝ์ฐ์๋ rollbackFor์ ์ค์ ํ๋๋ผ๋ ์ ์ฉ์ด ๋์ง ์๊ธฐ ๋๋ฌธ์ catch๋ด์์ ๊ฐ์ ๋ก throw๋ฅผ ํด์ฃผ์ด์ผ ๋กค๋ฐฑ์ด ์ ์ฉ๋ฉ๋๋ค.
์ ๋ฆฌ
๊ฐ๋ฐ์๊ฐ ์ปดํ์ผ ์ ์์ธก/์ฒ๋ฆฌ ๊ฐ๋ฅํ ์์ธ์ ์ฒ๋ฆฌ๋ฅผ ๊ฐ๋ฐ์์๊ฒ ๊ฐ์ ํ๊ธฐ ์ํด Checked Exception์ ์คํ๋ง์์ ์ ๊ณตํ๋ ํธ๋์ญ์ ์์์ ๋กค๋ฐฑ์ ๊ธฐ๋ณธ ์ ์ฑ ์ ํฌํจ๋์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์ด๋ฅผ ์ปค์คํ ํ๊ธฐ ์ํด์๋ Transaction ์ด๋ ธํ ์ด์ ์์ rollbackFor ํน์ noRollbackFor์ ํตํด ์ง์ ์ธํ ํด ์ฃผ์ด์ผ ํฉ๋๋ค.
- try-catch๋ฅผ ํตํด ์์ธ๋ฅผ ํ์ธํ๋ฉด ๋กค๋ฐฑ ์ ์ฉ X
- Unchecked Exception์ default ์ธํ ์ผ๋ก ๋กค๋ฐฑ ์ ์ฉ O
- Unchekced Exception๋ try-catch๋ฅผ ํตํด ์์ธ ํ์ธ ์ ๋กค๋ฐฑ ์ ์ฉ X
- @Transaction์์ rollbackFor, noRollbackFor์ ํตํด ์ปค์คํ ๊ฐ๋ฅ
์์
์ต๊ทผ์ ๋ ์์ธํ๊ฒ ๋ค๋ฃฌ 'Spring ํธ๋์ญ์ ์ ์ธ์ ์ด๋ป๊ฒ ๋กค๋ฐฑ ๋ ๊น?' ๊ธ์ ์๋ก ๋ฐํํ์ผ๋ ๋ ์์ธํ๊ฒ ์๊ณ ์ถ์ผ์ ๋ถ๋ค์ ํด๋น ์ํฐํด์ ์์ฃผ๋ก ๋ด์ฃผ์๋ฉด ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.