[SPIRNG] TODO ver2 (1) - 초기 세팅, 테이블 연결, CRUD , stream(), Query Method

림민지·2025년 3월 31일

Today I Learn

목록 보기
37/62
post-thumbnail

🔗 JPA Query Method 가이드

🧶 프로젝트 초기 설정

Dependencies 추가

  1. Spring Web
  2. Lombok
  3. Spring Data JPA
  4. MySQL Driver

프로젝트 생성 뒤, build.gradle에서 위처럼 의존성이 잘 추가되었는지 확인

DB 만들기

인텔리제이 오른편에 위치하는 DB에서 MySQL을 선택해 Data Source를 생성하고(todoPlus) schema를 만들어줍니다

이렇게하면 초기 설정 완료!


📂 Entity (테이블 만들기)

JPA는 코드를 통해 DB의 테이블을 만들고 내부의 객체를 다룰 수 있다.

0️⃣ 요구사항

1. 일정 테이블 요구사항
작성 유저명, 할일 제목, 할일 내용, 작성일&수정일(JPA Auditing 활용)

2. 회원 테이블 요구사항
유저명, 비밀번호, 이메일, 작성일 & 수정일(JPA Auditing 활용)

⭐️ @EnableJpaAuditing

Application(main) 클래스에 반드시 @EnableJpaAuditing 이 어노테이션을 추가해야만 작성 수정일을 자동으로 만들어준다!!!

1️⃣ BaseEntity

두 테이블 모두 공통적으로 작성일과 수정일이 자동으로 생성 및 수정되어야하므로 하나의 클래스에서 두 객체를 다루고, 이를 상속해 사용할 수 있도록 하자.

@Getter //가져다쓸수있게
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

@MappedSuperclass
: 공통 필드(createdAt, modifiedAt) 를 여러 엔티티에서 공유하면서,
실제 테이블은 생성되지 않도록 하는 역할

@EntityListeners(AuditingEntityListener.class)
:JPA에게 너 자동으로 일 좀 해라ㅋ라고 하는 것.
JPA Auditing 기능을 활성화
→ @CreatedDate, @LastModifiedDate 같은 자동 시간 등록 기능이 동작

이렇게하면 생성 수정일을 JPA가 알아서 관리해준다

2️⃣ Member Entity (회원 관리)

아까 만든 BaseEntity를 상속해 만든다

public class Member extends BaseEntity {

JPA에게 이 클래스를 테이블로 사용해 라고 하려면

@Getter //가져가서 쓰렴
@Entity //이거 엔티티임
@Table(name = "member") //테이블명 지정해주기

@Table 어노테이션에 이름을 지정해서 클래스에 붙여주면 된다.

회원 관리 클래스에 필요한 객체는 유저명, 비밀번호, 이메일

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String name;

    @Column(nullable = false)
    private String password;

    @Column(nullable = false, unique = true)
    private String email;

@GeneratedValue(strategy = GenerationType.IDENTITY)
: 데이터베이스가 자동으로 ID 값을 생성 (AUTO_INCREMENT 기능 사용)

@Column
: 객체의 제한사항 적용하기
→ null이 가능한지, 중복값을 가질 수 있는지 등..

그리고 나중에 사용할 때는 이름, 비번, 이메일만 필요할 때가 있으므로
아래처럼 만들어준다

public Member(String name, String password, String email){
        this.name = name;
        this.password = password;
        this.email = email;
    }

    public Member() {}//기본 생성자

3️⃣ Todo Entity (일정)

Member Entity와 비슷하게 만들면 된다.
작성 유저명, 할일 제목, 할일 내용을 만들어보자

@Getter
@Entity
@Table(name = "schedule")
public class Todo extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Setter
    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    @Column(nullable = false)
    private String title;

    @Column(columnDefinition = "longtext")
    private String content;

여기서 중요한 점은 바로 일정의 작성자를 Member 테이블에서 가져와서 사용한다는 것이다.

한명의 회원은 여러개의 게시글을 작성할 수 있으므로 N:1 단방향 관계
@ManyToOne
@JoinColumn(name = "member_id")을 사용해서
Member테이블에서 같은 이름을 찾아 Join하고, 일정테이블에서는 작성 유저명 필드 대신 유저 고유 식별자 필드(id)를 사용한다

이렇게 클래스를 만든 후, 다이어그램을 보면

todo테이블의 member_id가 외래키로 member테이블의 기본키인 id를 가르키고 있는 모습을 확인할 수 있다.


👥 Member(회원 기능 구현)

📌 Create

회원을 생성해보자

Dto

회원가입할때는
이름 name, 비번 password, 이메일 email을 요청해야하고
응답할때는 이름 name, 이메일 email, 고유 식별번호 id를 반환해야한다.

@Getter
public class SignUpResponseDto {
    private final Long id;
    private final String name;
    private final String email;
    
    public SignUpResponseDto(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

Repository

JPA를 활용하면, 인터페이스를 통해 자동으로 데이터를 CRUD해준다

public interface MemberRepository extends JpaRepository<Member, Long> {}

JpaRepository<Member, Long> 인터페이스를 상속해 그 기능을 사용할 수 있도록 해주자

Service & Controller

컨트롤러에서 이름, 비번, 이메일을 입력받아 Service의 signup 메서드에 보내주면, 서비스에서 이를 레포지토리에 넘겨 저장한 후, 고유 id값을 받아 다시 반환해주는 로직을 구현하자

@RestController
@RequestMapping("/members") //prefix
@RequiredArgsConstructor //생성자 알아서 만들기
public class MemberController {
    private final MemberService memberService;

    @PostMapping("/signup")
    public ResponseEntity<SignUpResponseDto> signUp(@RequestBody SignUpRequestDto dto) {
    	//서비스 클래스에서 반환한 값을 signUpResponseDto에 담아 보내준다
        SignUpResponseDto signUpResponseDto = memberService.signUp(dto.getName(), dto.getPassword(), dto.getEmail());
        return new ResponseEntity<>(signUpResponseDto, HttpStatus.CREATED);
    }
@Service
@AllArgsConstructor
public class MemberService {
    private final MemberRepository memberRepository; //레포 가져오기

    public SignUpResponseDto signUp(String name, String password, String email) {
        Member member = new Member(name, password, email); //멤버 엔티티로 새로운 멤버 객체를 만들고
        Member savedMember = memberRepository.save(member); //레포에 save해준다
        return new SignUpResponseDto(savedMember.getId(), savedMember.getName(), savedMember.getEmail());
    }; //id, 이름, 이메일을 reponse
}

Postman

성공 응답을 받는 모습을 확인할 수 있다!

DB에서도 자동으로 생성 수정일이 등록되어 저장된 모습을 확인할 수 있다


📌 Read

조회 기능을 구현해보자

전체적인 로직은 create와 비슷하지만 id를 활용해 유저를 찾을 때, repository에서 달라지는 부분이 존재한다

1. 전체 회원 조회

주의할점은 조회할때 비밀번호가 노출되면 안된다는것이다.

그러려면 멤버를 반환하는 DTO를 따로 만들어주어야한다.

1. MemberResponseDto

@Getter
public class MemberResponseDto {
    private final Long id;
    private final String name;
    private final String email;

    public MemberResponseDto(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

2. Controller - findAllMember()

여러 회원을 반환해야하므로 List<>
형태는 MemberResponseDto의 형식과 같게 반환해야한다

    //모든 회원 조회하기
    @GetMapping
    public ResponseEntity<List<MemberResponseDto>> findAllMember() {
        List<MemberResponseDto> members = memberService.findAllMember();
        return new ResponseEntity<>(members, HttpStatus.OK);
    }

이렇게 작성하고 서비스의 findAllMember()를 만들어보자

3. ⭐️ Service - findAllMember()

    public List<MemberResponseDto> findAllMember() {
        List<Member> members = memberRepository.findAll();
        return members.stream().map(
        member -> new MemberResponseDto(member.getId(), member.getName(), member.getEmail())).toList();
    }

members리스트를 레포에서 받아오되, 우리는 비밀번호를 제외한 값들을 반환해주어야하므로 아까 만들어둔 DTO형식으로 반환해보자

stream을 사용할때 map()을 사용해서 내가 원하는 값만 뽑아 출력할 수 있따.

member -> new MemberResponseDto(member.getId(), member.getName(), member.getEmail())

map()안에 이렇게 ->를 통해 MemberResponseDto형식으로 담아 보내주면 된다!!!

그러면 포스트맨에서

이와 같이 MemberResponseDto 형식으로 반환된 모습을 볼 수 있다!!

2. 단건 회원 조희 (id)

1. Controller
id를 통해 조회해야하므로 @GetMapping할때 "/{id}"를 함께 적자
그리고 id를 매칭해야하므로 @PathVariable 어노테이션을 사용!!

전체 조회때와 같이 비밀번호를 노출하면 안되므로 MemberResponseDto로 반환할 수 있도록 한다

    //회원 단건 조회 by ID
    @GetMapping("/{id}")
    public ResponseEntity<MemberResponseDto> findMemberById(@PathVariable Long id){
        MemberResponseDto memberResponseDto = memberService.findMemberById(id);
        return new ResponseEntity<>(memberResponseDto, HttpStatus.OK);
    }

memberService.findMemberById(id); 를 통해 조회해보자

2. Service

    //id로 단건조회
    public MemberResponseDto findMemberById(Long id) {
        Member member = memberRepository.findMemberByIdOrElseThrow(id);
        return new MemberResponseDto(member.getId(), member.getName(), member.getEmail());
    }

인자로 넘겨받은 id를 사용해 레포지토리(DB)에서 조회할 수 있도록 하자

3. Repository
JPA에서는 메서드 이름으로 DB를 활용할 수 있는데,
어떤 단어를 써야할지 고민될땐 꼭 공홈의 Query Method를 참고하자!!!

🔗 JPA Query Method 가이드

나는 id를 통해서 찾고, 예외처리도 해주고 싶으니까
findById & orElseThrow를 사용해서
"찾는다 / 멤버를 / 아이디 / 통해서 / 아니면 / 던져 에러" 이런식의 작명을 하면된다
(약간 웃김ㅋㅋㅋㅋ 나만?)

이를 활용해서 아까 만들어둔 레포 클래스에 적용하면 된다.

public interface MemberRepository extends JpaRepository<Member, Long> {
    default Member findMemberByIdOrElseThrow(Long id) {
        return findById(id).orElseThrow(()-> new ResponseStatusException(HttpStatus.NOT_FOUND, "입력하신 id의 회원은 존재하지 않습니다"));
    }
}

에러 처리를 한 번에 해줄 수 있으니 참 편하다 크크


📌 Delete

이제 유저 email과 비밀번호를 통해 유저의 정보를 수정할 수 있도록 해보자
→ 여기서 비번이 일치하지 않으면 실패 반환해야함!!

  1. @RequestBody를 통해 MemberRequestDto를 입력받도록 하자(JSON 형태로 입력받을 수 있게 함)
    //회원 정보 삭제 by 이름
    @DeleteMapping
    public ResponseEntity<Boolean> deleteMemberById(@RequestBody MemberRequestDto dto){
        
    }
  1. 비번이 맞는지 검증해야하므로 유저 이메일과 비밀번호를 Service로 보내서 로직 처리 및 검증하도록 하자
boolean result = memberService.delete(dto.getEmail(), dto.getPassword());
        if (result){
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

만약 result가 true라면 삭제하고, false면 BAD_REQUEST를 날리자

  1. 비번이 맞는지(true/false) 검증 및 레포와 소통해야하므로 Service에서 로직 처리 및 검증하도록 하자
//service
//이메일을 통해 멤버 조회 후, 비밀번호가 맞으면 삭제 로직 수행
    public boolean delete(String email, String password) {
        String findPw = memberRepository.findMemberdByEmail(email).get().getPassword();
        if (findPw.equals(password)){
            memberRepository.delete(memberRepository.findMemberdByEmail(email).get());
            return true;
        } else {
            return false;
        }
    }

memberRepository.findMemberdByEmail(email).get().getPassword(); 이 부분을 통해 레포에서 이메일에 맞는 비번을 찾아와야한다.

  1. 레포에서 이메일에 맞는 비번을 찾는 로직을 JPA로 구현하자
//이메일주소로 회원조회하기
    Optional<Member> findMemberdByEmail(String email);

findMemberdByEmail을 통해 JPA가
"SELECT * FROM member WHERE email = "내가 넣은 이메일"
쿼리를 실행할 수 있도록 해주자

여기서 받은 회원 정보를 사용해서 service에서 findMemberdByEmail(email).get().getPassword();를 통해 비밀번호를 조회할 수 있는거다

=> 만약 내가 입력한 비번과 저장된 비번이 같으면 삭제하고, 아니면 실패 반환!

PostMan

잘 삭제되는 것을 볼 수 있다


📌 Update

delete와 로직이 비슷하다

Controller

사용자에게 email oldPassword newPassword를 입력받아서 DB에 저장된 비번과 사용자가 입력한 기존비번이 같으면 새로운 비밀번호로 갈아주는 로직을 수행하자!!

//회원 비번 수정 by 이메일 & 비번
    @PutMapping
    public ResponseEntity<Boolean> updateMemberPassword(@RequestBody UpdatePasswordResponseDto dto){
        boolean result = memberService.updateMemberPassword(dto.getEmail(), dto.getOldPassword(), dto.getNewPassword());
        if (result){
            return new ResponseEntity<>(HttpStatus.OK);
        }else {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }

    }

Boolean타입으로 성공했는지 아닌지 반환받도록 했다.

 boolean result = memberService.updateMemberPassword(dto.getEmail(), dto.getOldPassword(), dto.getNewPassword());

service에서 비번 바꾸는 로직을 만들어 result에 반영해보자

Service

//이메일과 기존 비번 바꿀 비번을 입력해, 만약 기존 비번이 일치하면 비번 바꿔주기
    public boolean updateMemberPassword(String email, String oldPassword,String newPassword) {
        String findPw = memberRepository.findMemberdByEmail(email).get().getPassword();
        Member member = memberRepository.findMemberdByEmail(email).get();
        if (findPw.equals(oldPassword)){
            member.updatePassword(newPassword);
            memberRepository.save(member);
            return true;
        } else {
            return false;
        }
    }

삭제 로직과 같이, 비밀번호를 비교하는 로직을 구현하되
멤버 정보를 가져와서 Member 엔티티에 새로운 비번을 할당하는 로직을 만들었다.

//Member 엔티티
    public void updatePassword(String password){
        this.password = password;
    }

이 로직 수행 후 다시 DB에 저장해야하므로 save() 해주자

memberRepository.save(member);

PostMan

이렇게 OK를 반환받는 것을 확인할 수 있고,

DB에도 잘 반영된 모습을 확인할 수 있따!!


이렇게 오늘 회원을 등록하고 조회하고 수정하고 삭제하는 로직을 구현했다!!

이제 다음시간에는 게시판에 글을 작성하되, 이 회원정보와 연동될 수 있게하는 기능을 구현해보자!

profile
@lim_128

0개의 댓글