JAVA SPRING/PROJECT

[Day-11] 회원 등록, 목록 조회

sshhhh 2023. 8. 25.

파일구조

 


#회원등록

 

MemberForm

폼 객체를 사용해서 화면 계층과 서비스 계층을 명확하게 분리한다

<엔티티 그대로 안받고 이렇게 받는 이유>

- 필요한 것만 뽑아 쓰는게 깔끔함

- 요구사항이 실무에서 단순하지 않음 일대일로 매칭되는 경우 거의없음

- 엔티티에 화면을 추가하는 기능이 많아지면 지저분해짐 ->화면종속적기능 -> 유지보수 힘들다

엔티티를 최대한 순수하게 유지할것!!!

@Getter @Setter
public class MemberForm {

    @NotEmpty(message = "회원 이름은 필수입니다.") //name은 필수 나머지는 선택
    private String name;

    private String city;
    private String street;
    private String zipcode;

}

 

MemberController

@Controller
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @GetMapping("/members/new")
    public String createForm(Model model){
        model.addAttribute("memberForm", new MemberForm());//컨트롤러에서 뷰로 넘어갈때 데이터를 실어서 넘긴다.
        //form을 그냥 열고 빈화면을 가지고 간다.
        // (이름 내가 지어준것, 데이터)
        //memberForm 넘겼기 때문에 화면에서 new MemberForm()이라는 객체에 접근할 수 있다.
        return "members/createMemberForm"; //get방식으로 열린다
    }

    /*
    회원 등록 : 오류 처리
     */
    @PostMapping("/members/new") //실제로 등록
    //@Valid : MemberForm 어노테이션 적용되게
    //엔티티 그대로 안받고 이렇게 받는 이유 : 필요한 것만 뽑아 쓰는게 깔끔함
    public String create(@Valid MemberForm form , BindingResult result){

        //BindingResult result 오류가 여기 담겨서 실행이 된다
        if(result.hasErrors()){ //에러가 발생한 것을 인지하고
            return "members/createMemberForm"; //여기로 이동함
            //post로 보냈지만 다시 get으로 이동됐다
            //타임리프랑 스프링이 인티그레이션..되어잇어.. 끌고 갈 수 있음
        }

        Address address = new Address(form.getCity(),form.getStreet(),form.getZipcode());

        Member member = new Member();
        member.setName(form.getName());
        member.setAddress(address);

        memberService.join(member);

        return "redirect:/"; //저장 후 재로딩되면 좋지 않으니까 redirect해서 index로 넘김
        
    }
}

 

 

MemberService

//회원가입       //쓰기에는 true 넣으면 데이터 변경안되니까 넣지 말것
@Transactional //이렇게 따로 설정하면 이게 우선순위 (기본값 readOnly=false)
public Long join(Member member) {
    validateDuplicateMember(member); //중복회원 검증 로직
    memberRepository.save(member); //save : MemberRepository 함수
    return member.getId(); //값이 있다는게 보장이 된다. persist 하는 순간
}


 //중복체크
    private void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        if (!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다.");
        }
    }

 

MemberRepository

//jpa가 member를 저장하는 로직
//EntitlyManager가 persist하면 영속성 컨택스트에 Member Entity를 넣고 트랜잭션이 커밋되는 시점에 DB에 insert쿼리 날라가서 저장
public void save(Member member) {
    em.persist(member);
}

 

 

home.html

<a class="btn btn-lg btn-secondary" href="/members/new">회원 가입</a>

 

 

createMemberForm.html

 <!--

1.th:field="*{name} ctrl+space로 이동하면 th:object="${memberForm}의 name나옴 (참고하기때문에)

2. ${#fields.hasErrors('name')} : name에 에러가 있으면
   'form-control fieldError' : 4 style속성으로 색깔 걸기
-->
 <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요"
        th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">

 

<p th:if="${#fields.hasErrors('name')}"
   th:errors="*{name}">Incorrect date</p>
<!--  name필드에 에러메세지 ( 회원이름은 필수입니다) 출력

페이지소스보기

 

<div clas="form-group">
    <!--
    th:field="*{city}" : id ,name을 다 맞춰준다. field:MemberForm
    id=city , name =city 적어도 되지만 위처럼 간결하게
    -->
    <label th:for="city">도시</label>
    <input type="text" th:field="*{city}" class="form-control" placeholder="도시를 입력하세요">
</div>

 

페이지 소스보기

 

<form role="form" action="/members/new" th:object="${memberForm}" method="post">
.
.
.
    <button type="submit" class="btn btn-primary">Submit</button>
               <!--sumit을 누르면 /members/new의 메서드가 post로 넘어간다-->
</form>

 

전체코드

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header"/> <!--헤더 인클루드-->
<style>
    .fieldError {
        border-color: #fa93f4;
    }
</style>
<body>
<div class="container">
    <div th:replace="fragments/bodyHeader :: bodyHeader"/>

    <!--
     form이고, action경로로 갈거고
     th:object={} :타임리프문법 ,form안에서 이 객체를 쓰겠다
     post방식으로 넘김
    -->
    <form role="form" action="/members/new" th:object="${memberForm}" method="post">

        <div class="form-group">
            <label th:for="name">이름</label>
            <!--

           1.th:field="*{name} ctrl+space로 이동하면 th:object="${memberForm}의 name나옴 (참고하기때문에)

           2. ${#fields.hasErrors('name')} : name에 에러가 있으면
              'form-control fieldError' : 4 style속성으로 색깔 걸기
           -->
            <input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요"
                   th:class="${#fields.hasErrors('name')}? 'form-control fieldError' : 'form-control'">



            <p th:if="${#fields.hasErrors('name')}"
               th:errors="*{name}">Incorrect date</p>
            <!--  name필드에 에러메세지 ( 회원이름은 필수입니다) 출력
             스프링부트랑 타임리프 다 렌더링 되어있어서

             에러메세지 넣으려고 있는 코드-->

       </div>
        <div clas="form-group">
            <!--
            th:field="*{city}" : id ,name을 다 맞춰준다. field:MemberForm
            id=city , name =city 적어도 되지만 위처럼 간결하게
            -->
            <label th:for="city">도시</label>
            <input type="text" th:field="*{city}" class="form-control" placeholder="도시를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="street">거리</label>
            <input type="text" th:field="*{street}" class="form-control" placeholder="거리를 입력하세요">
        </div>
        <div class="form-group">
            <label th:for="zipcode">우편번호</label>
            <input type="text" th:field="*{zipcode}" class="form-control" placeholder="우편번호를 입력하세요">
        </div>

        <button type="submit" class="btn btn-primary">Submit</button>
        <!--sumit을 누르면 /members/new의 메서드가 post로 넘어간다-->
    </form>
    <br/>
    <div th:replace="fragments/footer :: footer"/>
</div> <!-- /container -->
</body>
</html>

 

 

콘솔창
DB insert완료

 

 

MemberForm

@NotEmpty(message = "회원 이름은 필수입니다.")

http://localhost:8080/members/new

 


 

#회원 목록 조회

 

MemberController

/*
회원목록
 */
@GetMapping("/members")
public String list(Model model){
    List<Member> members = memberService.findMembers(); //ctrl +alt + v
    model.addAttribute("members", members); //(키,값)
    return "members/memberList";

}

 

MemberService

/*
회원 전체 조회
*/
public List<Member> findMembers() {
    return memberRepository.findAll();
}

 

MemberRepository

//List 조회된 결과 반환해서 회원목록 뿌림
public List<Member> findAll() {
    //jpql 쿼리, 반환타입
    //sql이랑 기능적이랑 문법은 거의 동일 - > sql로 변환되기 때문
    //sql은 테이블을 대상으로 변환되지만 이거는 Entitly객체를 대상으로 변환
    return em.createQuery("select m from Member m", Member.class)
            .getResultList();
}

 

 

 

memberList.html

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>
<div class="container">
  <div th:replace="fragments/bodyHeader :: bodyHeader" />
  <div>
    <table class="table table-striped">
      <thead>
      <tr>
        <th>#</th>
        <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.address?.city}"></td>
        <td th:text="${member.address?.street}"></td>
        <td th:text="${member.address?.zipcode}"></td>
        <!--? : null이거나 그러면 더이상 진행하지 않음 if 이렇게 안쓰고..-->
      </tr>
      </tbody>
    </table>
  </div>
  <div th:replace="fragments/footer :: footer" />
</div> <!-- /container -->
</body>
</html>

 

http://localhost:8080/members

 

 

 

강의 : https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-JPA-%ED%99%9C%EC%9A%A9-1/dashboard

댓글