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에 담아서 성공 실패 여부를 리턴하자

좋은 웹페이지 즐겨찾기