WEB JAVA SPRING/PROJECT
[Day-11] 회원 등록, 목록 조회
sshhhh
2023. 8. 25. 16:12
#회원등록
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>
MemberForm
@NotEmpty(message = "회원 이름은 필수입니다.")
#회원 목록 조회
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>