2022-03-30(수)
세션을 사용하는 request handler
클라이언트가 보낸 세션ID로 이전에 생성한 세션 객체를 찾는다.
무효한 세션이면 새로 세션 객체를 만들어서 넘겨준다
서버에서 생성한 세션ID
세션ID는 웹브라우저에서 생성하는 게 아니라 서버에서 생성하는 거
세션을 새로 만든 게 아니면 응답할 때 세션ID를 리턴할 필요가 없다
새로 만들었을 때만 응답 헤더에 포함된다
이름(개념)
구조도
흐름(과정)
아직 구체적으로 머리로 그려지지 않으니까 머리에 20~30%만 남음
세션ID는 웹브라우저에서 생성하는 게 아니라 서버에서 생성하는 거
fetch("/member/signup", { // 비동기 방식으로 서버에 요청을 보낸다.
method: "POST",
body: new URLSearchParams(fd)
})
http://localhost:8080/member/form.html
// MemberController
@RequestMapping("/member/signin")
public Object signin(String email, String password) {
return memberService.get(email, password);
}
public interface MemberService {
int add(Member member);
Member get(String email, String password);
}
@Override
public Member get(String email, String password) {
return memberDao.findByEmailAndPassword(email, password);
}
@Param을 사용하여 SQL Mapper에서 사용할 이름 지정
보통은 같게 한다
public interface MemberDao {
int insert(Member member);
Member findByEmailAndPassword(@Param("email") String email, @Param("password") String password);
<select id="findByEmailAndPassword" resultMap="memberMap">
select
no,
name,
email
from
ml_member
where
email=#{email} and password=password(#{password})
</select>
<resultMap type="member" id="memberMap">
<id column="no" property="no"/>
<result column="name" property="name"/>
<result column="email" property="email"/>
<result column="regist_date" property="registDate"/>
</resultMap>
http://localhost:8080/member/signin.html
http://localhost:8080/member/[email protected]&password=abc123
json으로 바꾸기
fetch("/member/signin", { // 비동기 방식으로 서버에 요청을 보낸다.
method: "POST",
body: new URLSearchParams(fd)
})
.then(function(response) {
return response.json();
})
.then(function(obj) {
console.log(obj);
window.alert("로그인 성공");
});
fetch("/member/signin", { // 비동기 방식으로 서버에 요청을 보낸다.
method: "POST",
body: new URLSearchParams(fd)
})
.then(function(response) {
return response.json();
})
.then(function(obj) {
if (obj == "") {
window.alert("로그인 실패!");
} else {
window.alert(obj.name + "님 환영합니다!");
}
});
@RequestMapping("/member/signin")
public Object signin(String email, String password) {
Member loginUser = memberService.get(email, password);
if (loginUser == null) {
return "";
} else {
return loginUser;
}
}
json을 일반 문자열로 보내면 안 되네... null을 보내도 안 되네
@RequestMapping("/member/signin")
public Object signin(String email, String password) {
Member loginUser = memberService.get(email, password);
if (loginUser == null) {
return "fail";
} else {
return "success";
}
}
fetch("/member/signin", { // 비동기 방식으로 서버에 요청을 보낸다.
method: "POST",
body: new URLSearchParams(fd)
})
.then(function(response) {
return response.text();
})
.then(function(text) {
if (text == "success") {
location.href = "../index.html";
} else {
window.alert("로그인 실패!");
}
});
로그인 성공하면 index.html로 옴
@RequestMapping("/member/signin")
public Object signin(String email, String password, HttpSession session) {
Member loginUser = memberService.get(email, password);
if (loginUser == null) {
return "fail";
}
// 로그인이 성공하면,
// 다른 요청을 처리할 때 로그인 회원의 정보를 사용할 수 있도록 세션에 보관한다.
session.setAttribute("loginUser", loginUser);
return "success";
}
DAO 구현체가 사용할 SQL Mapper 파일의 위치는 인터페이스의 패키지 경로 및 이름과 일치해야 한다.
SQL Mapper 파일 이름이랑 인터페이스 이름하고 똑같아야 함 (MemberDao.xml)
http://localhost:8080/member/signin.html
<form name="form1">
이메일: <input name="email" type="email"><br>
암호: <input name="password" type="password"><br>
<div>
<button id="x-add-btn">로그인</button>
<button id="x-cancel-btn" type="button">취소</button>
</div>
<div>
<a href="form.html">회원가입</a>
</div>
</form>
@RequestMapping("/member/signup")
public Object signup(Member member) {
if (memberService.add(member) == 1) {
return "success";
} else {
return "fail";
}
}
// form.html
fetch("/member/signup", { // 비동기 방식으로 서버에 요청을 보낸다.
method: "POST",
body: new URLSearchParams(fd)
})
.then(function(response) {
return response.text();
})
.then(function(text) {
if (text == "success") {
location.href = "signin.html";
} else {
window.alert("회원가입 실패!");
}
});
return false;
};
document.querySelector("#x-cancel-btn").onclick = function() {
window.location.href = "../index.html";
};
상단 메뉴바를 추가한다.
// index.html
<style>
#header {
background-color: navy;
color: white;
height: 50px;
display: flex;
align-items: center;
}
</style>
</head>
<body>
<div id="header">
MyList
<button id="login-btn" type="button">로그인</button>
<button id="logout-btn" type="button">로그아웃</button>
</div>
package com.eomcs.mylist.controller;
import lombok.Data;
@Data
public class ResultMap {
final String status;
final Object data;
}
final 붙이면 생성자
package com.eomcs.mylist.controller;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
@Data
@NoArgsConstructor(force = true) // 기본 생성자를 무조건 만들게 한다.
@RequiredArgsConstructor // final이 붙은 필드의 값을 파라미터로 받는 생성자를 만들게 한다.
public class ResultMap {
final String status;
final Object data;
}
@RequestMapping("/member/getLoginUser")
public Object getLoginUser(HttpSession session) {
Object data = session.getAttribute("loginUser");
if (data != null) {
return new ResultMap("success", data);
} else {
return new ResultMap("fail", "로그인 하지 않았습니다.");
}
}
8단계 - 로그인 사용자 정보를 조회한다.
• controller.ResultMap 클래스 추가
JSON 형식의 데이터를 리턴할 때 사용할 클래스
작업 성공 유무와 결과를 저장한다.
• MemberController 클래스 변경
getLoginUser() 메서드 추가
final, 애노테이션 지우기
package com.eomcs.mylist.controller;
import lombok.Data;
@Data
public class ResultMap {
String status;
Object data;
}
lombok setter return this 검색
세터 게터 메서드 이름이 필드 이름과 같아
세터의 리턴 타입도 ResultMap
접두어(get/set)를 붙이는 경우도 있고 없는 경우도 있음
@Accessors(fluent = true) 하면 필드 이름과 같게 만들어진다
세터를 통해서만 접근할 수 있
package com.eomcs.mylist.controller;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(fluent = true)
public class ResultMap {
private String status;
private Object data;
}
한 눈에 알아보기가 쉽다. 그래서 이 방법을 쓰는 거
@RequestMapping("/member/getLoginUser")
public Object getLoginUser(HttpSession session) {
Object member = session.getAttribute("loginUser");
if (member != null) {
return new ResultMap().status("success").data(member);
} else {
return new ResultMap().status("fail").data("로그인 하지 않았습니다.");
}
}
에러 남
json 충돌 일어남
get, set으로 시작하는
json 데이터로 바꾸
@Accessors(chain = true) 로 변경하기
@RequestMapping("/member/getLoginUser")
public Object getLoginUser(HttpSession session) {
Object member = session.getAttribute("loginUser");
if (member != null) {
return new ResultMap()
.setStatus("success")
.setData(member);
} else {
return new ResultMap()
.setStatus("fail")
.setData("로그인 하지 않았습니다.");
}
}
success 입력하다 보면 오타 날 수도 있음
오타 방지
상수로 정의해놓기
package com.eomcs.mylist.controller;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
@Accessors(chain = true)
public class ResultMap {
public static final String SUCCESS = "success";
public static final String FAIL = "fail";
private String status;
private Object data;
}
@RequestMapping("/member/getLoginUser")
public Object getLoginUser(HttpSession session) {
Object member = session.getAttribute("loginUser");
if (member != null) {
return new ResultMap()
.setStatus(ResultMap.SUCCESS)
.setData(member);
} else {
return new ResultMap()
.setStatus(ResultMap.FAIL)
.setData("로그인 하지 않았습니다.");
}
}
상수 import 하기
@RequestMapping("/member/getLoginUser")
public Object getLoginUser(HttpSession session) {
Object member = session.getAttribute("loginUser");
if (member != null) {
return new ResultMap()
.setStatus(SUCCESS)
.setData(member);
} else {
return new ResultMap()
.setStatus(FAIL)
.setData("로그인 하지 않았습니다.");
}
}
<div id="header">
<span id="app-title">MyList</span>
<span id="user-name"></span>
<button id="login-btn" type="button">로그인</button>
<button id="logout-btn" type="button">로그아웃</button>
</div>
<script type="text/javascript">
fetch("/member/getLoginUser").then(function(response) {
return response.json();
}).then(function(result) {
document.querySelector("#user-name").innerHTML = result.data.name;
});
</script>
var el = document.querySelectorAll(".login");
for (var e of el) {
e.style.display = "none"
}
function css(selector, name, value) {
var el = document.querySelectorAll(selector);
for (var e of el) {
e.style[name] = value;
}
}
jQuery 사용하는 이유
코드가 간결해진다.
브라우저에 맞춰서 해당되는 기능이 동작하도록 코딩되어 있다.
크로스 브라우징 지원
9단계 - 로그아웃 기능을 추가한다.
로그아웃은 Controller만 바꾸면 됨
@RequestMapping("/member/signout")
public Object signout(HttpSession session) {
session.invalidate();
return new ResultMap().setStatus("success");
}
document.querySelector("#logout-btn").onclick = function() {
fetch("/member/signout").then(function(response) {
location.href = "/index.html"
});
}
18.2 세션과 쿠키의 활용: 세션 활용하기
로그인과 세션을 활용하여 사용자별로 데이터 처리하기
alter table ml_board
add column writer int not null,
add constraint ml_board_fk foreign key (writer) references ml_member(no);
기존 데이터가 있으면 not null 컬럼은 나중에 추가 불가
먼저 기존 데이터 다 지우기
delete from ml_board;
3단계에 거쳐서 하는 방법도 있음
null 허용하고 값 넣고 다시 not null로 바꾸는 건 가능
2단계 - 게시글을 다루는 도메인 클래스에 회원 번호를 담는 필드를 추가한다.
Board 클래스 변경하기
@Data
public class Board {
int no;
String title;
String content;
int viewCount;
java.sql.Date createdDate;
int writer;
}
게시글 입력 시 작성자 번호를 입력하도록 SQL Mapper 파일을 변경한다
게시글 데이터를 다룰 때 작성자 번호도 함께 다룬다
@RequestMapping("/board/add")
public Object add(Board board, HttpSession session) {
Member member = (Member) session.getAttribute("loginUser");
if (member == null) {
return new ResultMap().setStatus(FAIL).setData("로그인 하지 않았습니다.");
}
board.setWriter(member.getNo());
boardService.add(board);
return new ResultMap().setStatus(SUCCESS);
}
자기가 쓴 글만 가능
and writer=#{writer}
<update id="update" parameterType="Board">
update ml_board set
title=#{title},
content=#{content}
where
board_no=#{no} and writer=#{writer}
</update>
parameterType="board"로 변경하기
<delete id="delete" parameterType="board">
delete from ml_board
where board_no=#{no} and writer=#{writer}
</delete>
5단계 - 게시글 조회할 때 로그인 사용자의 이름을 함께 조회한다.
<resultMap>
에 Member 객체를 가져오는 코드를 추가
https://mybatis.org/mybatis-3/sqlmap-xml.html
<association>
← 1:1 관계의 테이블을 join 할 때 사용
<!-- 테이블의 컬럼과 객체 필드를 연결한다. -->
<resultMap type="board" id="boardMap">
<id column="board_no" property="no"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="created_date" property="createdDate"/>
<result column="view_count" property="viewCount"/>
<association property="writer" javaType="member">
<id column="no" property="no"/>
<result column="name" property="name"/>
</association>
</resultMap>
↓ name 컬럼 값은 member 객체의 name 이라는 필드에 저장해라
<result column="name" property="name"/>
저번 시간에는 ContactDao.xml에서 <collection>
을 써서 연락처 테이블과 전화번호 테이블을 join 했었다
<collection>
← 1:M 관계의 테이블을 join 할 때 사용
findAll
, findByNo
SQL문 변경: ml_member
테이블과 조인한다.
<select id="findAll" resultMap="boardMap">
select
b.board_no,
b.title,
b.created_date,
b.view_count,
m.no,
m.name
from
ml_board b
inner join ml_member m on (b.writer = m.no)
order by
b.board_no desc
</select>
그대로 복사해서 sql문이 올바른지 확인한다.
select
b.board_no,
b.title,
b.created_date,
b.view_count,
m.no,
m.name
from
ml_board b
inner join ml_member m on (b.writer = m.no)
order by
b.board_no desc;
잘 나온다
게시글과 작성자
90-MyList프로젝트2 / 67 페이지
🔹 테이블 관계 (ER-Diagram)
ml_board
ml_member
한 명의 멤버는 0개 이상의 게시글을 쓸 수 있다.
🔹 자바 객체 (UML Class Diagram) (객체 관계)
Board 객체
Member 클래스
Board 객체는 writer라는 이름으로 Member 객체 1개를 포함한다.
Class Diagram에 따라 소스코드를 표현한 게 ↓
Board 안에 Member를 포함하고 있다
Board 안에 Member를 포함하고 있다
<select id="findByNo" resultMap="boardMap" parameterType="int">
select
b.board_no,
b.title,
b.content,
b.created_date,
b.view_count,
m.no,
m.name
from
ml_board b
inner join ml_member m on (b.writer = m.no)
where
b.board_no=#{no}
</select>
이제 writer는 int가 아님. (Member로 바꿨음)
Board 객체 안에 들어 있는 writer 객체의 no (writer.no)
writer → writer.no
<insert>
, <update>
, <delete>
SQL문 변경: writer 필드의 타입이 int에서 Member 변경된 것을 적용한다.
BoardController 클래스 변경
add(), update(), delete() 메서드 변경
번호가 아니라 Member 객체를 담는다
member.getNo()
→ member
localhost:8080/board/list
board 객체 안에 writer 객체가 들어 있다
http://localhost:8080/board/get?no=12
${board.writer}
→ ${board.writer.name}
board 객체에 writer 라는 필드가 Member 객체이다
writer 객체의 name 필드
페이지 컨트롤러에서 결과를 리턴할 때 예전처럼 텍스트로 리턴하지 않고
항상 ResultMap에 담아서 성공 실패 여부를 리턴하자
Author And Source
이 문제에 관하여(2022-03-30(수)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@banana/2022-03-30수저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)