[부트캠프] TIL - Optional에 대하여 + @Transactional
Jpa를 사용한 개인 과제를 진행하며 리턴 타입을 Optional로 받으며 마주할 수 밖에 없는 부분에 대해서
학습하며 진행하고자 남깁니다.
public ScheduleResponseDTO getSchedule(Long id) {
// Schedule 검색 검색 이전에 id를 가진 Schedule이 존재하는지 여부 확인 작업 추가 가능 ** 단일 검색기능은 update에서도 공통으로 쓰이므로 함수로 따로 빼도될것.
// .get()의 경우 결과값이 null일 경우 NoSuchElementException 발생
Schedule schedule = scheduleRepository.findById(id).get();
// Entity -> ResponseDTO
return new ScheduleResponseDTO(schedule);
}
Id(Primary Key)값을 기반으로 Schedule 데이터 하나를 가져오는 ScheduleService class의 메소드입니다.
이 부분에서 JpaRepository를 상속받는 scheduleReposiotry의 메소드를 사용하여 값을 가져오는데
.get() 메소드를 사용하는 경우가 있습니다.
.get()메소드를 사용함으로써 결과 값이 존재하지 않는 경우에 대한 처리를 예측가능한 수준으로 처리할 수 있습니다.
.get()의 결과가 null인 경우 NoSuchElementException 발생.
항상 하는 어떤 DB의 검색, 수정, 삭제 작업 시에는 찾고자 하는 요소가 DB에 존재하는지의 유무를 사전에 검사하는
작업이 들어가게 됩니다, 그 부분에서 JpaRepository를 사용한 메소드의 리턴값에 .get()을 사용하면, 값이 존재하지
않는 경우에 대한 처리가 좁혀지므로 try~ catch를 사용하여 처리해줄 수 있겠습니다.
.orElseThrow()로 값이 없을 경우 예외를 발생
따로 지정한 예외클래스를 전달할 수도 있고, 직접 예외메시지를 입력할 수도 있습니다.
// throw Exception
return sampleRepository.findById(id)
.orElseThrow(IllegalArgumentException::new);
return sampleRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("no such data");
.orElse() 또는 .orElseGet()을 사용하여 값이 없는 경우 값을 지정
// null Return
return sampleRepository.findById(id)
.orElse(null);
// 비어있는 객체로 Return
return sampleRepository.findById(id)
.orElseGet(Sample::new);
@Transactional 중요내용
Test코드를 작성하다보면 @Transactional을 사용하는데 이 부분에 있어서 중요한 내용이 있다.
@Test
@Transactional
@Rollback(value = false)
public void createSchedule(){
Schedule schedule = new Schedule();
schedule.setUsername("sungrae");
schedule.setTitle("스케쥴제목");
schedule.setContents("스케쥴 내용");
//Schedule 반환.
//findBy~~~ 식으로 사용하는 경우 Optional타입을 반환한다고 하는 부분 찾아보기.
Schedule result = scheduleRepository.save(schedule);
System.out.println(result.getUsername());
System.out.println(result.getSchedule_id());
}
진행중인 과제의 테스트 코드의 일부입니다. 해당 부분에서는 우선 @Transactional이 없어도 상관없습니다.
이유는 Repository.save()에 @Transactional이 이미 포함되어있는 상태입니다.
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (this.entityInformation.isNew(entity)) {
this.entityManager.persist(entity);
return entity;
} else {
return this.entityManager.merge(entity);
}
}
SimpleJpaRepository의 save()메소드입니다, 해당 메소드에 자체적으로 @Transactional 어노테이션이 적용되어 있기 때문에 save()하나만 사용한 테스트코드의 경우 @Transactional 어노테이션이 붙을 필요는없습니다, 다만
다음과 같은 경우에는 반드시 붙어야만 합니다.
@Test
@Transactional
void test(){
Food food = new Food();
food.setName("test");
foodRepository.save(food);
Food food2 = new Food();
food.setPrice(1000);
foodRepository.save(food2);
}
//Food 클래스는 name 멤버변수에 @Column(nullable=false) 가 달렸습니다.
해당 코드의 경우 두번째 food2 를 save()하는 경우에 에러가 발생합니다, 이때 문제가 없던 앞부분의 save(food)는
쿼리가 정상적으로 날아갔지만, Rollback이 됩니다, 그 이유는 @Transactional로 테스트메소드가 묶여있으며
두번째의 save(food2)에서 에러가 발생하면서 전체가 Rollback이 되기때문입니다.
그렇다면 아래와 같은 상황을 비교해보겠습니다.
@Test
void test(){
Food food = new Food();
food.setName("test");
foodRepository.save(food);
Food food2 = new Food();
food.setPrice(1000);
foodRepository.save(food2);
}
//Food 클래스는 name 멤버변수에 @Column(nullable=false) 가 달렸습니다.
@Transactional이 빠진 상태입니다, 하지만 JpaRepository를 상속한 foodRepository.save()자체에 @Transactional이
걸려있기때문에 food.setName("test"); 뒤에 save(food)를 하고 바로 쿼리를 날려보냅니다.
테스트 메소드에 @Transactional이 없기때문입니다, 그러고 food2를 설정하고 save(food2)를 하며 에러가 발생합니다.
여기에서 두 상황의 차이는 두번째 테스트 메소드는 첫 번째의 save()메소드가 DB에 반영이되었다는겁니다.
따라서, @Transactional을 걸어주는 상황은 필요한 상황에 따라서 달라진다는 점이 중요합니다, 메소드 내부에
save() 메소드가 여러개가 존재해도 만약, 이중에 하나라도 문제가 발생할 경우 모든 실행을 취소해야 한다면
아무리 save()에 @Transactional이 자체적으로 걸려있다고 할지라도, 여러개의 save()를 포함한 메소드 자체에
@Transactional을 걸어줘야 하겠고, 여러 save()중 일부가 실패하더라도 상관없다면 메소드에 @Transactional을 걸지 않아도 되겠습니다.