주니어 기초 코딩공부/JSP 기초

[JSP] 웹 어플리케이션의 일반적인 구성 / 데이터 접근 객체의 구현 feat. mySQL 계정 생성, 미니 방명록 만드는 구성의 흐름 및 의식의 흐름....

jju_developer 2023. 1. 26. 20:39
728x90

안녕하세요 오늘은 웹 애플리케이션의 일반적인 구성 및 데이터 접근 객체를 구현하여 간단한 

방명록을 어떻게 만드는지를 보겠습니다.

정말 간단한 방명록을 만드는 구성의 흐름을 맛보겠습니다.

웹 어플리케이션의 일반적인 구성

 JSP만을 이용하는 경우의 문제
• 동일한 로직을 수행하는 코드가 중복될 가능성이 높음
• 기능 변경 발생 시 여러 코드에 동일한 수정 반영해 주어야 함
• 누락될 가능성 발생 → 버그 발생 가능성 높음

그렇기 때문에 공통된 부분을 자바 클래스로 만들어서 활용하자는 뜻입니다.

 

 클래스를 이용한 중복 제거
• 클래스를 이용해서 중복된 코드를 한 곳으로 분리
• 화면 요청 처리하는 JSP와 실제 로직을 수행하는 클래스로 분리하는 것이 일반적인 구성

 

웹 애플리케이션의 주요 구성 요소

 

MVC 프레임워크

model view control

즉, jsp는 로직을 쓰지 않고 뷰로써의 역할을 한다는 것입니다.

mvc 패턴 구현 방법

현재는 Control 부분을 servlet을 활용하고 있는데

Spring을 배우고 난 후, 서블랫을 스프링으로 변경할 예정입니다.

 

 

DAO : 데이터 접근 객체(Data Access Object)의 구현

 CRUD를 위한 메서드 정의
• insert() 메서드 - INSERT 쿼리를 실행한다.
• select() 메서드 - SELECT 쿼리를 실행한다. 검색 조건에 따라서 한 개 이상의 select() 메서드를 제공한다.
• update() 메서드 - UPDATE 쿼리를 실행한다.
• delete() 메서드 - DELETE 쿼리를 실행한다.


 테이블과 매핑될 클래스(Java Bean) 작성

// DAO 클래스의 insert() 메서드 작성 예
public void insert(Connection conn, Message message) throws SQLException {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement("insert into message values (?, ?, ?)");
pstmt.setInt(1, message.getId());
pstmt.setString(2, message.getGuestname());
...
return pstmt.executeUpdate();
} finally {
if (pstmt != null) try { pstmt.close(); } catch (SQLException ex) {}
}
}

예제

1. 권한 생성

2. geustbook으로 접근

 

 

 

3. guest book에서 테이블을 생성합니다.

 

4. web.xml에서 연동 여부 확인

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
		http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
	version="3.1">

	<servlet>
		<servlet-name>DBCPInit</servlet-name>
		<servlet-class>jdbc.DBCPInit</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

</web-app>

 

package jdbc;

import java.sql.DriverManager;
import java.util.Properties;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;

import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
import org.apache.commons.dbcp2.PoolableConnectionFactory;
import org.apache.commons.dbcp2.PoolingDriver;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class DBCPInit extends HttpServlet {

	@Override
	public void init() throws ServletException {
		loadJDBCDriver();
		initConnectionPool();
	}

	private void loadJDBCDriver() {
		try {
			Class.forName("com.mysql.jdbc.Driver");
		} catch (ClassNotFoundException ex) {
			throw new RuntimeException("fail to load JDBC Driver", ex);
		}
	}

	private void initConnectionPool() {
		try {
			String jdbcUrl = 
					"jdbc:mysql://localhost:3306/guestbook?" + 
					"useUnicode=true&characterEncoding=utf8";
			String username = "jspexam";
			String pw = "jsppw";

			ConnectionFactory connFactory = 
					new DriverManagerConnectionFactory(jdbcUrl, username, pw);

			PoolableConnectionFactory poolableConnFactory = 
					new PoolableConnectionFactory(connFactory, null);
			poolableConnFactory.setValidationQuery("select 1");

			GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
			poolConfig.setTimeBetweenEvictionRunsMillis(1000L * 60L * 5L);
			poolConfig.setTestWhileIdle(true);
			poolConfig.setMinIdle(4);
			poolConfig.setMaxTotal(50);

			GenericObjectPool<PoolableConnection> connectionPool = 
					new GenericObjectPool<>(poolableConnFactory, poolConfig);
			poolableConnFactory.setPool(connectionPool);
			
			Class.forName("org.apache.commons.dbcp2.PoolingDriver");
			PoolingDriver driver = 
					(PoolingDriver) DriverManager.getDriver("jdbc:apache:commons:dbcp:");
			driver.registerPool("guestbook", connectionPool);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

 

 

5. 자바빈

사실 이름은 저렇게 다르면 안 되고 동일해야 합니다.

칼럼과 클래스 이름은 수동으로 맵핑시키니까 지금은 괜찮아도 나중에는 안되니, 꼭 변경해주셔야 합니다.

 

즉, 데이터베이스에 있는 테이블의 객체들을 다 클래스에 정의하는 것입니다.

 

이게 테이블이 맵핑되는 자바빈 객체입니다.

 

6. MessageDao 클래스 구현

DAO data access object

 MessageDao 클래스는 guestbook_message 테이블에 대한 CRUD 메서드를 제공합니다.

package guestbook.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import guestbook.model.Message;
import jdbc.JdbcUtil;

public class MessageDao {
	private static MessageDao messageDao = new MessageDao();
	public static MessageDao getInstance() {
		return messageDao;
	}
	
	private MessageDao() {}
	
	public int insert(Connection conn, Message message) throws SQLException {
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(
					"insert into guestbook_message " + 
					"(guest_name, password, message) values (?, ?, ?)");
			pstmt.setString(1, message.getGuestName());
			pstmt.setString(2, message.getPassword());
			pstmt.setString(3, message.getMessage());
			return pstmt.executeUpdate();
		} finally {
			JdbcUtil.close(pstmt);
		}
	}

	public Message select(Connection conn, int messageId) throws SQLException {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = conn.prepareStatement(
					"select * from guestbook_message where message_id = ?");
			pstmt.setInt(1, messageId);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				return makeMessageFromResultSet(rs);
			} else {
				return null;
			}
		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(pstmt);
		}
	}

	private Message makeMessageFromResultSet(ResultSet rs) throws SQLException {
		Message message = new Message();
		message.setId(rs.getInt("message_id"));
		message.setGuestName(rs.getString("guest_name"));
		message.setPassword(rs.getString("password"));
		message.setMessage(rs.getString("message"));
		return message;
	}

	public int selectCount(Connection conn) throws SQLException {
		Statement stmt = null;
		ResultSet rs = null;
		try {
			stmt = conn.createStatement();
			rs = stmt.executeQuery("select count(*) from guestbook_message");
			rs.next();
			return rs.getInt(1);
		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(stmt);
		}
	}

	public List<Message> selectList(Connection conn, int firstRow, int endRow) 
			throws SQLException {
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			pstmt = conn.prepareStatement(
					"select * from guestbook_message " + 
					"order by message_id desc limit ?, ?");
			pstmt.setInt(1, firstRow - 1);
			pstmt.setInt(2, endRow - firstRow + 1);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				List<Message> messageList = new ArrayList<Message>();
				do {
					messageList.add(makeMessageFromResultSet(rs));
				} while (rs.next());
				return messageList;
			} else {
				return Collections.emptyList();
			}
		} finally {
			JdbcUtil.close(rs);
			JdbcUtil.close(pstmt);
		}
	}

	public int delete(Connection conn, int messageId) throws SQLException {
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(
					"delete from guestbook_message where message_id = ?");
			pstmt.setInt(1, messageId);
			return pstmt.executeUpdate();
		} finally {
			JdbcUtil.close(pstmt);
		}
	}

}

여기서 중요한 부분만 보겠습니다.

 

그 MessageDao를 보면 생성자가 provate으로 되어있습니다.

즉, 다른 클래스에서 객체를 만들 수 없습니다.

그렇기 때문에 생성자를 만들어 객체를 자기 필드(messageDao)에 넣었습니다.

그 변수도 private으로 되어있습니다.

싱글톤 패턴 DAO

여기서 특이한 점은 getInstance()는 public static으로 선언되었습니다.

 

즉, static은 객체를 생성하지 않고 이름으로 바로 호출할 수 있죠? (싱글톤 개발 패턴)

 

dao는 여러 개 만들 필요 없이 하나로만 만들어 싱클톤 개발 패턴으로 주로 개발합니다.

 

쭉 자바 코드를 보면 select, insert, delete 부분을 볼 수 있습니다.

 

 

7. 서비스 클래스의 구현

[chap15/src/guestbook/service/GetMessageListService.java]

 

서비스 클래스

package guestbook.service;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;

import guestbook.dao.MessageDao;
import guestbook.model.Message;
import jdbc.JdbcUtil;
import jdbc.connection.ConnectionProvider;

public class GetMessageListService {
	private static GetMessageListService instance = new GetMessageListService();

	public static GetMessageListService getInstance() {
		return instance;
	}

	private GetMessageListService() {
	}

	private static final int MESSAGE_COUNT_PER_PAGE = 3;

	public MessageListView getMessageList(int pageNumber) {
		Connection conn = null;
		int currentPageNumber = pageNumber;
		try {
			conn = ConnectionProvider.getConnection();
			MessageDao messageDao = MessageDao.getInstance();

			int messageTotalCount = messageDao.selectCount(conn);

			List<Message> messageList = null;
			int firstRow = 0;
			int endRow = 0;
			if (messageTotalCount > 0) {
				firstRow =
						(pageNumber - 1) * MESSAGE_COUNT_PER_PAGE + 1;
				endRow = firstRow + MESSAGE_COUNT_PER_PAGE - 1;
				messageList =
						messageDao.selectList(conn, firstRow, endRow);
			} else {
				currentPageNumber = 0;
				messageList = Collections.emptyList();
			}
			return new MessageListView(messageList,
					messageTotalCount, currentPageNumber,
					MESSAGE_COUNT_PER_PAGE, firstRow, endRow);
		} catch (SQLException e) {
			throw new ServiceException("목록 구하기 실패: " + e.getMessage(), e);
		} finally {
			JdbcUtil.close(conn);
		}
	}
}

얘도 싱글톤으로 생성되어 있습니다.

 

 

ConnectionProvider는

여기에 커넥션을 지정해 주었습니다.

 

여기서 selectCount로 conn객체를 넘겼는데 이 부분을 볼까요?

select를 하라고 지정해 둔 쿼리가 있습니다.

현재 리스트가 비어있으니까 null로 빈 메시지 리스트가 만들어집니다.

 

 

8. list.jsp 실행

list.jsp

<%@ page contentType="text/html; charset=utf-8" %>
<%@ page import="guestbook.model.Message"%>
<%@ page import="guestbook.service.MessageListView"%>
<%@ page import="guestbook.service.GetMessageListService"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%
	String pageNumberStr = request.getParameter("page");
	int pageNumber = 1;
	if (pageNumberStr != null) {
		pageNumber = Integer.parseInt(pageNumberStr);
	}

	GetMessageListService messageListService = 
			GetMessageListService.getInstance();
	MessageListView viewData = 
			messageListService.getMessageList(pageNumber);
%>
<c:set var="viewData" value="<%= viewData %>"/>
<html>
<head>
	<title>방명록 메시지 목록</title>
</head>
<body>

<form action="writeMessage.jsp" method="post">
이름: <input type="text" name="guestName"> <br>
암호: <input type="password" name="password"> <br>
메시지: <textarea name="message" cols="30" rows="3"></textarea> <br>
<input type="submit" value="메시지 남기기" />
</form>
<hr>
<c:if test="${viewData.isEmpty()}">
등록된 메시지가 없습니다.
</c:if>

<c:if test="${!viewData.isEmpty()}">
<table border="1">
	<c:forEach var="message" items="${viewData.messageList}">
	<tr>
		<td>
		메시지 번호: ${message.id} <br/>
		손님 이름: ${message.guestName} <br/>
		메시지: ${message.message} <br/>
		<a href="confirmDeletion.jsp?messageId=${message.id}">[삭제하기]</a>
		</td>
	</tr>
	</c:forEach>
</table>

<c:forEach var="pageNum" begin="1" end="${viewData.pageTotalCount}">
<a href="list.jsp?page=${pageNum}">[${pageNum}]</a> 
</c:forEach>

</c:if>
</body>
</html>

 

 

첫 메시지 남기기

 

3개 메시지 등록
삭제하기

 

코드설명

 

c 태그 쓰려면 우선

jar을 넣어야 하고

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

상단에 이 부분이 있어야 합니다.

여기서 이부분을 볼까요?

포스트 방식으로 writeMessage로 넘겼습니다. 그렇다면 writeMessage에는 어떤 내용이 있을까요?

writeMessage 코드

 


순서정리

1. lsit.jsp 에서 실행하여 폼이 만들어진 곳에 데이터를 입력한다.

 

2. writeMessage.jsp

각각의 메시지를 가져온다,

메시지 객체 가져옴

 

3. 서비스로 이동하여 write(message)를 전달

 

 

4. Connection 풀에서 커넥션을 얻고,

messageDao 객체를 얻어옵니다.

자기가 가져온 객체들을 DAO로 넘깁니다.

 

5. MessageDao에 각각에 가져온 객체를 이름이 같은 변수를 찾아서 거기에 넣어줍니다.

 

6. DB에 가서 guestbook_message에 insert가 끝난 것으로 body에 있는 방명록에 메시지를 남겼습니다를 출력합니다.

 

 

 

 

정말 장황한 글이 되었는데....

오늘은 어떻게 방명록에 데이터를 입력하면 어느 jsp 파일로 가는지

흐름을 하나하나 살펴보았는데요!

 

글이 너무 장황하지만.. 반복하면서 계속 본다면

부디...

이해할 수 있기를 빌겠습니다~ㅠ.ㅠ

 

그럼 오늘도 수고하셨습니다~!

 

728x90