package com.tenco.controller;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TodoController extends HttpServlet {
private static final long serialVersionUID = 1L;
public TodoController() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().append("Served at : ").append(request.getContextPath());
// getContextPath() --> 프로젝트 경로만 가져온다. 파일경로X
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
UserController
package com.tenco.controller;
import java.io.IOException;
import com.tenco.model.UserDAO;
import com.tenco.model.UserDAOImpl;
import com.tenco.model.UserDTO;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
// 주소 설계
// http://ip:포트/rootContext/urlmapping
// http://localhost:8080/mvc/user
// /user/* --> http://localhost:8080/mvc/user/xxx 가능
@WebServlet("/user/*")
public class UserController extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserDAO userDAO;
public UserController() {
super();
}
@Override
public void init() throws ServletException {
userDAO = new UserDAOImpl();
}
// GET 방식으로 들어올 때
// http://localhost:8080/mvc/user/signUp
// http://localhost:8080/mvc/user/signIn
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// user/signIn --> 로그인 페이지
// user/signUp --> 회원가입 페이지
String action = request.getPathInfo();
// getPathInfo() --> 클라이언트가 요청 시 보낸 URL과 관련된 추가 경로 정보를 반환한다.
switch (action) {
case "/signIn":
// 로그인 페이지로 보내는 동작 처리
request.getRequestDispatcher("/WEB-INF/views/signIn.jsp").forward(request, response);
// getRequestDispatcher() --> 클라이언트로부터 최초에 들어온 요청을 JSP/Servlet 내에서
// 원하는 자원으로 요청을 넘기는 역할을 수행하거나 특정 자원에 처리를 요청하고 처리 결과를 얻어오는 기능을 수행하는 클래스이다.
break;
case "/signUp":
// 회원 가입 페이지로 보내는 동작 처리
request.getRequestDispatcher("/WEB-INF/views/signUp.jsp").forward(request, response);
break;
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
break;
}
}
// http://localhost:8080/mvc/views/todoForm.jsp
// --> 서블릿을 통해 요청한 것이 아니라 해당 경로로 바로 이동했다.
// 그래서 보안 폴더인 web-inf에 views폴더(jsp포함)를 옮겨준다.
// 로그인 기능 요청(자원에 요청 -- GET 방식 예외적인 처리 _ 보안)
// POST 요청시 - 로그인 기능 구현, 회원가입 기능 구현
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getPathInfo();
switch (action) {
case "/signIn":
break;
case "/signUp":
signUp(request, response);
break;
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
break;
}
}
/**
* 회원가입 기능
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void signUp(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 임시로 인증 검사 필요 없는 기능
String username = request.getParameter("username");
String password = request.getParameter("password");
String email = request.getParameter("email");
// 방어적 코드 작성(username 확인한 것)
if(username == null || username.trim().isEmpty()) {
request.setAttribute("errorMessage", "사용자 이름을 입력하시오");
request.getRequestDispatcher("/WEB-INF/views/signUp.jsp").forward(request, response);
return;
}
// 방어적 코드 작성 (password 확인)
// 방어적 코드 작성 (email 확인)
UserDTO userDTO = UserDTO.builder().username(username).password(password).email(email).build();
int resultRowCount = userDAO.addUser(userDTO);
System.out.println(resultRowCount);
if(resultRowCount == 1) {
response.sendRedirect("user/signIn?message=success");
}else {
response.sendRedirect("user/signUp?message=error");
}
}
}
package com.tenco.model;
import java.util.List;
public interface UserDAO {
// insert 쿼리는 단순해서 보통 리턴타입으로 void를 사용한다.
int addUser(UserDTO userDTO);
UserDTO getUserById(int id);
UserDTO getUserByUsername(String username);
List<UserDTO> getAllUsers();
// 왜 파라미터 값으로 DTO와 id 값을 받는걸까?
// 내 정보를 확인하기 위해서는 권한이 필요하다.
// 그 권한을 인증하기 위해서는 세션 ID도 필요하다.
int updateUser(UserDTO user, int principalId);
int deleteUser(int id); // 인증검사를 실시해야 한다.
}
UserDAOImpl
package com.tenco.model;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import javax.naming.InitialContext;
import javax.sql.DataSource;
public class UserDAOImpl implements UserDAO {
private DataSource dataSource;
public UserDAOImpl() {
// jdbc 연결
try {
InitialContext ctx = new InitialContext();
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public int addUser(UserDTO userDTO) {
// 실행 완료된 행 개수를 알고 싶다.
int resultCount = 0;
// 회원가입시 insert 쿼리문
String sql = " INSERT INTO users(username, password, email) VALUES(? , ? , ?) ";
// 데이터베이스 커넥션
try (Connection conn = dataSource.getConnection()) {
// 트랜잭션 시작
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, userDTO.getUsername());
pstmt.setString(2, userDTO.getPassword());
pstmt.setString(3, userDTO.getEmail());
// executeUpdate() --> int 값으로 반환된다.
resultCount = pstmt.executeUpdate();
// 트랜잭션 커밋 - 물리적인 장치에 데이터 영구히 남기기
conn.commit();
System.out.println("resultCount : " + resultCount);
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
} // end of PreparedStatement
} catch (Exception e) {
System.out.println("커넥션 개체 오류");
e.printStackTrace();
} // end of connection
return resultCount;
}
/**
* 깊은 공부를 하지 않았다면
* SELECT에서는 일단 트랜잭션 처리를 하는 것을 이해하지 못한다.
* 그러니 일단 SELECT에서는 트랜잭션을 사용하지 말자.
*
* 하지만 팬텀 리드 현상 때문이라면 사용하는 것이 좋다.
* (정합성을 위해서 처리하는 것도 옳은 방법이다.)
*/
@Override
public UserDTO getUserById(int id) {
String sql = " SELECT * FROM users WHERE id = ? ";
UserDTO userDTO = null;
try (Connection conn = dataSource.getConnection()) {
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setInt(1, id);
ResultSet rs = pstmt.executeQuery();
// 특정 유저를 찾는 것이니 단일 행이다. --> if 사용
if(rs.next()) {
userDTO = new UserDTO();
userDTO.setId(rs.getInt(id));
userDTO.setUsername(rs.getString("username"));
userDTO.setPassword(rs.getString("password"));
userDTO.setEmail("email");
userDTO.setCreatedAt("created_at");
}
} catch (Exception e) {
// TODO: handle exception
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(userDTO.toString());
return userDTO;
}
@Override
public UserDTO getUserByUsername(String username) {
String sql = " SELECT * FROM users WHERE username = ? ";
UserDTO userDTO = null;
try (Connection conn = dataSource.getConnection()){
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
userDTO = new UserDTO();
userDTO.setId(rs.getInt("id"));
userDTO.setUsername(rs.getString("username"));
userDTO.setPassword(rs.getString("password"));
userDTO.setCreatedAt(rs.getString("created_at"));
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(userDTO.toString());
return userDTO;
}
@Override
public List<UserDTO> getAllUsers() {
String sql = " SELECT * FROM users ";
// 자료구조를 사용할 때 일단 생성시키자.
// 메모리 낭비가 약간 있는 방법이지만 그리 크지 않으니 일단 사용한다.
List<UserDTO> list = new ArrayList();
try (Connection conn = dataSource.getConnection()){
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
ResultSet rs = pstmt.executeQuery();
// 모든 이용자를 검색하는 것이기 때문에 다중행이다.
// --> while 반복문 사용
while(rs.next()) {
UserDTO userDTO = new UserDTO();
userDTO.setId(rs.getInt("id"));
userDTO.setUsername(rs.getString("username"));
userDTO.setPassword(rs.getString("password"));
userDTO.setEmail(rs.getString("email"));
userDTO.setCreatedAt(rs.getString("created_at"));
list.add(userDTO);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list.toString());
return list;
}
@Override
public int updateUser(UserDTO user, int principalId) {
int rowCount = 0;
String sql = " UPDATE users SET password = ?, email = ? WHERE id = ? ";
try (Connection conn = dataSource.getConnection()){
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setString(1, user.getPassword());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, principalId);
rowCount = pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return rowCount;
}
@Override
public int deleteUser(int id) {
int rowCount = 0;
String sql = " DELETE FROM users WHERE id = ? ";
try (Connection conn = dataSource.getConnection()){
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setInt(1, id);
pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return rowCount;
}
}
UserDTO
package com.tenco.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* DTO 클래스
* 데이터를 변환하거나 담는 개념이며,
* 메서드를 사용할 수 있다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserDTO {
private int id;
private String username;
private String password;
private String email;
private String createdAt;
// 필요하다면 메서드 정의 가능
}
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입</title>
</head>
<body>
<h1>회원가입 화면입니다.</h1>
<%
String eMessage = (String)request.getAttribute("errorMessage");
// getAttribute
// Object 타입이다. 모든 객체를 담을 수 있고, 클래스 객체를 받을 수 있다.
// 해당 메소드를 이용하여 세션 변수를 검색할 수 있다.
// setAttribute를 통해 값을 설정해주지 않으면 null값을 반환받는다.
// getParameter()와 getAttribute() 차이
// getParameter()는 클라이언트로부터 HTML 값을 받아올 때
// getAttribute()는 JSP, Servlet과 값을 주고 받는다.
%>
</body>
</html>