Project/Greendar

프로젝트 그린더 Data Jpa 적용 2편

sung.hyun.1204 2023. 4. 18. 22:08

1편에서 이어서 작성을 한다.

 

https://chosunghyun18.tistory.com/98

 

프로젝트 그린더 Data Jpa 적용 1편

이 글은 프로젝트의 일정은 끝났지만 개인의 학습과 탐구심의 목표를 채우기 위하여 이것 저것을 한 글 입니다. 관련 깃허브 : https://github.com/Team-Greendar/GreendarServer GitHub - Team-Greendar/GreendarServer: G

chosunghyun18.tistory.com

 

... +

https://github.com/Team-Greendar/GreendarServer/tree/develop/greendar

 

GitHub - Team-Greendar/GreendarServer: Greendar Server Repository

Greendar Server Repository. Contribute to Team-Greendar/GreendarServer development by creating an account on GitHub.

github.com

변경하고자 하는 기존의 Repo 코드이다.   전문은 상단 깃허브의 히스토리를 보면 된다.

....
@Repository
@RequiredArgsConstructor
public class EventTodoRepository {
    @PersistenceContext
    private final EntityManager em;

    public EventTodo save(Boolean complete, String imageUrl, EventTodoItem eventTodoItem, Member member) {
        EventTodo eventTodo= EventTodo.of(new TodoImage(imageUrl),complete,eventTodoItem,member);
        em.persist(eventTodo);
        return eventTodo;
    }

    public EventTodo updateEventTodoComplete(Long eventTodoId, Boolean complete) {
        EventTodo eventTodo = em.find(EventTodo.class, eventTodoId);
        eventTodo.updateComplete(complete);
        em.merge(eventTodo);
        return eventTodo;
    }

    public EventTodo updateEventTodoImageUrl(Long eventTodoId, String imageUrl) {
        EventTodo eventTodo = em.find(EventTodo.class, eventTodoId);
        eventTodo.updateImage(imageUrl);
        em.merge(eventTodo);
        return eventTodo;
    }

    .....
}

 

원하는 결과는 기존의 레포단에서 서비스 단에 제공하는  기능을 유지하고 최소한의 코드를 적는 법,

JPA Data 를 사용하는 것이다.

 

1. 인터페이스를 만들어주자.  기존 Repo class 에 인터페이스를 선언하고 접근자 와 body 를 전부 제거 해준다.

public interface EventTodoRepository {

    EventTodo save(Boolean complete, String imageUrl, EventTodoItem eventTodoItem, Member member);

    EventTodo updateEventTodoComplete(Long eventTodoId, Boolean complete);

    EventTodo updateEventTodoImageUrl(Long eventTodoId, String imageUrl) ;

    EventTodo findOneByEventTodoItemIdMemberId(Long memberId, Long eventTodoItemId);

    List<EventTodo> findAllEventTodoByMember(Long memberId);

    List<EventTodoResponseDto> findAllByDay(LocalDate day, Member member);
    List<EventTodoResponseDto> findAllByMonth(LocalDate date, Member member);

}

2. 구현 부분을 작성 : 기존의 코드에서 복 붙이다. 구현 부분이 필요 없을 수 도 있지만 혹시나 잘못된경우를 생각하여 만들었다 .

(data JPA 를 적용하고 확인하면 삭제를 해주자)

..
@Repository
@RequiredArgsConstructor
public class EventTodoJpaRepository implements EventTodoRepository {
    @PersistenceContext
    private final EntityManager em;

    public EventTodo save(Boolean complete, String imageUrl, EventTodoItem eventTodoItem, Member member) {
        EventTodo eventTodo= EventTodo.of(new TodoImage(imageUrl),complete,eventTodoItem,member);
        em.persist(eventTodo);
        return eventTodo;
    }

    public EventTodo updateEventTodoComplete(Long eventTodoId, Boolean complete) {
        EventTodo eventTodo = em.find(EventTodo.class, eventTodoId);
        eventTodo.updateComplete(complete);
        em.merge(eventTodo);
        return eventTodo;
    }

..
    public EventTodo findOneByEventTodoItemIdMemberId(Long memberId, Long eventTodoItemId) {
        try {
            return em.createQuery("select e from EventTodo  e " +
                                    "where e.member.id =:memberId and e.eventTodoItem.id =:eventTodoItemId"
                            , EventTodo.class)
                    .setParameter("memberId", memberId)
                    .setParameter("eventTodoItemId", eventTodoItemId)
                    .getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }
..
}

3. 메서드 명을 dataJPA 에 맞게 변경 및 testcode 작성을 해주자

 

Spring Data Jpa 를 DB Update 를 하고자 하면 Udate 쿼리를 사용하거나 jpa 의 변경 감지를 이용하는 방식으로 업데이트를 한다.

 

model 에 변경에 관한 코드를 추가해준후 service layer 에서 호출을 해준다.

 

 

4 . data JPA 적용 + 복잡한 쿼리 옮겨주기 

 

 

기존 코드 : Service Layer , 사용자가 체크박스를 체크를 했을때 , 없으면 생성을 하고 있으면 업데이트를 하는 기능이다. 

@Transactional
    public EventTodo updateEventTodo(Boolean complete , String imageUrl, Long eventTodoItemId,String token) {
        Member member = memberRepository.fineOneByToken(token);
        EventTodoItem eventTodoItem = eventTodoItemRepository.findOneById(eventTodoItemId);
        EventTodo eventTodo = eventTodoRepository.findOneByEventTodoItemIdMemberId(member.getId(),eventTodoItem.getId());

        // 생성
        if(eventTodo==null) {
            if(complete == null)
            {
                return eventTodoRepository.save(false,imageUrl,eventTodoItem,member);
            }
            else {
                return eventTodoRepository.save(true,"EMPTY",eventTodoItem,member);
            }
        }else{
            if(complete == null)
            {
                return eventTodoRepository.updateEventTodoImageUrl(eventTodo.getId(),imageUrl);
            }
            else {
                return eventTodoRepository.updateEventTodoComplete(eventTodo.getId(),complete);
            }
        }

    }

 

 

1차 변경 후 코드이다.

 

Service Layer :

@Transactional
    public EventTodo updateEventTodo(Boolean complete , String imageUrl, Long eventTodoItemId,String token) {
        Member member = memberRepository.fineOneByToken(token);
        EventTodoItem eventTodoItem = eventTodoItemRepository.findOneById(eventTodoItemId);
        EventTodo eventTodo = eventTodoRepository.findByEventTodoItemIdAndMemberId(eventTodoItem.getId(),member.getId())
                .orElseThrow(() -> new EntityNotFoundException("EventTodo not found w" ));

        // 생성
        if(eventTodo==null) {

            if(complete == null)
            {   EventTodo newEventTodo = EventTodo.of(new TodoImage(imageUrl),false,eventTodoItem,member);
                return eventTodoRepository.save(newEventTodo);
            }
            else {
                EventTodo newEventTodo = EventTodo.of(new TodoImage("EMPTY"),true,eventTodoItem,member);
                return eventTodoRepository.save(newEventTodo);
            }
        }else{
            if(complete == null)
            {
                return eventTodo.updateImage(imageUrl);
            }
            else {
                return eventTodo.updateComplete(complete);
            }
        }

    }

 

위의 변경 후의 Service 코드는 2 % 부족하다.

 

Optional 을 사용하기위하여 Data jpa 를 적용했으니 다음과 같이 변경하자.

@Transactional
public EventTodo updateEventTodo(Boolean complete, String imageUrl, Long eventTodoItemId, String token) {
    Member member = memberRepository.findOneByToken(token);
    EventTodoItem eventTodoItem = eventTodoItemRepository.findOneById(eventTodoItemId);

    EventTodo eventTodo = eventTodoRepository.findByEventTodoItemIdAndMemberId(eventTodoItem.getId(), member.getId())
            .orElse(EventTodo.of(new TodoImage(imageUrl), complete != null && complete, eventTodoItem, member));

    if (complete != null) {
        eventTodo.updateComplete(complete);
    } else {
        eventTodo.updateImage(imageUrl);
    }

    return eventTodoRepository.save(eventTodo);
}

 

 

 

변경 후의 Repo code 는 파라미터를 받아서 바로 쿼리에 넣는것이기 때문에, 파라미터가 많아졌다.

@Repository
public interface EventTodoRepository extends JpaRepository<EventTodo,Long> {

    Optional<EventTodo> findByEventTodoItemIdAndMemberId(Long eventTodoItemId, Long memberId);

    @Query("select  new greendar.domain.eventtodo.dto.EventTodoResponseDto(e) "+
            "from EventTodo e " +
            "where e.eventTodoItem.date = :oneDay and e.member.id = :memberId " +
            "order by e.eventTodoItem.date desc")
    List<EventTodoResponseDto> findAllByDay(@Param("oneDay")LocalDate day, @Param("memberId") Long memberId);

    @Query("select new greendar.domain.eventtodo.dto.EventTodoResponseDto(e) " +
            "from EventTodo e " +
            "where e.member.id=:memberId and e.eventTodoItem.date between :startDate and :endDate " +
            "order by  e.eventTodoItem.date desc")
    List<EventTodoResponseDto> findAllByMonth(@Param("startDate")LocalDate startDate,
                                              @Param("endDate")LocalDate endDate,
                                              @Param("memberId") Long memberId);

}

 

변경전 : 

 

@Repository
@RequiredArgsConstructor
public class EventTodoRepository {
    @PersistenceContext
    private final EntityManager em;

    public EventTodo save(Boolean complete, String imageUrl, EventTodoItem eventTodoItem, Member member) {
        EventTodo eventTodo= EventTodo.of(new TodoImage(imageUrl),complete,eventTodoItem,member);
        em.persist(eventTodo);
        return eventTodo;
    }

    public EventTodo updateEventTodoComplete(Long eventTodoId, Boolean complete) {
        EventTodo eventTodo = em.find(EventTodo.class, eventTodoId);
        eventTodo.updateComplete(complete);
        em.merge(eventTodo);
        return eventTodo;
    }

    public EventTodo updateEventTodoImageUrl(Long eventTodoId, String imageUrl) {
        EventTodo eventTodo = em.find(EventTodo.class, eventTodoId);
        eventTodo.updateImage(imageUrl);
        em.merge(eventTodo);
        return eventTodo;
    }

    public EventTodo findOneByEventTodoItemIdMemberId(Long memberId, Long eventTodoItemId) {
        try {
            return em.createQuery("select e from EventTodo  e " +
                                    "where e.member.id =:memberId and e.eventTodoItem.id =:eventTodoItemId"
                            , EventTodo.class)
                    .setParameter("memberId", memberId)
                    .setParameter("eventTodoItemId", eventTodoItemId)
                    .getSingleResult();
        } catch (NoResultException nre) {
            return null;
        }
    }

    public List<EventTodo> findAllEventTodoByMember(Long memberId) {
        return em.createQuery("select e from EventTodo  e " +
                                " where e.member.id = :memberId " +
                                "order by e.eventTodoItem.date desc"
                        , EventTodo.class)
                .setParameter("memberId", memberId)
                .getResultList();
    }

    public List<EventTodoResponseDto> findAllByDay(LocalDate day, Member member){
        return em.createQuery("select  new greendar.domain.eventtodo.dto.EventTodoResponseDto(e) "+
                    "from EventTodo e " +
                    "where e.eventTodoItem.date = :oneDay and e.member.id = :memberId " +
                    "order by e.eventTodoItem.date desc"
                    , EventTodoResponseDto.class)
                    .setParameter("oneDay",day)
                    .setParameter("memberId",member.getId())
                    .getResultList();
    }


    public List<EventTodoResponseDto> findAllByMonth(LocalDate date, Member member) {
            YearMonth month = YearMonth.from(date);
            LocalDate start = month.atDay(1);
            LocalDate end = month.atEndOfMonth();
            return em.createQuery("select new greendar.domain.eventtodo.dto.EventTodoResponseDto(e) " +
                        "from EventTodo e " +
                        "where e.member.id=:memberId and e.eventTodoItem.date between :startDate and :endDate " +
                        "order by  e.eventTodoItem.date desc",EventTodoResponseDto.class)
                        .setParameter("startDate",start)
                        .setParameter("endDate",end)
                        .setParameter("memberId",member.getId())
                        .getResultList();
    }

}

 

 

 

 

SpringBootData JPA 를 이용하면 확실하게 코드 량이 줄어들고 도메인 model의 추가 로직이 필요한점 , 명세를 정확하게 이해를 하고 내부 동작을 이해를 해야한다.