프로젝트 그린더 Data Jpa 적용 2편
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의 추가 로직이 필요한점 , 명세를 정확하게 이해를 하고 내부 동작을 이해를 해야한다.