본문 바로가기
Java Web Programming/4. JSP

[JSP] DBCP + Model2 MVC Pattern (+ 각 객체 개념설명)

by 파프리카_ 2020. 9. 14.
728x90
반응형

[ DBCP ]

DBCP ⇒  <<Interface>> javax.sql.DataSource

 

: DataBase Connection Pool 

: DB와 커넥션을 맺고 있는 객체를 관리하는 역할 ( MVC 중 Model에 적용된다 )
⇒ 데이터베이스 연동 시, connection을 생성하고 소멸시키는 것이 아니라 (JDBC interface), 

1) 미리 생성한 connection (connection pool)을 2) 빌려오고 3) 반납받는 형식으로 시스템 성능 향상을 위해 사용!

 

JDBC와 같이 DB와 연동되어 사용하는 역할을 한다. 하지만 JDBC보다 DBCP가 더 효율적으로 사용수 있어서 DBCP를 사용한다.

JDBC의 개발단계는 다음과 같다.

  1. Driver loading
    : 사용하는 데이터 베이스에서 지원하는 드라이버(ex.ojdbc.jar)를 로딩하여,
      데이터베이스와 java 어플리케이션 연동을 위한 기본정보를 메모리에 적재한다.
    : class loading 단계에서 driver도 loading해주는 작업
    → Class.forName ([driver path])
  2. Connection(연결)
    : 데이터베이스와 연결하고, 정보를 반환받아 Connection 객체에 저장한다.
    : Connection 객체에 속한 메서드로 SQL을 실행할 수 있다. 
    → Connection con = DriverManager.getConnection([db url], [sql id], [sql password])
  3. Statement/PreparedStatement
    : 데이터베이스와 소통하기 위한 SQL을 실행할 수 있는 메서드를 지원한다.

  4. SQL 실행 
    1) select의 경우 (ResultSet에 결과 값 할당)
    →  ResultSet rs = pstmt.executeQuery()

    2) insert, delete, update 의 경우
    → pstmt.executeUpdate()
  5. close(Connection, Statement, ResultSet)
    : 역순으로 닫아주기

> JDBC에 대한 자세한 개념 설명 → https://creamilk88.tistory.com/57

 

JDBC를 사용할 때에는,

1, 2번 단계 (driver loading, connectiong)를 반복적으로 실행하고,  코드에 작성해줘야하는 번거로움이 있다.

또한 connection을 생성하고, connection을 닫아주는 (2번, 5번 단계) 과정에서 가장 많은 자원이 소모된다.

 

 

이를 해결하기 위해 DBCP를 사용한다.

아래와 같은 방법을 통해 JDBC의 1,2,5번째 단계를 메모리 관점에서 효율적이고, 코드 작성 관점에서 간단하게 사용이 가능하다.

Connection Pool이라는 공간을 별도로 미리 생성하여,  

만들어 둔 connection pool에서 커넥션을 빌려오고 반납하는 형식으로 이용하여 효율성을 높인다. (생성, 소멸 X)

 

[ Connection Pool 구조 ]

 

출처 : 한국데이터상업진흥원 (https://www.kdata.or.kr/info/info_04_view.html?field=&keyword=&type=techreport&page=18&dbnum=183740&mode=detail&type=techreport)

 

Connection pool 이론에 대한 자세한 설명 링크 → https://www.kdata.or.kr/info/info_04_view.html?field=&keyword=&type=techreport&page=18&dbnum=183740&mode=detail&type=techreport

 

데이터 기술 동향 < 정보마당 - 한국데이터산업진흥원

Connection pool ㈜엑셈 컨설팅본부 /APM팀 박 종현 Connection pool 이란 ? 사용자의 요청에 따라 Connection 을 생성하다 보면 많은 수의 연결이 발생했을 때 서버에 과부하가 걸리게 된다 . 이러한 상황을 ��

www.kdata.or.kr


Connection Pool

: DataBase Connection Pool 객체를 생성해 공유하는 클래스

 

DBCP

 시스템 성능 향상을 위해 DB connection을 생성, 소멸시키는 것이 아니라,

      미리 생성한 connection을 빌려주고 반납하도록 한다.

     (아래 코드에서는 DataSourceManager 클래스에서 인스턴스 변수인 dataSource가 DBCP 역할을 한다)

 

javax.sql.DataSource Interface 타입으로 DBCP를 관리한다.

그 이유는 WAS가 변경되면 DBCP도 변경될 수 있으므로, 추상화된 인터페이스 타입으로 관리하는 것이 유지보수에 유리하다.

 

/DataSourceManager.java

package org.kosta.model;

import javax.sql.DataSource;

import org.apache.tomcat.dbcp.dbcp2.BasicDataSource;

public class DataSourceManager {
	private static DataSourceManager instance = new DataSourceManager();
	
	private DataSource dataSource;
	
	private DataSourceManager() {
		//was tomcat에서 제공하는 dbcp를 생성해서 사용한다
		BasicDataSource dbcp = new BasicDataSource();
		
		//dbcp(datasource)에 driver와 db connection 정보를 설정
		dbcp.setDriverClassName("oracle.jdbc.OracleDriver");
		dbcp.setUrl("jdbc:oracle:thin:@127.0.0.1:1521:xe");
		dbcp.setUsername("scott");
		dbcp.setPassword("tiger");
		
		//setInitialSize( ) : 처음 로드될 떄 생성할 connection 개수 설정
		dbcp.setInitialSize(3);
		
		//setMaxTotal( ) : 사용할 최대 connection 개수 설정
		dbcp.setMaxTotal(10);
		
		//인스턴스 변수인 dataSource에 위를 통해 설정한 dbcp를 할당한다
		this.dataSource = dbcp;
	}
	public static DataSourceManager getInstance() {
		return instance;
	}
	
	public DataSource getDataSource() {
		return dataSource;
	}
}

 


[ 프로그램 진행 원리 및 개요 ]

DBCP + Model2 설계방식 ( + MVC / Singleton / Front Controller Pattern 적용)

WAS(Tomcat)에서 제공하는 DBCP를 이용해서 현재 웹 어플리케이션에 적용해본다.

 

 

1번 기능 : carNO로 차 상세 정보 검색 기능

index.jsp -- command=findCarByNo -- FindCarByNoController -- CarDAO2

                                                                                                        ㅣ                        findCarByNo(String no) : CarDTO

                                                                                                        ㅣ forward 방식

                                                                                                        ㅣ

                                                                                      ㅣ                                ㅣ

                                                                           findcar-ok.jsp                findcar-fail.jsp

                                                                         : 차 정보 제공                       : 차 정보가 없습니다 alert후 index.jsp로 이동

                                                                         (carNO,  model, price)

 


2번 기능 : 모든 차 데이터의 정보 리스트로 읽어오는 기능

index.jsp -- command=getAllCarList -- GetAllCarListController -- CarDAO2

                                                                                                        ㅣ                        getAllCarList() : ArrayList<CarDTO>

                                                                                                        ㅣ forward 방식

                                                                                                        ㅣ

                                                                                                car-list.jsp

                                                                                                table 형식으로 carNo와 model만 리스트로 제공

 


[ 전체 구현 코드 ] 

DB 

> MVC_CAR 테이블 및 시퀀스 생성

CREATE TABLE MVC_CAR (
    car_no  NUMBER        PRIMARY KEY,
    model   VARCHAR2(100) NOT NULL,
    price   NUMBER        NOT NULL
)

CREATE SEQUENCE MVC_CAR_SEQ;

INSERT INTO MVC_CAR(car_no, model, price) VALUES (MVC_CAR_SEQ.nextval, 'K5', 2000);
INSERT INTO MVC_CAR(car_no, model, price) VALUES (MVC_CAR_SEQ.nextval, 'K7', 2500);
INSERT INTO MVC_CAR(car_no, model, price) VALUES (MVC_CAR_SEQ.nextval, 'K9', 3000);

 

> MVC_CAR TABLE


Model

[ 데이터 계층간 데이터 교환을 위한 자바빈즈 ]

VO : Value Object (read-only)

DTO : Data Transfer Object

 

[ 데이터 연동 로직 ]

DAO : Data Acess Object

 

/CarDTO.java

package org.kosta.model;

public class CarDTO {
	private String carNO;
	private String model;
	private int price;
	
	
	public CarDTO() {
		super();
	}

	public CarDTO(String carNO, String model, int price) {
		super();
		this.carNO = carNO;
		this.model = model;
		this.price = price;
	}

	public String getCarNO() {
		return carNO;
	}

	public void setCarNO(String carNO) {
		this.carNO = carNO;
	}

	public String getModel() {
		return model;
	}

	public void setModel(String model) {
		this.model = model;
	}

	public int getPrice() {
		return price;
	}

	public void setPrice(int price) {
		this.price = price;
	}
	
}

 

/CarDAO2.java

1) DAO에 Singleton Design Pattern을 적용한다.

    : 시스템 상에서 단 한번 객체 생성을 하고 여러 곳에서 공유해서 사용하도록 한다.

2) DBCP를 이용해 driver loading & connection pool을 생성한다.

   : connection pool은 DataSourceManager class의 dataSourse을 통해 생성하며,

     connection을 빌려오고 반납하는 역할을 한다.

package org.kosta.model;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

import javax.sql.DataSource;

public class CarDAO2 {

	private static CarDAO2 instance = new CarDAO2();
	
	//DBCP에서 connection pool을 이용한다
	private DataSource dataSource;
	
	// 생성자에 private를 명시하여 외부해서 생성하지 못하게 막는다
	private CarDAO2() {
		// DBCP를 통해 driver loading & connection pool 생성
		this.dataSource = DataSourceManager.getInstance().getDataSource();
	}
	
	// public static 메서드로 외부에 객체 생성을 공유한다.
	public static CarDAO2 getInstance() {
		return instance;
	}
	
	//closeAll(rs, pstmt, con) method 
	public void closeAll(ResultSet rs, PreparedStatement pstmt, Connection con) 
			throws SQLException {
		if (rs != null)
			rs.close();
		if (pstmt != null)
			pstmt.close();
		// Connection을 dbcp에 반납한다(소멸하는 것이 아니다!)
		if (con != null)
			con.close();
	}
	
	/* 1번 기능: carNO으로 차 상세정보 검색
	 * findCarByNo method
	 */
	public CarDTO findCarByNo(String no) throws SQLException {
		CarDTO carDTO = null;
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			//dbcp로 부터 커넥션을 빌려온다 (생성하는 것이 아님!)
			con = dataSource.getConnection();
			
			String sql = "SELECT model, price FROM mvc_car WHERE car_no=?";
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, no);
			rs = pstmt.executeQuery();
			if (rs.next())
				carDTO = new CarDTO(no, rs.getString(1), rs.getInt(2));
		} finally {
			closeAll(rs, pstmt, con);
		}
		
		return carDTO;
	}
	
	/* 2번 기능: 모든 차 리스트 테이블로 조회
	 * getAllCarList method
	 */
	public ArrayList<CarDTO> getAllCarList() throws SQLException {
		ArrayList<CarDTO> list = new ArrayList<CarDTO>();
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		
		try {
			//dataSource에서 connection을 빌려온다
			con = dataSource.getConnection();
			
			String sql = "SELECT car_no, model FROM mvc_car";
			pstmt = con.prepareStatement(sql);
			rs = pstmt.executeQuery();
			while(rs.next()) {
				CarDTO carDTO = new CarDTO();
				carDTO.setCarNO(rs.getString("car_no"));
				carDTO.setModel(rs.getString("model"));
				list.add(carDTO);
			}
		} finally {
			closeAll(rs, pstmt, con);
		}
		
		return list;
	}
	
}//class

 


View

/index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DBCP TEST</title>
</head>
<body>
	<form action="front">
		<input type="hidden" name="command" value="findCarByNo">
		차번호 <input type="number" name="carNo">
		<input type="submit" value="검색"> 
	</form>
	<br><br>
	<a href="front?command=getAllCarList">자동차 리스트보기</a>
</body>
</html>

 

/error.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 bgcolor="lime">
	예외가 발생하였습니다.<br>
	콘솔창을 확인해주세요.
</body>
</html>

--  1번 기능 : 아이디를 통해 차 상세정보 검색하기

 

/findcar-ok.jsp

<%@page import="org.kosta.model.CarDTO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>차 상세정보</title>
</head>
<body>
	<% CarDTO carDTO = (CarDTO) request.getAttribute("carDTO"); %>
	차번호 : <%=carDTO.getCarNO() %><br>
	모델명 : <%=carDTO.getModel() %><br>
	가격 : <%=carDTO.getPrice() %> <br>
</body>
</html>

 

/findcar-fail.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>fail</title>
</head>
<body>
	<script type="text/javascript">
		alert("입력하신 아이디에 해당하는 차 정보가 없습니다");
		location.href = "index.jsp";
	</script>
</body>
</html>

 


--  2번 기능 : DB에 있는 모든 차 정보 테이블 형태로 읽어오기

 

/car-list.jsp

<%@page import="org.kosta.model.CarDTO"%>
<%@page import="java.util.ArrayList"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>car list</title>
</head>
<body>
<%	
	@SuppressWarnings("unchecked")
	ArrayList<CarDTO> list 
		= (ArrayList<CarDTO>) request.getAttribute("list"); 
%>
	<table border=2>
		<thead>
			<tr>
				<th>CarNo</th>
				<th>model</th>				
			</tr>
		</thead>
		
		<tbody>
			<% for(int i=0; i<list.size(); i++){ %>
			<tr>
				<th><%=list.get(i).getCarNO() %></th>
				<th><%=list.get(i).getModel() %></th>
			</tr>
			<% } %>
		</tbody>
	</table>
</body>
</html>

 


Controller

/controller.java

→ 개별 Controller 객체를 표준화하는 interface

: 컨트롤러 구현체를 사용하는 측에서 단일한 방법 (추상메서드) 으로

다양한 컨트롤러 구현체를 실행하게 할 수 있도록 계층구조를 형성하는 역할

package org.kosta.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Controller {
	public String execute(HttpServletRequest request, HttpServletResponse response) 
		throws Exception;
}

 

/ HandlerMapping.java

컨트롤러 객체 생성을 전담하는 Factory 객체

DispatcherSerlvet과 개별 Controller들과의 결합도를 낮추는 역할

package org.kosta.controller;
/*
 * 컨트롤러 객체 생성을 전담하는 Factory 객체
 * DispatcherSerlvet과 개별 Controller들과의 결합도를 낮추는 역할
 */
public class HandlerMapping {
	private static HandlerMapping instance = new HandlerMapping();
	private HandlerMapping() { }
	public static HandlerMapping getInstance() {
		return instance;
	}
	
	public Controller create(String command) {
		Controller controller = null;
		if (command.contentEquals("findCarByNo"))
			controller = new FindCarByNoController();
			
		return controller;
	}
}

 

/DispatcherServlet.java

handleRequest method 작동 방식

1. 에러(예외) 발생 시, 콘솔에 메세지 발생 경로 출력 후,  error.jsp로  redirect 방식으로 view로 이동한다.

2. 클라이언트가 전송한 command 정보를 받는다.

3. HandlerMapping 을 이용해 개별 컨트롤러 객체를 컨트롤러 인터페이스 타입으로 반환받는다.

4. 개별 컨트롤러를 실행한다.

5. 실행 후 반환된 String url 정보를 이용해,

     forward 방식 or redirect 방식으로 view로 이동해 응답하게 한다.

package org.kosta.controller;

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;

/**
 */
@WebServlet("/front")
public class DispatcherServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;

	// doGet method
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		this.handleRequest(request, response);
	}

	// doPost method
	protected void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// POST 방식 한글처리
		request.setCharacterEncoding("utf-8");
		this.handleRequest(request, response);
	}

	// handle Request method
	protected void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		try {
			// 2. 클라이언트가 전송한 command 정보를 받는다.
			String command = request.getParameter("command");

			// 3. HandlerMapping 을 이용해 개별 컨트롤러 객체를 컨트롤러 인터페이스 타입으로 반환받는다.
			Controller controller = HandlerMapping.getInstance().create(command);
			
			// 4. 개별 컨트롤러를 실행하여, url을 반환받는다.
			String url = controller.execute(request, response);
			
			// 5. 실행 후 반환된 String url 정보를 이용해,
			// forward 방식 or redirect 방식으로 view로 이동해 응답하게 한다.
			if (url.startsWith("redirect:"))
				response.sendRedirect(url.substring(9));
			else
				request.getRequestDispatcher(url).forward(request, response);
			
		} catch (Exception e) {
			// 1. exception 발생 시, 콘솔에 에러 메세지 표시 후,
			// error.jsp로 redirect 방식으로 view에 띄워주기
			e.printStackTrace();
			response.sendRedirect("error.jsp");
		}
	}
}

 


--1번 기능 : 아이디를 통해 차 상세정보 검색 controller

 

/FindCarByNoController.java

package org.kosta.controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kosta.model.CarDAO;
import org.kosta.model.CarDTO;

public class FindCarByNoController implements Controller {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) 
			throws Exception {
		String url = null;
		String carNo = request.getParameter("carNo");
		
		CarDTO carDTO = CarDAO.getInstance().findCarByNo(carNo);
		
		if (carDTO == null)
			url = "findcar-fail.jsp";
		else {
			url = "findcar-ok.jsp";
			request.setAttribute("carDTO", carDTO);
		}
		return url;
	}

}

--2번 기능 : DB에 있는 모든 행의 정보 list형식으로 받아서 view(jsp)에 전송해주는 기능 controller

 

/GetAllCarListController.java

package org.kosta.controller;

import java.util.ArrayList;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.kosta.model.CarDAO2;
import org.kosta.model.CarDTO;

public class GetAllCarListController implements Controller {

	@Override
	public String execute(HttpServletRequest request, HttpServletResponse response) 
			throws Exception {
		ArrayList<CarDTO> list = CarDAO2.getInstance().getAllCarList(); 
		request.setAttribute("list", list);
		return "car-list.jsp";
	}

}

 

 


 ※ JDBC를 이용한 방식(기존 방식)과 DBCP를 이용한 방식(connection pool이용) 의 차이

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" import="java.sql.*, javax.sql.*, org.kosta.model.*" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DBCP test</title>
</head>
<body>
	1. connection을 생성 소멸하는 기존 방식의 커넥션<br>
	<% 
		String url = "jdbc:oracle:thin:@127.0.0.1:1521:xe";
		// Connection 생성
		Connection con = DriverManager.getConnection(url, "scott", "tiger");
	%>
	<%= con.getClass() %>
	<% con.close(); //connection 소멸 %> 
	<br><br>
	2. connection을 connection pool에 미리 생성하고, 
	반납하는 DBCP를 이용한 커넥션<br>
	<%
		//DataSource Interface 사용
		DataSource dataSource = DataSourceManager.getInstance().getDataSource();
		// Connection을 DBCP로부터 빌려옴
		Connection dbcpCon = dataSource.getConnection();
	%>
	<%= dbcpCon.getClass() %>
	<% dbcpCon.close(); //connection 반납 %>
</body>
</html>


브라우저 결과 화면

 

 

728x90
반응형