MVC 패턴이란?
MVC (Model-View-Controller) 패턴은 소프트웨어 설계 패턴으로, 애플리케이션을 세 가지 주요 구성 요소인 모델(Model), 뷰(View), 컨트롤러(Controller)로 나누어 구현하는 방식을 말한다. 이를 통해 코드의 재사용성과 유지보수성을 높이고, 역할과 책임을 명확히 분리하는데 목적이 있다.
💡 소프트웨어 설계 패턴은 소프트웨어 개발에서 자주 발생하는 문제를 해결하기 위해 검증된 재사용 가능한 솔루션입니다. 설계 패턴은 객체 지향 설계 원칙을 따르며, 다양한 상황에서 사용될 수 있는 일반적인 템플릿을 제공한다. (디자인패턴이라고도 한다.)
주요 소프트웨어 설계 패턴
생성 패턴 (Creational Patterns)
: 객체 생성 메커니즘을 제공하여 코드의 유연성을 높인다.
구조 패턴 (Structural Patterns)
: 클래스와 객체를 조합하여 더 큰 구조를 형성한다.
행위 패턴 (Behavioral Patterns)
: 객체 간의 상호작용과 책임 분담을 정의한다.
그럼 MVC 패턴은 무슨 패턴일까?
MVC (Model-View-Controller) 패턴은 1979년에 트리그브 렌스카우그(Trygve Reenskaug)가 제록스 팔로 알토 리서치 센터(Xerox Palo Alto Research Center, PARC)에서 개발한 Smalltalk-80 언어에서 처음 소개되었다. 렌스카우그의 원래 목표는 사용자 인터페이스를 설계하는 데 있어 데이터, 비즈니스 로직, 그리고 사용자 인터페이스를 명확히 분리하는 것이다. 이로 인해 시스템을 더 쉽게 이해하고 유지보수할 수 있게 되었다.
MVC는 구조 패턴과 행위 패턴을 결합한 복합 패턴으로 볼 수 있다.
MVC 패턴은 Model, View, Controller의 앞 글자를 딴 것으로 프로그램을 구성하는 요소들을 모델, 컨트롤, 뷰로 나누어 설계하는 아키텍처 패턴 중의 하나이다.
- Model (모델) : 애플리케이션의 데이터와 비즈니스 로직을 관리한다. 데이터베이스와의 상호작용을 처리하며, 데이터의 상태를 유지 한다 .
- View (뷰) : 사용자 인터페이스를 담당 한다 . 모델의 데이터를 사용자에게 보여주고, 사용자의 입력을 받아들인다.
- Controller (컨트롤러) : 사용자의 입력을 처리하고, 모델과 뷰를 연결한다. 사용자의 요청을 받아 적절한 모델을 호출하고, 결과를 뷰에 전달한다.
Dynamic Web Project 생성
- MVC 패턴을 활용한 코드 설계
- 필요 라이브러리 확인
히카리를 사용하기 위해서는 alpha5.jar도 필요함 (의존성)
순으로 읽음
커넥션 풀을 사용하기 위한 DataSource 설계와 conext.xml 파일에 사용
context.xml 파일에 DataSource 설정 (HikariCP 사용 )
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
name="jdbc/MyDB"
auth="Container"
factory="com.zaxxer.hikari.HikariJNDIFactory"
uniqueResourceName="MyDB"
minimumIdle="5"
maximumPoolSize="10"
connectionTimeout="30000"
idleTimeout="60000"
maxLifetime="1800000"
jdbcUrl="jdbc:mysql://localhost:3306/m_todo?serverTimezone=Asia/Seoul"
driverClassName="com.mysql.cj.jdbc.Driver"
username="root"
password="asd123"/>
</Context>
- Resource name="jdbc/MyDB": JNDI 이름으로, 애플리케이션에서 이 이름을 통해 데이터 소스를 참조한다.
- auth="Container": 인증 방식으로, 컨테이너에 의해 인증됨을 의미 한다.
- type="javax.sql.DataSource”: 이 리소스가 DataSource 타입임을 명시 한다.
- factory="com.zaxxer.hikari.HikariJNDIFactory”: HikariCP의 JNDI 팩토리 클래스를 사용하여 데이터 소스를 생성 한다.
- uniqueResourceName="MyDB”: HikariCP 설정에 사용되는 고유한 리소스 이름이다.
- minimumIdle="5": 최소 연결 수를 5로 설정한다.
- maximumPoolSize="10”: 최대 연결 수를 10으로 설정 다.
- connectionTimeout="30000”: 연결을 시도할 때 30초(30000 밀리초) 동안 대기한다.
- idleTimeout="600000”: 유휴 상태의 연결을 10분(600000 밀리초) 동안 유지 한다.
- maxLifetime="1800000”: 연결의 최대 수명을 30분(1800000 밀리초)으로 설정 한다.
- driverClassName="com.mysql.cj.jdbc.Driver": MySQL JDBC 드라이버 클래스 이름이다.
context.xml 파일을 사용하는 이유
자원 공유에 목적
- 여러 웹 애플리케이션이 동일한 자원(예: 데이터 소스)을 사용할 때, 이를 중앙에서 관리하고 공유할 수 있다.
- 각 웹 애플리케이션이 별도로 설정하지 않고도 동일한 설정을 사용할 수 있어, 설정의 중복을 피할 수 있다.
환경 설정 분리
- 애플리케이션 코드와 환경 설정을 분리하여 관리할 수 있다.
- 데이터베이스 연결 정보와 같은 환경 설정을 코드에서 분리하여 관리하면, 애플리케이션을 다른 환경(예: 개발, 테스트, 운영)으로 이동할 때 설정만 변경하면 된다.
즉, 대규모 애플리케이션에서는 많은 설정이 필요하다. 이를 중앙에서 관리할 수 있는 방법론이 필요 하였고 관리자나 운영자가 설정을 일관되게 관리하고 변경할 수 있는 메커니즘이 제공하기 위한 기술로 발전하게 되었다.
context.xml 파일의 역할과 JNDI의 개념
context.xml
- 서버 시작 시 톰캣 같은 서블릿 컨테이너에 의해 로드됩니다. 일반적으로 web.xml 파일보다 먼저 실행이 된다.
- 이 파일에 정의된 리소스는 메모리에 객체로 올라가며, JNDI 네임스페이스에 이름으로 등록된다.
- 이러한 설정은 각 애플리케이션에서 공유할 수 있도록 전역적으로 설정된다.
JNDI (Java Naming and Directory Interface)
- 네이밍 및 디렉토리 서비스를 통해 자원(예: 데이터 소스)을 이름으로 찾고 참조하는 기술이다.
- 애플리케이션은 JNDI 네임스페이스에 저장된 자원에 접근하여 해당 자원의 객체를 사용할 수 있다.
- JNDI를 통해 네이밍 서비스에 의해 관리되는 객체(리소스)를 중앙에서 쉽게 관리하고 공유할 수 있다.
!! 중요 !!
context.xml에 기술된 리소스는 내부적으로 파일을 읽어서 객체를 WAS(Web Application Server)의 메모리에 로드하고, 해당 객체(DataSource)는 JNDI API를 활용하여 JNDI 네임스페이스에 설정된 이름으로 등록된다. 이렇게 등록된 객체는 전역적으로 다른 애플리케이션 또는 컴포넌트가 이름을 통해 접근하여 사용할 수 있는 기술이다.
... 생략
InitialContext ctx = new InitialContext();
// JNDI 네임스페이스에서 "java:comp/env/jdbc/MyDB" 이름으로 데이터 소스를 찾음
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
JNDI 이름 규칙
java:comp/env : 표준 JNDI 컨텍스트 루트로, 애플리케이션 환경 항목이 배치되는 기본 네임스페이스이다.
Resource 이름 : 리소스 이름은 context.xml 파일에서 정의된 이름과 일치해야 한다.
DBUtil 설계
package com.tenco.utils;
import java.sql.Connection;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class DBUtil {
private static DataSource dataSource;
// 정적 초기화 블록
static {
// TODO - 삭제 예정
try {
// InitialContext 객체를 생성하여 JNDI API 기술을 통해 존재하는 리소스를 찾는 방법
InitialContext ctx = new InitialContext();
dataSource = (DataSource)ctx.lookup("java:comp/env/jdbc/MyDB");
} catch (NamingException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
}
InitialContext 객체의 역할
InitialContext 객체는 JNDI (Java Naming and Directory Interface) API의 기본 클래스 중 하나로, 애플리케이션이 네이밍 및 디렉토리 서비스를 사용할 수 있도록 해준다.
MySQL 테이블 설계
create database if not exists db_todo2;
use db_todo2;
-- 정규화 1, 2, 3 정규 테이블 설계
-- users 테이블을 생성
create table if not exists users(
id int auto_increment primary key,
username varchar(50) not null,
password varchar(255) not null,
email varchar(100) not null,
created_at timestamp default current_timestamp
);
desc users;
alter table users add constraint unique(username);
-- todos 테이블 생성
create table if not exists todos(
id int auto_increment primary key,
title varchar(100) not null,
description text,
created_at timestamp default current_timestamp,
due_date date,
completed boolean default false,
user_id int not null,
foreign key(user_id) references users(id)
);
-- 샘플 데이터 삽입
-- users 테이블에 데이터 삽입
INSERT INTO users (username, password, email) VALUES
('홍길동', 'asd123', 'hong@example.com'),
('김철수', 'asd123', 'kim@example.com'),
('이영희', 'asd123', 'lee@example.com');
-- todos 테이블에 데이터 삽입
INSERT INTO todos (user_id, title, description, due_date, completed) VALUES
(1, '할 일 1', '할 일 1에 대한 설명입니다.', '2023-12-31', FALSE),
(1, '할 일 2', '할 일 2에 대한 설명입니다.', '2024-01-15', TRUE),
(2, '할 일 3', '할 일 3에 대한 설명입니다.', '2024-02-28', FALSE),
(3, '할 일 4', '할 일 4에 대한 설명입니다.', '2024-03-10', TRUE);
select * from users;
select * from todos;
show processlist;
UserDAO 설계(인터페이스)
package com.tenco.model;
import java.util.List;
public interface UserDAO {
int addUser(UserDTO userDTO);
UserDTO getUserById(int id);
UserDTO getUserByUsername(String username);
List<UserDTO> getAllUsers();
int updateUser(UserDTO user, int principalId);
int deleteUser(int id);
}
UserDTO 설계
package com.tenco.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* 데이터 변환, 담는 개념 , 메서드 사용할 수 있다.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserDTO {
private int id;
private String username;
private String password;
private String email;
private String createdAt;
// 필요하다면 메서드 정의 가능
}
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.naming.NamingException;
import javax.sql.DataSource;
public class UserDAOImpl implements UserDAO {
private DataSource dataSource;
public UserDAOImpl() {
try {
InitialContext ctx = new InitialContext();
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
public int addUser(UserDTO userDTO) {
int resultCount = 0;
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());
resultCount = pstmt.executeUpdate();
// 트랜잭션 커밋
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
} // end of PreparedStatement
} catch (Exception e) {
e.printStackTrace();
} // end of Connection
return resultCount;
}
/*
* 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 (rs.next()) {
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"));
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("UserDTO : " + 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.setEmail(rs.getString("email"));
userDTO.setCreatedAt(rs.getString("created_at"));
}
} catch (Exception e) {
e.printStackTrace();
}
// TODO - 삭제 예정
System.out.println("UserDTO By Username : " + userDTO.toString());
} catch (Exception e) {
e.printStackTrace();
}
return userDTO;
}
@Override
public List<UserDTO> getAllUsers() {
String sql = " select * from users ";
// 자료구조를 사용할 때 일단 생성 시키자.
List<UserDTO> list = new ArrayList<UserDTO>();
try (Connection conn = dataSource.getConnection()) {
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
ResultSet rs = pstmt.executeQuery();
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("UserList All : " + 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);
rowCount = pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return rowCount;
}
}
TodoController
package com.tenco.controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import com.tenco.model.TodoDAO;
import com.tenco.model.TodoDAOImpl;
import com.tenco.model.TodoDTO;
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;
import jakarta.servlet.http.HttpSession;
@WebServlet("/todo/*")
public class TodoController extends HttpServlet {
private static final long serialVersionUID = 1L;
private TodoDAO todoDAO;
public TodoController() {
todoDAO = new TodoDAOImpl();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String action = request.getPathInfo();
HttpSession session = request.getSession();
UserDTO principal = (UserDTO) session.getAttribute("principal");
if (principal == null) {
response.sendRedirect("/mvc/user/signIn?message=invalid");
return;
}
switch (action) {
case "/todoForm":
todoFormPage(request, response);
break;
case "/list":
todoListPage(request, response, principal.getId());
break;
case "/detail":
todoDetailPage(request, response, principal.getId());
break;
case "/delete":
deleteTodo(request, response, principal.getId());
break;
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
break;
}
}
/**
* todo 삭제 기능
*
* @param request
* @param response
* @param principalId
* @throws IOException
*/
private void deleteTodo(HttpServletRequest request, HttpServletResponse response, int principalId)
throws IOException {
try {
int todoId = Integer.parseInt(request.getParameter("id"));
todoDAO.deleteTodo(todoId, principalId);
} catch (Exception e) {
response.sendRedirect(request.getContextPath() + "/todo/list?error=invalid");
}
response.sendRedirect(request.getContextPath() + "/todo/list");
}
/**
* 상세 보기 화면
*
* @param request
* @param response
* @throws IOException
*/
private void todoDetailPage(HttpServletRequest request, HttpServletResponse response, int principalId) throws IOException {
try {
int todoId = Integer.parseInt(request.getParameter("id"));
TodoDTO dto = todoDAO.getTodoById(todoId);
if(dto.getUserId() == principalId) {
request.setAttribute("todo", dto);
request.getRequestDispatcher("/WEB-INF/views/todoDetail.jsp").forward(request, response);
} else {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script> alert('권한이 없습니다'); history.back(); </script>");
}
} catch (Exception e) {
response.sendRedirect(request.getContextPath() + "/todo/list?error=invalid");
}
}
/**
* 사용자별 todo 리스트 화면 이동
*
* @param request
* @param response
* @param principalId
* @throws IOException
* @throws ServletException
*/
private void todoListPage(HttpServletRequest request, HttpServletResponse response, int principalId)
throws IOException, ServletException {
List<TodoDTO> list = todoDAO.getTodosByUserId(principalId);
request.setAttribute("list", list);
request.getRequestDispatcher("/WEB-INF/views/todoList.jsp").forward(request, response);
}
/**
* todo 작성 페이지 이동
*
* @param request
* @param response
* @throws IOException
* @throws ServletException
*/
private void todoFormPage(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
request.getRequestDispatcher("/WEB-INF/views/todoForm.jsp").forward(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
UserDTO principal = (UserDTO) session.getAttribute("principal");
if (principal == null) {
response.sendRedirect(request.getContextPath() + "/user/signIn?error=invalid");
return;
}
String action = request.getPathInfo();
switch (action) {
case "/add":
addTodo(request, response, principal.getId());
break;
case "/update":
updateTodo(request, response, principal.getId());
break;
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
break;
}
}
/**
* todo 수정 기능
* @param request
* @param response
* @param principalId - 세션 ID 값
* @throws IOException
*/
private void updateTodo(HttpServletRequest request, HttpServletResponse response, int principalId) throws IOException {
try {
int todoId = Integer.parseInt(request.getParameter("id"));
String title = request.getParameter("title");
String description = request.getParameter("description");
String dueDate = request.getParameter("dueDate");
boolean completed = "on".equalsIgnoreCase(request.getParameter("completed"));
TodoDTO dto = TodoDTO.builder()
.id(todoId)
.userId(principalId)
.title(title)
.description(description)
.dueDate(dueDate)
.completed(String.valueOf(completed)).build();
todoDAO.updateTodo(dto, principalId);
} catch (Exception e) {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("<script> alert('잘못된 요청입니다'); history.back(); </script>");
}
// list 화면 재 요청 처리
response.sendRedirect(request.getContextPath() + "/todo/list");
}
/**
* 세션별 사용자 todo 등록
*
* @param request
* @param response
* @param principalId : 세션에 담겨 있는 UserId 값
* @throws IOException
*/
private void addTodo(HttpServletRequest request, HttpServletResponse response, int principalId) throws IOException {
String title = request.getParameter("title");
String description = request.getParameter("description");
String dueDate = request.getParameter("dueDate");
boolean completed = "on".equalsIgnoreCase(request.getParameter("completed"));
TodoDTO dto = TodoDTO.builder().userId(principalId).title(title).description(description).dueDate(dueDate)
.completed(String.valueOf(completed)).build();
todoDAO.addTodo(dto, principalId);
response.sendRedirect(request.getContextPath() + "/todo/list");
}
}
TodoDTO
package com.tenco.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class TodoDTO {
private int id;
private int userId;
private String title;
private String description;
private String dueDate;
private String completed; // "1" , "0"
// completed 를 데이터 변환 메서드를 만들어 보자.
public String completedToString() {
return this.completed.equals("1") ? "true" : "false";
}
}
TodoDAO
package com.tenco.model;
import java.util.List;
public interface TodoDAO {
void addTodo(TodoDTO dto, int principalId);
TodoDTO getTodoById(int id);
List<TodoDTO> getTodosByUserId(int userId);
List<TodoDTO> getAllTodos();
void updateTodo(TodoDTO dto, int principalId);
void deleteTodo(int id, int principalId);
}
TodoDAOImpl
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.naming.NamingException;
import javax.sql.DataSource;
public class TodoDAOImpl implements TodoDAO {
private DataSource dataSource;
public TodoDAOImpl() {
try {
InitialContext ctx = new InitialContext();
dataSource = (DataSource) ctx.lookup("java:comp/env/jdbc/MyDB");
} catch (NamingException e) {
e.printStackTrace();
}
}
@Override
public void addTodo(TodoDTO dto, int principalId) {
String sql = " INSERT INTO todos(user_id, title, description, due_date, completed) "
+ " VALUES(? , ? , ? , ? , ?) ";
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, principalId);
pstmt.setString(2, dto.getTitle());
pstmt.setString(3, dto.getDescription());
pstmt.setString(4, dto.getDueDate());
pstmt.setInt(5, dto.getCompleted() == "true" ? 1 : 0 );
pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
e.printStackTrace();
conn.rollback();
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public TodoDTO getTodoById(int id) {
String sql = " SELECT * FROM todos WHERE id = ? ";
TodoDTO dto = null;
try (Connection conn = dataSource.getConnection()) {
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
dto = new TodoDTO();
dto.setId(rs.getInt("id"));
dto.setUserId(rs.getInt("user_id"));
dto.setTitle(rs.getString("title"));
dto.setDescription(rs.getString("description"));
dto.setDueDate(rs.getString("due_date"));
dto.setCompleted(rs.getString("completed"));
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return dto;
}
@Override
public List<TodoDTO> getTodosByUserId(int userId) {
String sql = " SELECT * FROM todos WHERE user_id = ? ";
List<TodoDTO> todos = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setInt(1, userId);
try (ResultSet rs = pstm.executeQuery()) {
while (rs.next()) {
TodoDTO dto = new TodoDTO();
dto.setId(rs.getInt("id"));
dto.setUserId(rs.getInt("user_id"));
dto.setTitle(rs.getString("title"));
dto.setDescription(rs.getString("description"));
dto.setDueDate(rs.getString("due_date"));
dto.setCompleted(rs.getString("completed"));
todos.add(dto);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return todos;
}
@Override
public List<TodoDTO> getAllTodos() {
String sql = " SELECT * FROM todos ";
List<TodoDTO> todos = new ArrayList<>();
try (Connection conn = dataSource.getConnection()) {
PreparedStatement pstm = conn.prepareStatement(sql);
try (ResultSet rs = pstm.executeQuery()) {
while (rs.next()) {
TodoDTO dto = new TodoDTO();
dto.setId(rs.getInt("id"));
dto.setUserId(rs.getInt("user_id"));
dto.setTitle(rs.getString("title"));
dto.setDescription(rs.getString("description"));
dto.setDueDate(rs.getString("due_date"));
dto.setCompleted(rs.getString("completed"));
todos.add(dto);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
return todos;
}
@Override
public void updateTodo(TodoDTO dto, int principalId) {
// SELECT <-- 있지 없지 확인 과정 (필요)
String sql = " UPDATE todos SET title = ?, description = ? " + ", due_date = ?, completed = ? "
+ " WHERE id = ? and user_id = ? ";
try (Connection conn = dataSource.getConnection()) {
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, dto.getTitle());
pstmt.setString(2, dto.getDescription());
pstmt.setString(3, dto.getDueDate());
pstmt.setInt(4, dto.getCompleted() == "true" ? 1 : 0);
pstmt.setInt(5, dto.getId());
pstmt.setInt(6, dto.getUserId());
pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 삭제 기능
* id - Todos Pk
* principalId - 세션 ID
*/
@Override
public void deleteTodo(int id, int principalId) {
String sql = " DELETE FROM todos "
+ " WHERE id = ? "
+ " AND user_id = ? " ;
try (Connection conn = dataSource.getConnection()){
conn.setAutoCommit(false);
try (PreparedStatement pstmt = conn.prepareStatement(sql)){
pstmt.setInt(1, id);
pstmt.setInt(2, principalId);
pstmt.executeUpdate();
conn.commit();
} catch (Exception e) {
conn.rollback();
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
webapp/views/signUp.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원 가입</title>
<link rel="stylesheet" type="text/css" href="../css/styles.css">
</head>
<body>
<h2>회원가입</h2>
<!-- 에러 메세지 출력 -->
<%
// String errorMessage = (String)request.getAttribute("message");
String errorMessage = (String)request.getParameter("message");
if(errorMessage != null) {
%>
<p style="color:red"> <%=errorMessage%></p>
<% } %>
<!-- 절대 경로 사용 해보기 -->
<form action="/mvc/user/signUp" method="post">
<label for="username">사용자 이름 : </label>
<input type="text" id="username" name="username" value="야스오1">
<label for="password">비밀번호: </label>
<input type="password" id="password" name="password" value="1234">
<label for="email">이메일 : </label>
<input type="text" id="email" name="email" value="abc@nate.com">
<button type="submit">회원가입</button>
</form>
</body>
</html>
webapp/views/signIn.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>로그인</title>
</head>
<body>
<h2>로그인</h2>
<!-- 회원 가입 성공 메세지 출력 -->
<%
// String errorMessage = (String)request.getAttribute("message");
String success = (String)request.getParameter("message");
if(success != null) {
%>
<p style="color:blue"> <%=success%></p>
<% } %>
<!-- 절대 경로 사용 해보기 -->
<form action="/mvc/user/signIn" method="post">
<label for="username">사용자 이름 : </label>
<input type="text" id="username" name="username" value="야스오1">
<label for="password">비밀번호: </label>
<input type="password" id="password" name="password" value="1234">
<button type="submit">로그인</button>
</form>
</body>
</html>
webapp/views/todoFrom.jsp
<%@ 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> ToDo Page </h1>
<%-- http://localhost:8080/mvc/todo/add --%>
<form action="add" method="post">
<label for="title">제목: </label>
<input type="text" id="title" name="title" value="코딩연습 무한반복">
<br><br>
<label for="description">설명: </label>
<textarea rows="30" cols="50" id="description" name="description" >
그래야 성공하고 높은 연봉은 기본 ... 아니면 워라벨
</textarea>
<br><br>
<label for="dueDate">마감기한: </label>
<input type="date" id="dueDate" name="dueDate" value="2024-07-11">
<br><br>
<label for="completed">완료 여부: </label>
<input type="checkbox" id="completed" name="completed" ><br>
<button type="submit">추가</button>
</form>
<br><br>
<a href="list">목록으로 돌아가기</a>
</body>
</html>
webapp/views/todoList.jsp
<%@page import="java.util.Date"%>
<%@page import="com.tenco.model.TodoDTO"%>
<%@page import="java.util.ArrayList"%>
<%@page import="java.util.List"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>할 일 목록</title>
<link rel="stylesheet" type="text/css" href="../css/styles.css">
</head>
<body>
<%
List<TodoDTO> todoList = (List<TodoDTO>)request.getAttribute("list");
if (todoList != null && !todoList.isEmpty()) {
%>
<h2>할 일 목록</h2>
<a href="todoForm"> 새 할일 추가</a>
<table border="1">
<tr>
<th>제목</th>
<th>설명</th>
<th>마감일</th>
<th>완료 여부</th>
<th>(액션-버튼)</th>
</tr>
<%
for (TodoDTO todo : todoList) {
%>
<tr>
<td><%=todo.getTitle()%></td>
<td><%=todo.getDescription()%></td>
<td><%=todo.getDueDate()%></td>
<td><%=todo.completedToString() == "true" ? "완료" : "미완료"%></td>
<td><a href="detail?id=<%=todo.getId()%>">상세보기</a>
<form action="delete" method="get">
<input type="hidden" name="id" value="<%=todo.getId()%>">
<button type="submit">삭제</button>
</form></td>
</tr>
<%
}
%>
</table>
<%
} else {
%>
<hr>
<p>등록된 할 일이 없습니다.</p>
<%
}
%>
</body>
</html>
webapp/views/todoDetail.jsp
<%@page import="com.tenco.model.TodoDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>상세보기 화면</title>
</head>
<body>
<h2>상세 보기</h2>
<%
TodoDTO todo = (TodoDTO)request.getAttribute("todo");
if(todo != null) {
%>
<p>제목 : <%=todo.getTitle() %></p><br>
<p>설명 : <%=todo.getDescription() %></p><br>
<p>마감일 : <%=todo.getDueDate() %></p><br>
<p>완료여부 : <%=todo.completedToString() == "true" ? "완료" : "미완료" %></p><br>
<hr><br>
<form action="update" method="post">
<input type="hidden" name="id" value="<%=todo.getId() %>">
<label for="title">제목 : </label>
<input type="text" id="title" name="title" value="<%=todo.getTitle() %>">
<br>
<label for="description">설명 : </label>
<input type="text" id="description" name="description" value="<%=todo.getDescription() %>">
<br>
<label for="dueDate">마감일 : </label>
<input type="date" id="dueDate" name="dueDate" value="<%=todo.getDueDate() %>">
<br>
<label for="completed">완료여부 : </label>
<input type="checkbox" id="completed" name="completed" <%= todo.completedToString() == "true" ? "checked" : "" %> >
<br>
<button type="submit">수정</button>
</form>
<%
} else {
out.print("<p> 정보를 불러오는데 실패 </p>");
}
%>
</body>
</html>
'JSP' 카테고리의 다른 글
# 커스텀 태그( JSTL ) 라이브러리 사용, ( EL 표현식 ) (0) | 2024.08.02 |
---|---|
Dynamic Web Project 를 활용한 기본적인 CRUD JSP 게시판 제작 (0) | 2024.07.09 |
서블릿과 JSP의 개념과 차이점 (1) | 2024.07.05 |
쿠키와 세션 관리 (0) | 2024.07.04 |
폼 처리와 요청 방식 (0) | 2024.07.04 |