09009

[Spring] 예제 2 - (중요) 카테고리 속성을 적용한 회원가입 구현 수정 본문

Back-End/Spring
[Spring] 예제 2 - (중요) 카테고리 속성을 적용한 회원가입 구현 수정
09009

화면에 체크박스 기능 추가 - 라디오 버튼

registerPage.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>회원 가입</h1>
<form action="./registerProcess" method="post">
	ID: <input type="text" name="user_id"><br>
	PW: <input type="password" name="user_pw"><br>
	nickname: <input type="text" name="nickname"><br>
	gender: 
	<input type="radio" name="gender" value="M">남
	<input type="radio" name="gender" value="F">여 <br>
	
	취미: 
	<input type="checkbox">농구
	<input type="checkbox">축구
	<input type="checkbox">야구
	<input type="checkbox">농구<br>
	
	email: <input type="text" name="email"><br>
	생년월일: <input type="date" name="birth"><br>
	전화번호: <input type="text" name="phone"><br>
	<button>회원가입</button>
	
</form>
<a href="./loginPage">로그인 페이지로 이동</a>
</body>
</html>

회원가입할 때 새로운 속성이 추가되었는데  member 테이블을 수정해야할까?

그것보다는 회원의 취미 정보를 담을 테이블을 따로 추가하는 것이 더 바람직하다.

 

member 테이블 수정? (x)

테이블 추가 (o)

- 다중 속성 insert하는 경우

- 제 1 정규화: 도메인이 원자값이어야 한다.

제 1 정규화 성립 X (올바르지 못한 설계)
위의 사례는 가능하긴 하다.

위의 사례는 취미를 등록할 수 있는 개수가 제한된다. (컬럼의 개수가 제한됨)

논리적으로는 동작하나, null값이 비정상적으로 많이 들어가는 문제 발생

 

다중속성의 경우,  원칙적으로 한 테이블에 컬럼을 여러 개 생성하는 것이 아니라 테이블 자체를 만드는 것이 더 바람직하다.

→ 테이블을 따로 설계하고 생성해야 한다.

 

다중속성은 1대 다 형태로 테이블을 설계

 

ex) 1번 회원이 야구, 축구를 선택

 

위 방식으로 테이블을 설계한 후 회원가입 작업을 수행하면 두 테이블에 데이터가 insert 될 것이다.

 

더 좋은 방법은

테이블을 하나 더 생성하는 것이다. (카테고리 테이블)

새로운 카테고리가 추가하고 싶을 때 코드 수정을 용이하게 하기 위함이다.

 

 

테이블 설계 

취미 테이블(fp_hobby)을 따로 생성하고 회원과 취미의 정보를 연결해주는 테이블(fp_member_hobby)을 생성한다.

 

테이블 생성

(fp_hobby_category → fp_hobby)

-- 취미 카테고리 T
DROP TABLE fp_hobby_category;
CREATE TABLE fp_hobby_category(
  id NUMBER PRIMARY KEY,
  name VARCHAR2(40)
);

DROP SEQUENCE fp_hobby_category_seq;
CREATE SEQUENCE fp_hobby_category_seq;

-- 회원 - 취미 연결 T
DROP TABLE fp_member_hobby;
CREATE TABLE fp_member_hobby(
  id NUMBER PRIMARY KEY,
  member_id NUMBER,
  hobby_id NUMBER
);

DROP SEQUENCE fp_member_hobby_seq;
CREATE SEQUENCE fp_member_hobby_seq;

 

취미 테이블 데이터 입력 (관리자 페이지까지 만들면 입력 안해도됨)

-- 카테고리 초기화
INSERT INTO fp_hobby_category VALUES(fp_hobby_category_seq.nextval, '야구');
INSERT INTO fp_hobby_category VALUES(fp_hobby_category_seq.nextval, '축구');
INSERT INTO fp_hobby_category VALUES(fp_hobby_category_seq.nextval, '농구');
INSERT INTO fp_hobby_category VALUES(fp_hobby_category_seq.nextval, '당구');

테이블 생성을 완료하였으면 DTO부터 생성한다.

HobbyCategoryDto (fp_hobby 테이블에 관한 DTO)

package com.ja.finalproject.dto;

public class HobbyCategoryDto {
	private int id;
	private String name;
	
	public HobbyCategoryDto() {
		super();
	}
	public HobbyCategoryDto(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	
}

MemberHobbyDto (fp_member_hobby 테이블에 관한 DTO)

package com.ja.finalproject.dto;

public class MemberHobbyDto {
	private int id;
	private int member_id;
	private int hobby_id;
	public MemberHobbyDto() {
		super();
	}
	public MemberHobbyDto(int id, int member_id, int hobby_id) {
		super();
		this.id = id;
		this.member_id = member_id;
		this.hobby_id = hobby_id;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public int getMember_id() {
		return member_id;
	}
	public void setMember_id(int member_id) {
		this.member_id = member_id;
	}
	public int getHobby_id() {
		return hobby_id;
	}
	public void setHobby_id(int hobby_id) {
		this.hobby_id = hobby_id;
	}
	
}

회원가입 페이지에서 취미 체크박스 항목이 있는 화면이 출력될 수 있도록

쿼리를 제작하고 mapper를 수정해주어야 한다.

ex) 회원가입 페이지에서 취미 항목을 선택할 수 있는 화면이 보여야 한다. - 반환형은 List로 적용

 

그리고 DB에서 취미 테이블에 카테고리를 추가될 때마다

화면에서도 항목이 추가된 것이 적용될 수 있도록 설계할 것이다. - c:foreach문 적용

 

MemberSqlMapper

	// 취미 관련
	public List<HobbyCategoryDto> selectHobbyList();

MemberSqlMapper.xml

회원가입 화면에 있는 취미 체크박스 항목에 취미 카테고리 여러 개가 출력될 수 있도록 쿼리를 작성한다.

	<!--  취미 관련 -->
	<select id="selectHobbyList" resultType="com.ja.finalproject.dto.HobbyCategoryDto">
		SELECT * FROM fp_hobby_category ORDER BY id ASC
	</select>

DB 테스트

MemberServiceImpl

	public List<HobbyCategoryDto> getHobbyList() {
		return memberSqlMapper.selectHobbyList();
	}

MemberController

기존 메서드 수정

	@RequestMapping("registerPage")
	public String registerPage(Model model) {
		
//		List<HobbyCategoryDto> list = memberService.getHobbyList();
//		model.addAttribute("hobbyList", list);
		
		model.addAttribute("hobbyList", memberService.getHobbyList());
		
		return "member/registerPage";
	}

 

jstl 코드 추가

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

✍ registerPage.jsp 수정

 

아래 코드 추가

취미: 
	<c:forEach items="${hobbyList}" var="hobby">
		<input type="checkbox"> ${hobby.name}
	</c:forEach>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>회원 가입</h1>
<form action="./registerProcess" method="post">
	ID: <input type="text" name="user_id"><br>
	PW: <input type="password" name="user_pw"><br>
	nickname: <input type="text" name="nickname"><br>
	gender: 
	<input type="radio" name="gender" value="M">남
	<input type="radio" name="gender" value="F">여 <br>
	
	취미: 
	<c:forEach items="${hobbyList}" var="hobby">
		<input type="checkbox"> ${hobby.name}
	</c:forEach>
	
	<br>
	
	email: <input type="text" name="email"><br>
	생년월일: <input type="date" name="birth"><br>
	전화번호: <input type="text" name="phone"><br>
	<button>회원가입</button>
	
</form>
<a href="./loginPage">로그인 페이지로 이동</a>
</body>
</html>

COMMIT 후 실행

클라이언트에서 작성한 파라미터를 어떻게 서버에 전송해야 할까?

일단 dto로 파라미터를 받지 않고 name 속성을 추가하여 이용한다.

registerPage.jsp 

수정

	
	취미: 
	<c:forEach items="${hobbyList}" var="hobby">
		<input type="checkbox" name="hobby_id" value="${hobby.id}"> ${hobby.name}
	</c:forEach>

checkbox의 특징

페이지 소스를 확인하면 name 속성의 이름이 같은 것이 여러 개 존재한다는 것을 확인할 수 있다.

get 방식으로 변경하고 웹을 실행시키고 아래 쿼리스트링을 확인해보면 아래와 같이 나온다.

위와 같이 파라미터가 전송된다.

체크박스에서 항목을 체크한 것만큼 데이터가 전송되는 것이다.

 

체크박스에서 항목을 여러 개 체크할 수 있기 때문에 hobby_id를 String이 아닌 list로 받아야 한다.

 

여러 개를 받아야 하기 때문에 위 코드는 하나만 받는다. 잘못된 경우

체크박스의 경우, 같은 name으로 여러 개의 값이 전송될 수 있으므로

받는 쪽에서도 여러 개의 값을 받아야 한다 → 반환형을 list 계열로 선언한다.

 

테스트 코드

INSERT INTO fp_member_hobby VALUES(
    fp_member_hobby_seq.nextval,
    1,
    3
  );

MemberSqlMapper 

	// 취미 등록
	// 파라미터 두개 -> dto로 묶는다
	public void insertMemberHobby(MemberHobbyDto memberHobbyDto);

MemberSqlMapper.xml

	<insert id="insertMemberHobby">
		INSERT INTO fp_member_hobby VALUES(
		    fp_member_hobby_seq.nextval,
		    #{member_id},
		    #{hobby_id}
		  )
	</insert>

hobby도 insert 해야 한다.

 

(중요)

로그인한 상태가 아니지만 회원가입 페이지에서 데이터를 insert하려할 때,  회원번호가 필요한 문제가 발생한다.

글쓰기하였을 때는 세션에 회원번호가 저장되어 있는데 회원가입의 경우는 그렇지 않다.

 

dual

dual: 아무 의미없는 테이블

SELECT fp_member_seq.nextval FROM dual;

의 쿼리를 실행할 때마다 번호가 올라감

MemberSqlMapper 

	// primary key를 생성하는 쿼리 -> insert할때마다 넣는 것이 좋음
	// 두 개의 테이블에 동시에 INSERT할때 필요
	public int createPk();

 

MemberSqlMapper.xml

	<select id="createPk" resultType="int">
		SELECT fp_member_seq.nextval FROM dual
	</select>

그리고 insert 쿼리 시퀀스 부분을 아래와 같이 변경한다.

	<insert id="insert">
		INSERT INTO fp_member VALUES(
			#{id},
			#{user_id},
			#{user_pw},
			#{nickname},
			#{gender},
			#{email},
			#{birth},
			#{phone},
			SYSDATE
		)
	</insert>

MemberServiceImpl

메서드 수정

	public void register(MemberDto memberDto, int[] hobby_id) {
		
		System.out.println("여기서 알고리즘 수행");
		
		int memberPk = memberSqlMapper.createPk(); // primary key 생성, nextval이 실행된다.
		
		// ! 중요 !
		memberDto.setId(memberPk); // 파라미터로 안받기 때문에 SET으로 해주어야함.
		
		memberSqlMapper.insert(memberDto);
		
		if(hobby_id != null) {
			for(int hobbyId : hobby_id) {
				
				MemberHobbyDto memberHobbyDto = new MemberHobbyDto();
				memberHobbyDto.setHobby_id(hobbyId);
				memberHobbyDto.setMember_id(memberPk);
				
				memberSqlMapper.insertMemberHobby(memberHobbyDto);
			}
		}

	}

MemberController

수정

	@RequestMapping("registerProcess")
	public String registerProcess(MemberDto params, int[] hobby_id) {
		
		System.out.println("registerProcess called");
		System.out.println(params.getId());
		System.out.println(params.getUser_id());
		System.out.println(params.getNickname());

		memberService.register(params, hobby_id);
		
		return "member/registerComplete";
	}