개발자입니다
[비트캠프] 76일차(16주차3일) - Servlet(세션, 쿠키, 리다이렉트), myapp-49(로그인, 권한별 기능 제한) 본문
[비트캠프] 76일차(16주차3일) - Servlet(세션, 쿠키, 리다이렉트), myapp-49(로그인, 권한별 기능 제한)
끈기JK 2023. 2. 22. 12:59
49 세션으로 클라이언트 구분하기
Web Browser 가 Servlet Container 에 처음 방문하면 HttpSession 객체를 생성하고 세션ID = 2500 을 생성해서 Web Browser 에게 준다. 그러면 www.a.com : 세션ID = 2500 을 Cookie에 기록한다.
Web Browser 가 Servlet Container 에 방문하면서 세션ID = 2500 가 있는 Cookie 를 제시한다. 그러면 세션ID = 2500 인 Session 객체를 사용한다.
*Cookie?
- 웹서버가 웹브라우저에게 보내는 부가정보
- 웹브라우저에서 유지
- 웹서버 방문시 제출
49 세션 생성 및 사용 과정
① Client 가 Server 에 요청한다. Server 는 세션을 생성한다.
② Server 는 세션ID = 100 을 기록하고 Client 에게 보낸다.
응답 프로토콜
HTTP/1.1 200 ok
...
Set-Cookie : sessionid=100 ← 한 개의 cookie
Set-Cookie : aaa=hello // aaa : cookie name, hello : cookie value
③ Client 는 세션ID = 100 을 Cookie 에 보관한다.
④ Client 는 Server 에 요청할때 세션ID = 100 을 포함해서 요청한다.
요청 프로토콜
GET /web/auth HTTP/1.1
...
Cookie : sessionid=100; aaa=hello ← 서버로부터 받은 쿠키들을 한 줄의 헤더로 보낸다.
⑤ Server 는 세션ID = 100 인 객체를 찾아서 사용한다.
☆세션ID는 cookie 기술을 이용하여 주고 받는다 → 웹브라우저가 cookie 를 허용하지 않으면 SessionID 를 주고 받을 수 없다.
49 세션과 쿠키를 이용하여 로그인 처리
세션객체 생성 전
Web Browser 에서 ① email/pwd 로 로그인 요청 을 ServletContainer 에게 한다. 여기서 ② service() 를 실행하라고 LoginServlet 에게 지시한다. 여기서 ③ findByE---() 메서드를 실행하라고 StudentDao 에게 지시한다. 여기서 DBMS 에 ④ select 질의하고 ⑤ 결과 받아 ⑥ Student 객체 생성한다. LoginServlet 으로 ⑦ 리턴 하고 여기서 HttpSession 을 ⑧ 생성 및 보관한다. ⑨ 리턴 하고 ⑩ 응답 으로 세션ID를 쿠키로 보낸다. Web Browser 에서 ⑪ 세션ID 보관 을 쿠키 테이블에 한다.
세션객체 생성 후
Web Browser 에서 쿠키 테이블의 ☆① 쿠키 정보 가져오기 하고 ② 요청 을 ServletContainer 에게 한다. 이때 세션ID를 쿠키로 보낸다. 여기서 ③ service() 를 실행하라고 LoginServlet 에게 지시한다. 여기서 HttpSession 에 ☆④ 세션ID로 세션객체 찾기 한다. 찾은 ⑤ 값을 꺼낸다.
parameterType에 단축문법 관련하여 mybatis.org 에서 설명 확인 가능하다.
java.util.map 이 map 이라고 정의되어 있다
49 web.xml
$context root/*.html, *.css, *.js, *.gif 등 정적자원
/*.jsp 동적자원
/WEB-INF
classes/*.class, *.properties, *.xml 등
lib/*.jar
web.xml ← web app. 설정 정보(자원(서블릿/JSP, 필터, 리스너, 파라미터 등) 배치 정보)를 담은 파일
↑ Deployment Descriptor 파일 (DD 파일)
루트 경로를 입력했을 때 index.html 말고 다른 파일 나오도록 하는 방법
webapp/WEB-INF 폴더 생성 후 안에 web.xml 생성한다.
다음 경로에서 내용 가져온다.
C:\Users\bitcamp\server\apache-tomcat-9.0.71\webapps\examples\WEB-INF
welcome-file 순서대로 가장 처음 보여주는 페이지를 찾는다.
metadata-complete="false" 로 해야 외부에서 @WebServlet 애너테이션이 있는 서블릿 을 찾는다.
"true" 로 하면 web.xml 에 있는 매핑 정보로만 서블릿을 찾는다.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0"
metadata-complete="false">
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
<welcome-file>index.xhtml</welcome-file>
<welcome-file>index.htm</welcome-file>
</welcome-file-list>
</web-app>
49 웹브라우저와 쿠키
Web Browser1 의 tab1, tab2 에서 쿠키 테이블을 사용한다.
Web Browser2 의 tab1 에서 쿠키 테이블을 공유해서 사용한다.
→ 쿠키 테이블을 공유하는 모든 웹브라우저를 종료해야마 쿠키테이블이 제거된다.
사생활 보호 모드로 Web Browser 실행 → 각 웹브라우저 마다 별도로 쿠키 관리 → 세션ID를 별도로 관리
Web Browser1 이 쿠키 테이블을 별도 사용한다.
Web Browser2 가 쿠키 테이블을 별도 사용한다.
49 Refresh vs Redirect
Refresh
Web Browser 에서 ① 요청을 Servlet Container 로 하고 ② 응답(콘텐트 포함) 을 받는다. Web Browser 에서 ③ 콘텐트 출력 한다.
Redirect
Web Browser 에서 ① 요청을 Servlet Container 로 하고 ② 응답(콘텐트를 포함하지 않는다) 을 받는다. Web Browser 에서 ③ 콘텐트 출력 도 없다.
49 필터를 이용한 로그인 여부 검사
ServletContainer 로 요청 오면 doFilter() 실행을 AuthFilter 에게 지시한다. ... service() 실행을 Servlet 에게 지시한다.
로그인 하지 않았으면 AuthFilter 에서 forwarding 을 /web/auth/form 으로 보낸다.
브라우저에서 로그인 정보를 꺼내지 말고 서버의 세션에서 로그인 정보를 꺼내서 번호를 가져와라!
브라우저에서 사용자 검증 해도 서버에서도 검증 해야한다.
49 DB 데이터를 중첩 객체에 저장하는 방법
① 이전 방식
하나의 게시글 데이터를 Board 객체에 저장 한다.
Board 가 Member 를 writer 필드에 포함한다.
SQL 매퍼 설정을 result map 에 한다.
49 테이블 관계와 객체 관계의 비교
테이블 관계
app_member (부모 테이블) I OI< app_board (자식 테이블)
객체 관계
→ 필요에 따라 관계를 정의한다. 테이블 관계에 영향 받지 않는다.
Board 에서 필드명 writer(property name)에 Member 를 포함한다.
Member 에서 필드명 boards 에 Board 를 포함한다.
또는 Map<Member,List<Board>> 로 저장한다.
### 49. 로그인, 로그아웃 처리하기: HttpSession, Cookie 사용
- HttpSession을 사용하여 로그인, 로그아웃을 처리하는 방법
- 클라이언트 별로 세션을 구분하는 원리
- ddl3.sql 실행: app_board 테이블 변경
- 게시글을 입력할 때 로그인 사용자 아이디를 작성자로 설정하는 방법
- 로그인 사용자만 등록, 변경, 삭제하는 방법
- 필터를 이용하여 로그인 여부 검사하는 방법
- 게시글 작성자만 자신의 게시글을 변경, 삭제할 수 있게 만드는 방법
filter/AuthFilter 생성
servlet/auth 폴더에 파일들 생성
webapp/auth 폴더 및 파일 생성
webapp/WEB-INF 폴더에 web.xml 생성
webapp 폴더에 index.jsp 생성
webapp/WEB-INF/web.xml 파일에서 index.jsp 를 welcome-file 첫번째로 설정한다.
webapp/index.jsp
로그인 했을때와 안했을 때 로그인/로그아웃에 차이 둔다.
<%@ 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>비트캠프 - NCP 1기</title>
</head>
<body>
<h1>강의관리시스템!</h1>
<ul>
<li><a href="student/list">학생관리</a></li>
<li><a href="teacher/list">강사관리</a></li>
<li><a href="board/list">게시판</a></li>
<c:if test="${empty loginUser}">
<li><a href="auth/form">로그인</a></li>
</c:if>
<c:if test="${not empty loginUser}">
<li><a href="auth/logout">로그아웃(${loginUser.name})</a></li>
</c:if>
</ul>
</body>
</html>
servlet/auth/LoginFormServlet.java
로그인 클릭시 서블릿: auth/form 으로 이동시킨다.
package bitcamp.myapp.servlet.auth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/auth/form")
public class LoginFormServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 웹브라우저가 보낸 email 쿠키 꺼내기
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("email")) {
request.setAttribute("email", cookie.getValue());
break;
}
}
}
request.getRequestDispatcher("/auth/form.jsp").forward(request, response);
}
}
webapp/auth/form.jsp
넘어온 "error" 의 값이 "loginfail" 이면 해당 문구 출력한다.
로그인 클릭시 /auth/login 경로로 요청한다.
<%@ 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>비트캠프 - NCP 1기</title>
</head>
<body>
<h1>로그인</h1>
<form action="login" method="post">
<c:if test="${error == 'loginfail'}">
<p>이메일 또는 암호가 맞지 않습니다!</p>
</c:if>
<table border="1">
<tr>
<th>회원 유형</th>
<td>
<input type="radio" name="usertype" value="student" checked> 학생
<input type="radio" name="usertype" value="teacher"> 강사
</td>
</tr>
<tr>
<th>이메일</th>
<td><input type="email" name="email" value="${cookie.email.value}"></td>
</tr>
<tr>
<th>암호</th>
<td><input type="password" name="password"></td>
</tr>
</table>
<div>
<label for="saveEmail"><input type="checkbox" id="saveEmail" name="saveEmail">이메일 저장</label><br>
<button>로그인</button>
</div>
</form>
</body>
</html>
servlet/auth/LoginServlet.java
form.jsp 에서 넘어온 "usertype" 값을 꺼내서 usertype 에 저장한다.
"email", "password" 값을 꺼내서 paramMap 에 저장한다.
이메일 저장 체크 확인을 위해서 "saveEmail" 에서 값을 꺼내서 값이 있는지(!= null) 확인한다.
있으면 paramMap 에서 "email" 의 값을 꺼내서 cookie 객체 생성해서 "email" 이라고 저장하고 보존 시간 설정한다.
응답 헤더에 쿠키 추가한다.
이메일 저장 체크 안했으면 ("saveEmail" == null 이면) cookie 객체 생성해서 "email" 에 "" 저장하고 보존 시간 0으로 해서 응답 헤더에 쿠키 추가한다.
usertype 에 따라 student 혹은 teacher 객체 가져온다.
member 객체 찾았으면(null 이 아니면)
- 세션 객체 가져와서 "loginUser" 의 값에 member 객체 보관한다. server root 로 sendRedirect 한다.
member 객체 못찾았으면
- request 에 "error" 의 값에 "loginfail" 저장하고 "/auth/form.jsp" 로 forward 한다.
package bitcamp.myapp.servlet.auth;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import bitcamp.myapp.dao.StudentDao;
import bitcamp.myapp.dao.TeacherDao;
import bitcamp.myapp.vo.Member;
@WebServlet("/auth/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private StudentDao studentDao;
private TeacherDao teacherDao;
@Override
public void init() {
ServletContext ctx = getServletContext();
studentDao = (StudentDao) ctx.getAttribute("studentDao");
teacherDao = (TeacherDao) ctx.getAttribute("teacherDao");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usertype = request.getParameter("usertype");
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("email", request.getParameter("email"));
paramMap.put("password", request.getParameter("password"));
if (request.getParameter("saveEmail") != null) {
// 다으번에 로그인 입력 화면을 출력할 때 이전에 입력한 이메일이 보여지도록
// 쿠키로 웹 브라우저에 보관해 둔다.
Cookie cookie = new Cookie("email", (String) paramMap.get("email"));
// 쿠키 보존 시간을 지정한다.
// => 쿠키 보존 시간을 지정하지 않으면 웹 브라우저가 실행되는 동안만 유지된다.
cookie.setMaxAge(60 * 60 * 24 * 30); // 30일 동안 유지
// 응답 헤더에 추가한다.
response.addCookie(cookie);
} else {
// 쿠키에 저장된 이메일을 제거하고 싶을 때
Cookie cookie = new Cookie("email", "");
cookie.setMaxAge(0);
response.addCookie(cookie);
}
Member member = null;
switch (usertype) {
case "student":
member = studentDao.findByEmailAndPassword(paramMap);
break;
case "teacher":
member = teacherDao.findByEmailAndPassword(paramMap);
break;
}
if (member != null) {
// 로그인 사용자 정보를 저장할 세션 객체를 준비한다.
// 1) 요청 프로토콜에 세션ID가 쿠키로 넘어 왔다면,
// - 세션ID에 해당하는 객체를 찾는다.
// 1-1) 세션 객체가 유효한 경우,
// - 찾은 세션 객체를 리턴한다.
// - 응답할 때 세션ID를 쿠키로 전달하지 않는다.
// 1-2) 세션 객체가 타임아웃 되어 유효하지 않은 경우,
// - 2) 를 수행한다.
// 2) 요청 프로토콜에 세션ID가 쿠키로 넘어오지 않았다면,
// - 새 세션 객체를 생성한 후 리턴한다.
// - 응답할 때 새로 생성한 세션의 ID를 쿠키로 웹브라우저에게 전달한다.
//
HttpSession session = request.getSession();
// 로그인 사용자 정보를 세션에 보관한다.
session.setAttribute("loginUser", member);
response.sendRedirect("../"); // ===> http://localhost:8080/web/
// 웹브라우저에게 전달하는 URL이다.
// 응답 프로토콜 예:
// HTTP/1.1 302
// Location: /index.html
// ...
//
} else {
request.setAttribute("error", "loginfail");
request.getRequestDispatcher("/auth/form.jsp").forward(request, response);
}
}
}
StudentDao, TeacherDao 인터페이스에 findByEmailAndPassword() 추가한다.
package bitcamp.myapp.dao;
import java.util.List;
import java.util.Map;
import bitcamp.myapp.vo.Student;
public interface StudentDao {
void insert(Student s);
List<Student> findAll(String keyword);
Student findByNo(int no);
Student findByEmailAndPassword(Map<String,Object> params);
int update(Student s);
int delete(int no);
}
servlet/auth/LogoutServlet.java
로그아웃은 session.invalidate() 로 세션 무효화시킨다.
package bitcamp.myapp.servlet.auth;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/auth/logout")
public class LogoutServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
// 세션을 무효화시킨다.
session.invalidate();
response.sendRedirect("../");
}
}
ddl3.sql
게시판에 작성자 표기를 위해 app_board 데이터 구조 변경한다.
-- 게시글 작성자의 번호를 저장하는 컬럼 추가
-- 작성자 번호는 app_member 테이블의 PK 컬럼을 참조하는 FK로 만든다
alter table app_board
add column writer int,
add constraint app_board_fk foreign key (writer) references app_member(member_id);
vo/Board.java
2가지 방법으로 Board 와 Member 를 연결할 수 있다.
Board 클래스에
1. writerNo, writerName 필드 추가
2. Member 필드 추가 // 이 방법 사용
public class Board implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private int no;
private String title;
private String content;
private String password;
private Date createdDate;
private int viewCount;
// private int writerNo;
// private String writerName;
private Member writer;
mapper/BoardMapper.xml
<resultMap> 에 객체를 추가하기 위해서는 <association> 을 사용해야 한다.
속성 중 property 에 Board 클래스의 필드명, javaType 에 필드 타입 입력한다.
<association> </association> 안에 member 객체의 DBMS column 및 java property 설정한다.
DB에서 app_board의 writer 컬럼 값은 writer.no 에, app_member의 name 컬럼 값은 writer.name 에 저장한다.
insert 문에서 writer.no 로 수정되었다.
findAll 문에서 join 추가하였다.
findByNo 문에서 서브쿼리 사용하였으나 사실 비추! 경험삼아 사용
<mapper namespace="BoardDao">
<resultMap type="board" id="boardMap">
<id column="board_id" property="no"/>
<result column="title" property="title"/>
<result column="content" property="content"/>
<result column="pwd" property="password"/>
<result column="created_date" property="createdDate"/>
<result column="view_cnt" property="viewCount"/>
<association property="writer" javaType="member">
<id column="writer" property="no"/>
<result column="name" property="name"/>
</association>
<!--
Board board = new Board();
board.setNo(rs.getInt("board_id"));
board.setTitle(rs.getString("title"));
board.setContent(rs.getString("content"));
board.setPassword(rs.getString("pwd"));
board.setCreatedDate(rs.getString("created_date"));
board.setViewCount(rs.getInt("view_cnt"));
Member m = new Member();
m.setNo(rs.getInt("writer"));
m.setName(rs.getString("name"));
board.setWriter(m);
-->
</resultMap>
<insert id="insert" parameterType="board">
insert into app_board(title, content, writer)
values(#{title}, #{content}, #{writer.no})
</insert>
<select id="findAll" resultMap="boardMap" parameterType="string">
select
b.board_id,
b.title,
b.writer,
b.created_date,
b.view_cnt,
m.name
from
app_board b
inner join app_member m on b.writer = m.member_id
<if test="keyword != '' and keyword != null">
where
b.title like(concat('%',#{keyword},'%'))
or b.content like(concat('%',#{keyword},'%'))
</if>
order by
b.board_id desc
</select>
<select id="findByNo" parameterType="int" resultMap="boardMap">
select
b.board_id,
b.title,
b.content,
b.writer,
(select name from app_member where member_id = b.writer) name,
b.created_date,
b.view_cnt
from
app_board b
where
b.board_id=#{no}
</select>
<!-- 후략 -->
insert, update, delete 서블릿
로그인 사용자 정보 코드 추가하였다.
Board 객체에 Member 객체 들어간다. loginUser 에서 번호 가져와서 Member 객체에 저장 후 이를 Board 객체에 저장한다.
@WebServlet("/board/insert")
public class BoardInsertServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private BoardDao boardDao;
@Override
public void init() {
ServletContext ctx = getServletContext();
boardDao = (BoardDao) ctx.getAttribute("boardDao");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 로그인 사용자의 정보를 가져온다.
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
Board board = new Board();
board.setTitle(request.getParameter("title"));
board.setContent(request.getParameter("content"));
// board.setPassword(request.getParameter("password"));
// board.setWriterNo(loginUser.getNo());
Member writer = new Member();
writer.setNo(loginUser.getNo());
board.setWriter(writer);
boardDao.insert(board);
request.getRequestDispatcher("/board/insert.jsp").forward(request, response);
}
}
@WebServlet("/board/update")
public class BoardUpdateServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private BoardDao boardDao;
@Override
public void init() {
ServletContext ctx = getServletContext();
boardDao = (BoardDao) ctx.getAttribute("boardDao");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 로그인 사용자의 정보를 가져온다.
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
Board board = new Board();
board.setNo(Integer.parseInt(request.getParameter("no")));
board.setTitle(request.getParameter("title"));
board.setContent(request.getParameter("content"));
// board.setPassword(request.getParameter("password"));
Board old = boardDao.findByNo(board.getNo());
if (old.getWriter().getNo() != loginUser.getNo()) {
response.sendRedirect("../auth/fail");
return;
}
if (boardDao.update(board) == 0) {
request.setAttribute("error", "data");
}
request.getRequestDispatcher("/board/update.jsp").forward(request, response);
}
}
@WebServlet("/board/delete")
public class BoardDeleteServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private BoardDao boardDao;
@Override
public void init() {
ServletContext ctx = getServletContext();
boardDao = (BoardDao) ctx.getAttribute("boardDao");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 로그인 사용자의 정보를 가져온다.
Member loginUser = (Member) request.getSession().getAttribute("loginUser");
int boardNo = Integer.parseInt(request.getParameter("no"));
Board old = boardDao.findByNo(boardNo);
if (old.getWriter().getNo() != loginUser.getNo()) {
response.sendRedirect("../auth/fail");
return;
}
if (boardDao.delete(boardNo) == 0) {
request.setAttribute("error", "data");
}
request.getRequestDispatcher("/board/delete.jsp").forward(request, response);
}
}
servlet/auth/AuthFailServlet.java
@WebServlet("/auth/fail")
public class AuthFailServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/auth/fail.jsp").forward(request, response);
}
}
webapp\auth\fail.jsp
<%@ 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>비트캠프 - NCP 1기</title>
</head>
<body>
<h1>권한 오류!</h1>
<p>실행 권한이 없습니다!</p>
</body>
</html>
조언
*나중에 쓸걸 대비해서 다 주석으로 두고 이렇게 하지 말라. 너무 많은 고민 하지 말라. 추후 필요하면 그냥 다시 작성하라.
*SAP ERP 같은 평범한 기술은 알려지면 금방 따라하기 때문에 노출하지 않는다. 별거 아닌 기술을 갖고 있는 선배가 후배에게 기술을 지키기 위해 할 수 있는 방법은 안알려주는 것이다.
과제
/
'네이버클라우드 AIaaS 개발자 양성과정 1기 > DBMS, SQL, JDBC, Servlet' 카테고리의 다른 글
[비트캠프] 78일차(16주차5일) - myapp-51~53(서비스 객체 도입, Front controller, Page Controller) (0) | 2023.02.24 |
---|---|
[비트캠프] 77일차(16주차4일) - Servlet(파일 업로드, 멀티파트), myapp-50(파일 업로드) (0) | 2023.02.23 |
[Java] 예제 소스 정리 - EL, JSTL (0) | 2023.02.21 |
[비트캠프] 75일차(16주차2일) - Servlet(MVC 모델II, EL, JSTL), myapp-47~48 (0) | 2023.02.21 |
[Java] 예제 소스 정리 - JSP (0) | 2023.02.20 |