Weekly TIL - Day 32

2025. 5. 16. 11:07·Weekly TIL

⭐️ Today's summary

오늘은 엔티티간 관계맵핑을 끝내고  Board 엔티티를 가지고 CRUD를 해보았다.

 

오늘은 C, R을 다루고 내일은 U,D 내일 모레는 Notice 엔티티를 가지고 CRUD를 해보겠다.

 

API 요청에 대한 테스트는 POSTMAN툴을 사용하여 진행하겠다.

 

⭐️ Problem

어떤 부분을 DTO로 만들어서 보내야하는지와, 영속성 등록과 C,R을 하는 부분을 처음 다루어

보기때문에, 이부분에 대해서 아래에 정리해 보겠다.

 

⭐️ Try

[ MemberController.java ]

package com.kh.jpa.controller;

import com.kh.jpa.dto.MemberDto;
import com.kh.jpa.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/members")
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    // 회원등록 API
    @PostMapping
    public ResponseEntity<String> addMember(@RequestBody MemberDto.Create createDto) {
        String userId = memberService.createMember(createDto);

        //return new ResponseEntity<>(userId, HttpStatus.OK);
        return ResponseEntity.ok(userId);
    }

    // 회원조회
    @GetMapping("/{userId}")
    public ResponseEntity<MemberDto.Response> getMember(@PathVariable String userId) {
        return ResponseEntity.ok(memberService.findMember(userId));
    }

}

 

우선 MemberController를 다뤄보겠다.

 

이번 프로젝트는 RestAPI 방식으로 데이터를 주고받을것이기 때문에 Controller또한 RestController

어노테이션을 사용한다.

 

그다음 회원을 조회하기전에 회원을 Insert한 후에 Select를 해보겠다.

 

우선 DTO를 먼저 보자.

 

[ MemberDto.java ]

package com.kh.jpa.dto;

import com.kh.jpa.entity.Member;
import com.kh.jpa.eums.CommonEnums;
import lombok.*;

import java.time.LocalDateTime;

public class MemberDto {

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Create {
        private String user_id;
        private String user_pw;
        private String user_name;
        private String email;
        private Member.Gender gender;
        private Integer age;
        private String phone;
        private String address;

        public Member toEntity() {
            return Member.builder()
                    .userId(user_id)
                    .userPw(user_pw)
                    .userName(user_name)
                    .email(email)
                    .gender(gender)
                    .age(age)
                    .phone(phone)
                    .address(address)
                    .build();
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    public static class Response {
        private String user_id;
        private String user_name;
        private String email;
        private Member.Gender gender;
        private Integer age;
        private String phone;
        private String address;
        private LocalDateTime modifyDate;
        private LocalDateTime enrollDate;
        private CommonEnums.Status status;

        public static Response toDto(Member member) {
            return Response.builder()
                    .user_id(member.getUserId())
                    .user_name(member.getUserPw())
                    .email(member.getEmail())
                    .gender(member.getGender())
                    .age(member.getAge())
                    .phone(member.getPhone())
                    .address(member.getAddress())
                    .enrollDate(member.getEnrollDate())
                    .modifyDate(member.getModifyDate())
                    .status(member.getStatus())
                    .build();
        }
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Update {
        private String user_name;
        private String email;
        private Member.Gender gender;
        private String phone;
        private String address;
        private Integer age;


    }

}

 

🔥 MemberController - PostMapping

현재 addMember 메서드는 매개변수로 Board 객체. 즉, 엔티티가 아닌 DTO로 받고있다.

 

DTO로 받는 이유는 클라이언트에서 요청한 데이터를 엔티티 자체로 DB에 저장하는것이 아닌

받은 값만 전달하기 위해 전달용 객체인 DTO에 담는다.

 

MemberDto.Create의 멤버변수에 값을 다 넣어주고 그 DTO 객체를 Service로보냈다.

 

아래 Service 코드를 보자 

 


 

🔥 MemberController - GetMapping

 

회원을 조회하는 부분에서는 요청한 API에서 PathValue인 userId를 꺼내서 service로 넘겨주는 것이 끝이다.

 

추후 그 값을 ResponseEntity.ok에 담아서 리턴하고 있다.

 

 


 

[ MemberService.java ]

package com.kh.jpa.service;

import com.kh.jpa.dto.MemberDto;

public interface MemberService {
    String createMember(MemberDto.Create createDto);

    MemberDto.Response findMember(String userId);
}

 

[ MemberServiceImpl.java ]

package com.kh.jpa.service;

import com.kh.jpa.dto.MemberDto;
import com.kh.jpa.entity.Member;
import com.kh.jpa.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

    @Override
    // createDto : 사용자가 입력한 회원정보
    public String createMember(MemberDto.Create createDto) {
        Member member = createDto.toEntity(); // 메모리 올라온 Member
        memberRepository.save(member);
        return member.getUserId(); // 영속상태의 Member
    }


    @Transactional(readOnly = true)
    @Override
    public MemberDto.Response findMember(String userId) {
        return memberRepository.findOne(userId)
                .map(MemberDto.Response :: toDto) // 있으면 변환해줘. 없으면 하지마
                .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 회원입니다."));

    }

}

 

🔥 MemberServiceImpl - PostMapping

우선 위에 @Transactional 어노테이션이 있는것을 볼 수 있다.

 

트랜잭션을 처리해주는 어노테이션이며 @Repository에서 영속성을 관리할건데 ,

이때 영속성을 캐싱 공간과 쓰기 지연 저장소가 존재하며 쿼리문을 쓰기 지연 장소에 저장한다.

 

여기서 DB에 반영하기 위해서 flush()를 사용하여 DB에 SQL을 날리고, commit까지 해줘야 하지만,

@Transactional 어노테이션을 사용하면 트랜잭션의 시작과 종료(커밋/롤백)을 자동으로 관리해준다.

 

즉, 개발자가 flush()나 commit()을 명시적으로 호출하지 않아도,

어노테이션이 해당 메서드를 트랜잭션 범위로 감싸주며,

메서드 실행 시점에 트랜잭션을 시작하고, 정상적으로 메서드가 종료되면 내부적으로 flush -> commit,

예외가 발생하면 자동으로 rollback을 수행시켜준다.

 

이제 트랜잭션 처리는 어노테이션에 맡기고 Repository에 코드를 넘겨주면 된다.

 

Repository란 해당 클래스는 DB와 관련된 CRUD 작업을 수행하는 클래스이다.

 

createMember()는 매개변수로 createDto를 받아주고,

사용자 정의 메서드인 toEntity()메서드를 통해 DTO -> Entity로 변경해준다.

 

-> 내부적으로 Bulider를 사용하여 필요한 값만 넣어줌.

나머지 값은 @PrePersist 어노테이션 사용하여, em.persist()가 사용되기 전에 기본값 세팅.

 

🧐 왜그렇게 해야하는가? 

 

JPA에서 영속성 컨텍스트에 등록하려면 반드시 엔티티 객체여야 한다. -> DTO 객체를 넣으면 안됨 ‼️

 

그 후 엔티티로 변경된 객체(Member 객체)를 Repository로 전달하고있다.

 

이제 Repository로 가보자.


 

🔥 MemberServiceImpl - GetMapping

현재 findMember()메서드에 @Tranactional(readOnly = true) 라고 적혀있다.

 

이것은 트랜잭션은 시작되지만, 내부적으로 쓰기(C,U,D)는 차단되고 읽기만 허용된다.

 

flush 동작을 생략하게 되어 성능이 약간 좋아진다.

 

사용하는 이유는 SELECT시에 실수로라도 데이터가 변경되는 걸 방지하고,

불필요한 flush,snapshot 비교 등을 생략해 성능을 높이기 위해 사용한다.

 

우선 Repository에 finOne을 하고오면 DB에서 Member 엔티티를 조회해 온다.

 

하지만 이때 Null일수도 있기때문에 Optional 객체로 감싼뒤 체크를 하고 그 값을 Service에 준것이기 때문에

 

Option 객체를 제거하는 동작이 필요하다. -> Null체크용 객체이기 때문에. 지금 넘길 값을 엔티티 자체가 아닌

전달한 값만 들어있는 DTO 객체를 반환해야함.

 

메서드 참조 방식을 사용해 Option 객체 내부에 있는 map함수를 이용해 DTO로 하나씩 바꾸는 과정을 거쳤다.

 

만약 값이 있으면 변환을해주고 없으면 하지않고, 아래에 있는 에러 메세지를 반환한다.

 

엔티티를 DTO로 바꾸던지, DTO를 엔티티로 바꾸던지 과정을 Service에서 진행한다 보면된다.

 


 

[ MemberRepository.java ]

package com.kh.jpa.repository;

import com.kh.jpa.entity.Member;

import java.util.Optional;

public interface MemberRepository {
    void save(Member member);

    Optional<Member> findOne(String userId);
}

 

[ MemberRepositoryImpl.java ]

package com.kh.jpa.repository;

import com.kh.jpa.entity.Member;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository // 해당 클래스는 DB와 관련된 작업을 수행하는 클래스다.
public class MemberRepositoryImpl implements MemberRepository {

    @PersistenceContext // EntityManager를 주입해줘.
    private EntityManager em;

    @Override
    public void save(Member member) {
        em.persist(member); // 영속
    }
}

 

🔥 MemberRepositoryImpl - PostMapping

Repository 또한 Mapper와 동일하게 인터페이스를 구현하는 방식으로 설계한다.

 

여기서는 이제 엔티티를 DB에 반영하기 위해서 영속성 컨텍스트에 엔티티를 올려줘야 한다.

 

@PersistenceContext를 사용하여 EntityManger를 주입받고,

 

주입 받은 EntityManger 객체를 사용하여 영속성 관리를 하면 된다.

 

em.persist(Member); 라고 작성한 것은 Member 엔티티를

영속상태로 만들고 관리하겠다는 뜻이다.

 

등록 후 Service로 돌아가서 member.getUserId()를 하여

영속성에 등록된 Member 엔티티의 userId를  가져오고 있다.

 

그 이후 return이 되어 createMember()메서드가 종료되면 트랜잭션이 flush -> commit을 날린다. (오류 없을시)

 

그 반환값을 userId에 담아 응답 값으로 ResponseEntity.ok(userId)를 날려주고 있다.

 

결과 화면

 


 

🔥 MemberRepositoryImpl - GetMapping

위에서 거의 설명하였지만 여기서는 Optinal.ofNullable를 사용하여 Null 체킹을 해주고 있다.

 

여기선 Null값을 허용해서 만약 Null이라면 Service에가서 에러를 띄운다.

 

에러가 나지않는다면 그대로 값을 반환한다.

 

결과화면

 

 



처음 배운 POSTMAN과 JPA이용 엔티티를 통한 SQL 작성이 정말 간편하게 CRUD를 할 수 있고, 

 

클라이언트단을 만들어 API를 직접 보내지 않아도 되는 장점이 있어 좋다.

'Weekly TIL' 카테고리의 다른 글

Weekly TIL - Day 34  (1) 2025.05.18
Weekly TIL - Day 33  (0) 2025.05.17
Weekly TIL - Day 31  (2) 2025.05.15
Weekly TIL - Day 30  (3) 2025.05.14
Weekly TIL - Day 29  (0) 2025.05.13
'Weekly TIL' 카테고리의 다른 글
  • Weekly TIL - Day 34
  • Weekly TIL - Day 33
  • Weekly TIL - Day 31
  • Weekly TIL - Day 30
KoesJin
KoesJin
hEELo
  • KoesJin
    Seok DevLog
    KoesJin
  • 전체
    오늘
    어제
    • 분류 전체보기 (110)
      • Back End (31)
        • DataBase (15)
        • JAVA (12)
        • JDBC (4)
      • Front End (9)
        • HTML5 & CSS (3)
        • Java Script (6)
        • REACT (0)
      • Server (9)
        • JSP - TomCat - Servlet (7)
        • Spring Boot (2)
      • GitHub (1)
      • AWS (1)
      • IT 지식 (기술면접 대비) (20)
      • Weekly TIL (39)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    MVC 패턴
    View
    INNER JOIN
    DAO
    select
    GC
    weekly til - day 39
    weekly til - day 40
    dml
    순서에 대하여
    commit
    DDL
    weekly til - day 41
    order by
    css
    where
    exception
    weekly til - day 38
    from
    weekly til - day 43
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
KoesJin
Weekly TIL - Day 32
상단으로

티스토리툴바