아마도 SQLException 이런 걸 직접 만든다는 뜻 같다.
DB오류에 따라서 사실 복구 가능한 경우는 많이 있지는 않지만,
복구 하고 싶은 경우가 있다.
예를 들면 계정을 새로 생성하는데 DB에 이미 같은 id가 있으면 이 id뒤에 뭔가를 덧붙여 만드는..
그 외 우리가 id확인이나 닉네임 확인 할 때 같은 거 있으면 뒤에 뭐 붙여서 추천해주는 그런 경우 있는데, 그런건가봄.
날리는 오류코드도, 같은 SQLException이지만 오류의 종류에 따라 오류 코드 같은 것을 보내준다.
이것도 DB에 따라 다르긴 한데, H2같은 경우는 Primary Key를 사용한 것에 같은 걸 생성하려 든다면 23505이다. 42000은 sql 문법 오류.
이건 e.getErrorCode() 해보면 읽을 수 있다.
그럼 뭐 예를 들어 e.getErrorCode() == 23505 이런 식이면
이미 존재하는 아이디입니다!
추천 아이디 :
Member.memberId + '12345'
이런 식으로 응답하게끔 하거나 뭐 그런..
@Slf4j
public class ExTranslatorV1Test {
Repository repository;
Service service;
@BeforeEach
public void init(){
DriverManagerDataSource dataSource = new DriverManagerDataSource(URL, USERNAME, PASSWORD);
repository = new Repository(dataSource);
service = new Service(repository);
}
@Test
void duplicateKeySave(){
service.create("myId");
service.create("myId");
}
@Slf4j
@RequiredArgsConstructor
static class Service{
private final Repository repository;
public void create(String memberId){
try{
repository.save(new Member(memberId, 0));
log.info("saveId={}",memberId);
}catch (MyDuplicateKeyException e){
log.info("키 중복, 복구 시도");
String retryId = generateNewId(memberId);
log.info("retryId = {}", retryId);
repository.save(new Member(retryId, 0));
}catch(MyDbException e){
log.info("데이터 접근 계층 예외",e);
throw e;
}
}
private String generateNewId(String memberId){
return memberId + UUID.randomUUID().toString().substring(0,5);
}
}
@RequiredArgsConstructor
static class Repository{
private final DataSource dataSource;
public Member save(Member member){
String sql = "insert into member(member_id, money) values(?,?)";
Connection con = null;
PreparedStatement pstmt = null;
try{
con = dataSource.getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
}catch (SQLException e){
if(e.getErrorCode() == 23505){
throw new MyDuplicateKeyException(e);
}
throw new MyDbException(e);
}finally {
JdbcUtils.closeStatement(pstmt);
JdbcUtils.closeConnection(con);
}
}
}
}
그런 식으로 만들어 봄.
먼저 리포지토리,
Member를 받아서 저장을 시도 하는데,
만약 에러가 뜨고, 마침 그 에러의 에러코드가 23505(H2의 Primary Key 중복)이면 MyDuplicateKeyException 이라는 에러로 던짐. 그 외에는 그냥 MyDbException으로 던짐. 그러니까 MyDuplicateKeyException은 내가 이건 특별히 처리할 것이다 라는 것을 구분하기 위해 저 23505가 에러코드면 MyDuplicateKeyException로 에러를 던진다.
그래서 @Test에 보면, 저렇게 같은 이름으로 생성시도를 두번하는데, 당연히 member_id부분은 DB의 테이블을 생성했을 때 primary key로 해놔서 에러를 내놓을 것이고, H2의 Primary Key의 중복 에러의 코드는 23505라고 했기에, 그 코드를 읽고 MyDuplicateKeyException을 던질 것이다.
그럼 그것이 service로 올라갈 것이고, 거기서 잡은 예외가 MyDuplicateKeyException이면 memberId에 UUID의 일부분을 덧붙여 다시 DB에 save를 시도해본다.
그 외에 MyDbException도 한번 잡아보고, 저렇게 catch 여러 개 할 수 있다.
물론 MyDbException 저렇게 굳이 잡아서 재 던지지 않아도 런타임에러라 굳이 명시 안해줘도 알아서 위로 간다.
MyDuplicateKeyException은 우리가 RuntimeException을 상속받아 만든 에러이기 때문에 서비스는 여전히 순수성을 유지할 수 있다.
그래서 뭐 당연한 말이지만, 예외변환은 리포지토리에서 일어나야 함.
아직 남은 문제는, 저런 primary key 중복 오류코드 같은 거 DB마다 다르다.
'스프링 > 5. 스프링 DB-1' 카테고리의 다른 글
41. 예외 추상화 적용. (0) | 2023.10.06 |
---|---|
40. 스프링 예외 추상화. (0) | 2023.10.05 |
38. 런타임 예외로 문제 해결 (0) | 2023.10.04 |
37. 스프링으로 예외처리, 반복 해결 체크예외 (0) | 2023.10.04 |
35. 언체크 예외 활용. (0) | 2023.10.04 |