JAVA/JAVA | Spring 학습기록

[Spring] MySQL+JPA+Spring+Gradle 회원조회 Read 실습

kth990303 2021. 5. 10. 21:15
반응형

환경세팅하는 부분은 이 포스팅에서 볼 수 있다.

kth990303.tistory.com/46

 

[Spring] MySQL+JPA+Spring+Gradle 환경설정 및 실습

그동안 백준 풀면서 스프링 공부를 소홀히 한 게 느껴져서 오랜만에 스프링 공부도 할 겸, 간단한 회원조회 실습을 해보기로 했다. 말그대로 기초적인 간단한 실습을 복습 겸 하는거라, Domain에 M

kth990303.tistory.com

오늘 이 클래스들을 씹고 뜯고 맛볼 생각이다.

아직 이 코드들은 github에 올리지는 않았다.

github에 올리게 된다면 포스팅 수정 후 추가하겠다.

 

MVC 디자인패턴을 최대한 준수하기 위해 위와 같이 Domain, Repository, Controller, Service로 나누어서 코드를 짰다.

회원 엔티티에 있어야 할 정보들을 살펴보기 위해 Domain부터 살펴보자.

(참고로 Entity: JPA가 관리하는 클래스라고 생각하면 된다. 즉, 회원 클래스는 jpa에서 관리하는 클래스이므로 Member 클래스를 Member 엔티티라고 부르겠다.)


Domain

관리하는 db테이블이 member 분이므로 Member 엔티티밖에 없다. 바로 Member 클래스를 살펴보자.

 

Member.class

package shop210504.pracshop.domain;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Getter @Setter
@RequiredArgsConstructor
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String tier;
}

@Entity 를 추가하여 JPA에서 다루는 엔티티임을 명시해준다.

이 어노테이션을 추가하면 Mysql에서 Member 테이블과 Java에서 Member.class가 서로 연동돼있음을 JPA에서 알 수 있다. 회원 정보로는 회원넘버, 회원이름, 회원티어를 새겨주었다.

회원 넘버 위에 @Id는 db에서의 PK(Primary Key, 기본키)임을 명시해준 것이고, @GeneratedValue는 넘버링이 알아서 자동증가하여 번호를 매겨주게 한다는 뜻이다.


Repository

객체지향 설계를 위해 MemberRepository 인터페이스와 그 구현체에 해당하는 MemberRepositoryImpl 클래스로 나누어 코딩하였다. SOLID 원칙의 OCP(개방-폐쇄 원칙)을 잘 지킬 수 있게 된다. 더 자세한 건 나도 아직 잘 모르겠다... 확실한건 기능추가될 때 수정을 최소화할 수 있다. 

 

MemberRepository.class (Interface)

package shop210504.pracshop.repository;

import shop210504.pracshop.domain.Member;

import java.util.List;

public interface MemberRepository {
    Member save(Member member);
    Member findMember(Long id);
    void deleteById(Long id);
    List<Member> showMembers();
}

회원 저장을 위한 save 메소드, 

회원 삭제 및 정보수정에 쓰이는 회원탐색 findMember 메소드,

회원 리스트 출력을 위해 쓰이는 showMembers 메소드,

회원 삭제 deleteById 메소드가 있다.

 

구현체에는 위 코드들을 오버라이딩할 것이다.

 

MemberRepositoryImpl.class (Implementation)

package shop210504.pracshop.repository;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import shop210504.pracshop.domain.Member;

import javax.persistence.EntityManager;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepository{
    private final EntityManager em;

    @Override
    public Member save(Member member) {
        em.persist(member);
        return member;
    }

    @Override
    public Member findMember(Long id) {
        Member findMember = em.find(Member.class, id);
        return findMember;
    }

    @Override
    public void deleteById(Long id) {
        Member member = findMember(id);
        em.remove(member);
    }

    @Override
    public List<Member> showMembers() {
        //System.out.println("Repository의 showMembers 호출");
        return em.createQuery("select m from Member m", Member.class).getResultList();
    }
}

EntityManager를 통해 JPA ORM을 사용하게 했다.

JPA 명령어 (em.persist, em.find, em.remove 등)은 아래 포스팅을 참고하자.

(참고로 em.merge를 하면 update도 할 수 있을 듯한데, 굳이 그럴 필요가 없다.)

kth990303.tistory.com/34

 

[JPA] 영속성 컨테이너, 그리고 회원 CRUD

오랜만에 개발 공부를 하는 겸, 스프링 공부를 할까, JPA 공부를 할까, nodejs 공부를 할까 고민하던 중, 너무 오랫동안 방치해두었던 jpa 공부를 진행해보기로 했다. 인프런 김영한님의 JPA 기본편

kth990303.tistory.com

 

또한, @Repository 어노테이션을 붙여주면 스프링부트에서 의존관계 주입을 통해

이 클래스가 리포지토리로 쓰이며, 다른 객체에서 리포지토리 로직을 필요로 할 때, 이 어노테이션이 붙여진 클래스의 메소드들이 자동으로 이용된다.

@RequiredArgsContructor 역시 의존관계 자동 주입에 사용되며, 생성자를 자동으로 lombok이 생성해주게 하는 어노테이션이다.


Service

db에 회원 정보들이 저장돼있고, 그에 대한 접근을 repository 패키지에서 한다면,

이와 관련된 비즈니스 로직은 Service 패키지에서 작성한다고 보면 된다.

지금 이 코드는 Member 회원조회밖에 없으므로 Service, Repository가 별 차이가 없어보인다.

 

아래 질문글이 있는 걸 보아하니, 나만 이렇게 느낀 것이 아니었나보다.

www.inflearn.com/questions/77417

 

Repository vs Service 의 역할의 차이점 - 인프런 | 질문 & 답변

안녕하세요. 선생님 강의 정말 잘 보고있습니다. 궁금한점이 있어서요 Repository 패키지와 Service 패키지가 보통 구분되어있는데 정확하게 쓰임새의 차이점을 모르겠습니다. 그냥 스프링에서 Servic

www.inflearn.com

질문글에 남겨진 답변이 (아직은 잘 감은 안오지만) 많은 도움이 되었다.

 

Service 또한 Repository처럼 MemberService 인터페이스와 그 구현체인 MemberService.Impl 클래스로 나누어 코딩하였다.

MemberService.class (Interface)

package shop210504.pracshop.service;

import shop210504.pracshop.domain.Member;

import java.util.List;

public interface MemberService {
    Long join(Member member);
    Member find(Long id);
    void delete(Long id);
    List<Member> showMembers();
}

MemberServiceImpl.class (Implementation)

package shop210504.pracshop.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import shop210504.pracshop.domain.Member;
import shop210504.pracshop.repository.MemberRepository;

import java.util.List;

@Service
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
    private final MemberRepository memberRepository;

    @Override
    public Long join(Member member) {
        Member joinMember = memberRepository.save(member);
        return joinMember.getId();
    }

    @Override
    public Member find(Long id) {
        Member member = memberRepository.findMember(id);
        return member;
    }

    @Override
    public void delete(Long id) {
        memberRepository.deleteById(id);
    }

    @Override
    public List<Member> showMembers() {
        List<Member> members = memberRepository.showMembers();
        System.out.println("ServiceImple의 showMembers 호출");
        for (Member member : members) {
            System.out.println("member.getName() = " + member.getName());
        }
        return members;
    }
}

 

솔직히 아직은 Service와 Repository가 차이점이 없기 때문에

Service에 해당하는 로직은 전부 Repository의 메소드를 불러서 해결하고 있다.

그도 그럴것이, 지금 CRUD 기능만 실습하고 있기 때문이다.

 


Controller

회원정보들이 조회되는 화면만 보여주면 되므로 Controller에는 많이 적을 것이 없다.

 

IndexController.class

package shop210504.pracshop.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import shop210504.pracshop.domain.Member;
import shop210504.pracshop.service.MemberService;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class IndexController {
    private final MemberService memberService;

    @GetMapping("/member")
    public String home(Model model){
        List<Member> memberList = memberService.showMembers();
        model.addAttribute("members", memberList);
        return "index";
    }
}

 

주소창 localhost:8080 뒤에 @GetMapping에 적혀있는 /member 를 붙이면 

IndexController의 "/member"와 매핑된 메소드가 호출돼 

Service 객체의 showMembers() 메소드를 통해 회원정보들을 부른 후,

스프링부트 model에 "members"라는 attributeName으로 데이터를 저장한 후,

return "index" 를 통해 index.html을 호출한다.

<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>회원조회 연습 홈페이지</title>
    <link href="../static/css/index.css" th:href="@{/css/index.css}" rel="stylesheet" />
</head>
<body>
<h1>너가 관리하는 회원들이 궁금해?</h1>
<a href="/login">로그인</a><br>
<a href="/createMemberForm">회원가입</a>
<hr>
<div class="container">
    <table width="100%" border="1">
        <thead>
        <tr>
            <th>회원번호</th>
            <th>이름</th>
            <th>티어</th>
            <th>정보 수정</th>
        </tr>
        </thead>
        <tbody>
        <tr th:each="member : ${members}">
            <td th:text="${member.id}"></td>
            <td th:text="${member.name}"></td>
            <td th:text="${member.tier}"></td>
            <td><a href="/" th:href="@{'delete/'+${member.id}}">삭제</a></td>
        </tr>
        </tbody>

    </table>
</div>
</body>
</html>

attributeName에 해당하는 ${members} 에 List<Member> 가 저장돼있는 것이며,

thymeleaf 템플릿 엔진으로 멤버정보들을 출력해준다.


결과

TDD를 통해 Member 회원가입, 회원조회가 잘 일어남을 확인할 수 있었다.

자세한 건 아래 포스팅을 참고하자.

kth990303.tistory.com/47

 

[Spring] Spring boot Test 코드 작성해보기 (TDD 연습)

스프링 강의를 들으면서, 그리고 주변 개발자들이 포스팅한 것을 보면서 tdd의 중요성을 조금씩 느끼곤 있었지만, 에러가 뜰까봐 두려움 때문에, 그리고 직접 스프링 실행해서 db를 직접 cmd창으

kth990303.tistory.com

Test 모두 Passed

그래도 의심이 되는 분들을 위해 직접 결과를 보자.

회원 정보가 잘 들어가 있음을 확인

choisk0531이 잘 들어가 있음을 확인할 수 있다.

(@Rollback(false)로 해놓고 test를 많이 돌렸더니 choisk0531이 분신술을 써버린 것처럼 결과가 나왔다...)

MySQL에 또한 잘 들어가 있음을 확인했다.

(확인하고 바로 delete from member where name="choisk0531"; 쿼리 날렸다 ㅎㅎ)


간단하게 회원조회 코드를 짜보았다!

사실 spring, jpa가 기본적으로 공부할 게 많아

여기까지 오는데도 꽤 시간이 걸릴 수 있다.

 

앞으로 남은 시간동안 꾸준히 spring boot, jpa, java를 공부한다면

내 실력에 더 많은 발전이 이루어지지 않을까 기대한다.

반응형