1.데이터 및 구조 설계 [서비스 레이어]

dasd412·2022년 1월 29일
0

포트폴리오

목록 보기
7/41

엔티티 save 서비스 만들기

코드


@Service
public class SaveDiaryService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final WriterRepository writerRepository;
    private final DiaryRepository diaryRepository;
    private final DietRepository dietRepository;
    private final FoodRepository foodRepository;

    public SaveDiaryService(WriterRepository writerRepository, DiaryRepository diaryRepository, DietRepository dietRepository, FoodRepository foodRepository) {
        this.writerRepository = writerRepository;
        this.diaryRepository = diaryRepository;
        this.dietRepository = dietRepository;
        this.foodRepository = foodRepository;
    }

    //작성자 id 생성 메서드
    public EntityId<Writer> getNextIdOfWriter() {
        Long count = writerRepository.findCountOfId();
        Long writerId;
        if (count == 0) {
            writerId = 0L;
        } else {
            writerId = writerRepository.findMaxOfId();
        }
        return new EntityId<>(writerId + 1);
    }

    //일지 id 생성 메서드 (트랜잭션 필수)
    public EntityId<DiabetesDiary> getNextIdOfDiary() {
        Long count = diaryRepository.findCountOfId();
        Long diaryId;
        if (count == 0) {
            diaryId = 0L;
        } else {
            diaryId = diaryRepository.findMaxOfId();
        }
        return new EntityId<>(diaryId + 1);
    }

    //식단 id 생성 메서드 (트랜잭션 필수)
    public EntityId<Diet> getNextIdOfDiet() {
        Long count = dietRepository.findCountOfId();
        Long dietId;
        if (count == 0) {
            dietId = 0L;
        } else {
            dietId = dietRepository.findMaxOfId();
        }
        return new EntityId<>(dietId + 1);
    }

    //음식 id 생성 메서드 (트랜잭션 필수)
    public EntityId<Food> getNextIdOfFood() {
        Long count = foodRepository.findCountOfId();
        Long foodId;
        if (count == 0) {
            foodId = 0L;
        } else {
            foodId = foodRepository.findMaxOfId();
        }
        return new EntityId<>(foodId + 1);
    }

    // getIdOfXXX()의 경우 트랜잭션 처리 안하면 다른 스레드가 껴들어 올 경우 id 값이 중복될 수 있어 기본키 조건을 위배할 수도 있다. 레이스 컨디션 반드시 예방해야 함.
    @Transactional
    public Writer saveWriter(String name, String email, Role role) {
        logger.info("saveWriter");
        Writer writer = new Writer(getNextIdOfWriter(), name, email, role);
        writerRepository.save(writer);
        return writer;
    }

    @Transactional
    public DiabetesDiary saveDiary(Writer writer, int fastingPlasmaGlucose, String remark, LocalDateTime writtenTime) {
        logger.info("saveDiary");
        DiabetesDiary diary = new DiabetesDiary(getNextIdOfDiary(), writer, fastingPlasmaGlucose, remark, writtenTime);
        writer.addDiary(diary);
        writerRepository.save(writer);
        return diary;
    }

    @Transactional
    public Diet saveDiet(Writer writer, DiabetesDiary diary, EatTime eatTime, int bloodSugar) {
        logger.info("saveDiet");
        Diet diet = new Diet(getNextIdOfDiet(), diary, eatTime, bloodSugar);
        diary.addDiet(diet);
        writer.addDiary(diary);
        writerRepository.save(writer);
        return diet;
    }

    @Transactional
    public Food saveFood(Writer writer, Diet diet, String foodName) {
        logger.info("saveFood");
        Food food = new Food(getNextIdOfFood(), diet, foodName);
        diet.addFood(food);
        writerRepository.save(writer);
        return food;
    }

}    

문제 발생 원인

엔티티 식별자를 "복합키"로 만들었고, 복합키 중 일부분을 "대리키" 로 "직접 할당"하는 방식으로 구현했다. 이 부분에서 주의할 점은 두 가지다.

1.대리키를 할당하는 메서드 (getIdOfxxx)호출 시 다른 엔티티의 id를 호출할 수 있는 불상사가 일어날 수 있다.
(리턴 타입이 단순 Long형인 경우에 발생할 수 있는 문제다.)

2.대리키 직접 할당할 때 다른 스레드가 끼어들어 레이스 컨디션이 발생할 수 있는 문제다.

이 경우 중복 대리키가 발생해서 기본키 조건에 위배된다.

해결책

  1. EntityId<>라는 대리키 지급용 클래스를 따로 만들었다.
    이렇게 하면 컴파일 타임에서 잘못된 메소드 호출을 막을 수 있다.
    (제네릭의 사용 상 이점이다.)

  2. getIdOfxxx()는 다른 스레드가 못 끼어들도록 반드시 하나의 트랜잭션 내에 존재하도록 해야 한다.


엔티티 find 서비스 만들기

코드

@Query(value = "FROM DiabetesDiary diary INNER JOIN diary.writer w WHERE diary.writer.writerId = :writer_id AND diary.diaryId = :diary_id")
    Optional<DiabetesDiary> findDiabetesDiaryOfWriter(@Param("writer_id") Long writerId, @Param("diary_id") Long diaryId);

문제 발생 원인

위 코드의 문제점은 역시 Long 파라미터이다. 헷갈릴 수 있기 때문에 컴파일 타임에 잡아낼 수 있도록 강제할 필요가 있다.

해결책

find용 Service에서 파라미터로 EntityId<>를 받고 이를 Long으로 변환해주는 작업을 한다.

    @Transactional(readOnly = true)
    public Optional<DiabetesDiary> getDiabetesDiaryOfWriter(EntityId<Writer>writerEntityId,EntityId<DiabetesDiary>diabetesDiaryEntityId){
        logger.info("getDiabetesDiaryOfWriter");
        return diaryRepository.findDiabetesDiaryOfWriter(writerEntityId.getId(),diabetesDiaryEntityId.getId());
    }
profile
시드 레벨 스타트업의 2호 직원으로서 백엔드 시스템의 70%를 설계 및 개발하였고, TIPS 5억 투자 유치에 기여한 서버 개발자입니다. (Go/Python/MSA/Spring)

0개의 댓글