학습 목표
1. MVC 패턴을 사용해 보자.
2. context.xml 파일에 대한 개념을 살펴 보자.
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 패턴을 활용한 코드 설계
- 필요 라이브러리 확인

HikariCP 라이브러를 사용하기 위해서 다른 추가적인 라이브러리 설정이 필요하다.
HikariCP 및
https://wjdgns5.tistory.com/119
2024.05.27 Sprint Tool Suite lombok 적용
SpringToolSuite4에 lombok을 적용하는 방법 lombok 을 다운받는다.https://projectlombok.org/download Download projectlombok.org SpringToolSuite4에 들어와 폴더에 마우스 우클릭 후 lombok을 적용시킬 자바 프로젝트 폴
wjdgns5.tistory.com
https://wjdgns5.tistory.com/170
2024.06.17 HikariCP 라이브러를 설정 및 적용(1)
1. mvnrepository 홈페이지에 접속한다.https://mvnrepository.com/ 2. 검색창에 my connection java 를 검색한다. 밑에 버전 8.0.21 을 클릭한다. 8.0.21 버전 File에 jar(2.3MB)를 클릭한다. 다운받은 파일을 C 드
wjdgns5.tistory.com
https://wjdgns5.tistory.com/173
2024.06.17 HikariCP 라이브러를 설정 및 적용(2)
1. mvnrepository 홈페이지에 접속한다.https://mvnrepository.com/ 2. 검색창에 SLF4J API Module 를 검색 후 클릭한다. SLF4J API Module 밑에 2.0.0-alpha5 클릭 한다. Files에 jar (57KB)를 클릭한다. 다운받은 SLF4JAPI M
wjdgns5.tistory.com
https://wjdgns5.tistory.com/174
2024.06.17 HikariCP 라이브러를 설정 및 적용(3)
1. mvnrepository 홈페이지에 접속한다.https://mvnrepository.com/ 2. 검색창에 SLF4J Simple 를 검색 후 클릭한다. my_quiz 폴더를 우클릭 한다. New 를 클릭한 후 Foler 파일을 클릭한다. (폴더가 있다면
wjdgns5.tistory.com
https://wjdgns5.tistory.com/177
2024.06.17 HikariCP 라이브러를 설정 및 적용(4)
1. mvnrepository 홈페이지에 접속한다.https://mvnrepository.com/ 2. 검색창에 HikariCP 를 검색 후 클릭한다. 3. HikariCP 에서 버전 5.1.0을 클릭한다. 4. HikariCP >> 5.1.0 밑에서 Files 카테고리에 View All를 클릭
wjdgns5.tistory.com
기본 JDBC 를 활용한 클래스 설계
BasicDBUtil.java (사용X)
package com.tenco.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class BasicDBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/m_todo?serverTimezone=Asia/Seoul";
private static final String USER = "root";
private static final String PASSWORD = "asd123";
public static Connection getConnection() throws SQLException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("BasicDBUtil 에서 오류발생 함");
}
return DriverManager.getConnection(URL, USER, PASSWORD);
}
}
커넥션 풀을 사용하기 위한 DataSource 설계와 conext.xml 파일에 사용
context.xml 파일에 DataSource 설정 (HikariCP 사용 )
conext.xml
<?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">
</Resource>
</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 파일에서 정의된 이름과 일치해야 합니다.
InitialContext 객체의 역할
InitialContext 객체는 JNDI (Java Naming and Directory Interface) API의 기본 클래스 중 하나로, 애플리케이션이 네이밍 및 디렉토리 서비스를 사용할 수 있도록 해줍니다
테이블 설계
class_41.sql
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();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("UserDTO By Username : " + userDTO.toString());
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;
}
}
UserController (서블릿 클래스 상속)
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;
import com.tenco.model.UserDAO;
import com.tenco.model.UserDAOImpl;
// 주소설계
// http://localhost:8080/mvc/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 {
String action = request.getPathInfo();
System.out.println("action : " + action);
switch (action) {
case "/signIn":
// 로그인 페이지로 보내는 동작 처리
request.getRequestDispatcher("/views/signIn.jsp").forward(request, response);
break;
case "/signUp":
// 회원 가입 페이지로 보내는 동작 처리
request.getRequestDispatcher("/views/signUp.jsp").forward(request, response);
break;
default:
response.sendError(HttpServletResponse.SC_NOT_FOUND);
break;
}
}
// http://localhost:8080/mvc/views/todoForm.jsp
// http://localhost:8080/mvc/views/signUp.jsp
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
TestController (임시 코드 확인 )
package com.tenco.controller;
import java.io.IOException;
import java.util.List;
import com.tenco.model.UserDAO;
import com.tenco.model.UserDAOImpl;
import com.tenco.model.UserDTO;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@WebServlet("/test/*")
public class TestController extends HttpServlet {
private static final long serialVersionUID = 1L;
private UserDAO userDAO;
public TestController() {
super();
}
@Override
public void init(ServletConfig config) throws ServletException {
userDAO = new UserDAOImpl();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String action = request.getPathInfo();
switch (action) {
case "/byId":
// http://localhost:8080/mvc/test/byId
//userDAO.getUserById(1);
// userDAO.getUserByUsername("홍길동");
// userDAO.getAllUsers();
UserDTO dto = UserDTO.builder().password("999").email("h@naver.com").build();
int count = userDAO.updateUser(dto, 3);
System.out.println("count : " + count);
break;
default:
break;
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
'Java' 카테고리의 다른 글
| 2024.07.11 JSP 프로그래밍 기본 JSP와 MVC 패턴 Todo 프로젝트(3) (0) | 2024.07.11 |
|---|---|
| 2024.07.10 JSP 프로그래밍 기본 JSP와 MVC 패턴 Todo 프로젝트(2) (3) | 2024.07.10 |
| 2024.07.08 JSP 프로그래밍 기본 게시판 만들어 보기 - 1 단계 (미완) (0) | 2024.07.08 |
| 2024.07.05 JSP 프로그래밍 기본 서블릿과 JSP의 개념과 차이점 (0) | 2024.07.05 |
| 2024.07.04 JSP 프로그래밍 기본 쿠키와 세션 관리 (0) | 2024.07.04 |