커넥션 풀(DBCP : DataBase Connection Pool)
Connection Pool 등장 배경
- 기본 데이터베이스 연동 방법의 문제점
- 애플리케이션에서 데이터베이스 연결 과정에서 시간이 많이 소요
- 해결 방안
- 애플리케이션 실행 시 미리 Connection 객체를 생성해놓고, 미리 데이터베이스를 연결해놓음
- 애플리케이션은 데이터베이스 연동 작업 발생 시 미리 생성되어있는 Connection 객체를 이용해서 작업
커넥션 풀
- 일정량의 DB Connection 객체를 Pool에 저장해 두고 클라이언트 요청이 있을 때마다 가져다 사용하고 반환
- 클라이언트에서 다수의 요청이 발생될 경우 요청마다 DB Connection 객체를 생성하게 되면 데이터베이스에 부하가 발생하기 때문에 커넥션 풀 기법을 사용
- JDBC를 통하여 DB에 연결하게 되면 사용자의 요청이 있을 때마다 매번 드라이버를 로드하고 커넥션 객체를 생성하여 연결하고 종료 작업을 반복 -> 비효율적
커넥션 풀의 장점
- Pool 속에 미리 Connection이 생성되어 있기 때문에 커넥션 생성하는데 시간이 걸리지 않음
- 커넥션을 계속해서 사용하고 반환하기 때문에 재사용 가능하므로 많은 수의 커넥션을 만들지 않아도 됨
- 매번 커넥션을 생성하고 해제하는데 시간이 소요되지 않으므로 애플리케이션 실행 속도가 빨라지고 생성되는 커넥션 수를 제어하기 때문에 동시 접속자 수가 증가해도 애플리케이션이 쉽게 다운되지 않음
- 접속 시 사용할 커넥션이 없으면 대기 상태로 전환되고, 커넥션이 반환되면 대기 순서대로 커넥션 제공
커넥션 풀 동작 과정
- 톰캣 컨테이너를 실행한 후 응용프로그램 실행
- 톰캣 컨테이너 실행 시 ConnectionPool 객체 생성
- 생성된 커넥션 객체는 DBMS와 미리 연결
- 데이터베이스 연동 작업이 필요할 경우 응용 프로그램은 ConnectionPool에서 제공하는 메소드를 호출하여 연동
커넥션 풀 사용법
context.xml
파일에 <Resource> 정보 추가
- 톰캣 컨테이너가 데이터베이스 인증 작업 수행
- 커넥션 풀 사용하여 DB 연결
기존 DB 연결
public class BookDAO {
private Connection connDB() {
Connection con = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/servletdb?serverTimezone=UTC";
String user = "root";
String pwd = "1234";
con = DriverManager.getConnection(url, user, pwd);
if(con != null) {
System.out.println("연결 성공");
}
} catch(Exception e) {
System.out.println("연결 오류 발생!");
e.printStackTrace();
}
return con;
}
}
커넥션 풀 사용
public class MemberDAO {
// 커넥션 풀 사용하여 DB 연결
public MemberDAO() {
try {
Context init = new InitialContext();
dataSource = (DataSource) init.lookup("java:comp/env/jdbc/mysql");
System.out.println("DB 연결 성공");
} catch (NamingException e) {
e.printStackTrace();
}
}
}
Server의 context.xml 파일에 <Resource> 태그 추가
- maxWait : 커넥션이 없을 때 대기 시간(1000 : 1초)
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/">
<Resource
name="jdbc/mysql"
auth="Container"
driverClassName="com.mysql.cj.jdbc.Driver"
type="javax.sql.DataSource"
url="jdbc:mysql://localhost:3306/스키마이름?serverTimezone=UTC"
username="db아이디"
password="db비밀번호"
maxActive="50"
maxWait="1000" />
</Context>
커넥션 풀을 이용한 DB 연동 예제
- MemberVO
- MemberDAO
- MemberInsertServlet
- join.html
MemberVO.java
package com.example.servlet01.sec04;
import java.util.Date;
public class MemberVO {
private String id;
private String pwd;
private String name;
private String email;
private Date joinDate;
// 디폴트 생성자
public MemberVO() {}
// 현재는 매개변수 있는 생성자 필요 없음
public MemberVO(String id, String pwd, String name, String email, Date joinDate) {
this.id = id;
this.pwd = pwd;
this.name = name;
this.email = email;
this.joinDate = joinDate;
}
// Getter Setter
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getJoinDate() {
return joinDate;
}
public void setJoinDate(Date joinDate) {
this.joinDate = joinDate;
}
}
MemberDAO.java
package com.example.servlet01.sec04;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.Date;
public class MemberDAO {
private Connection con = null;
DataSource dataSource = null;
public MemberDAO() {
try {
Context init = new InitialContext();
dataSource = (DataSource) init.lookup("java:comp/env/jdbc/mysql");
System.out.println("DB 연결 성공");
} catch (NamingException e) {
e.printStackTrace();
}
}
// 회원 정보 조회 (전체 회원 정보 SELECT 해서 반환 : MemberVO 반환)
// MemberVO를 여러 행 반환 : ArrayList<MemberVO>
public ArrayList<MemberVO> memberSelect(){
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
ArrayList<MemberVO> memList = new ArrayList<MemberVO>();
try {
con = dataSource.getConnection();
String query = "select * from member";
pstmt = con.prepareStatement(query);
rs = pstmt.executeQuery();
while(rs.next()) { // 결과 세트에서 한 행씩 처리
// 한 행(회원 1명당) 처리
String id = rs.getString("memId");
String pwd = rs.getString("memPwd");
String name = rs.getString("memName");
String email = rs.getString("memEmail");
Date joinDate = rs.getDate("memJoinDate");
// 한 행 정보 가져와서 MemberVO에 저장 : setter 메소드 사용
MemberVO vo = new MemberVO();
vo.setId(id);
vo.setPwd(pwd);
vo.setName(name);
vo.setEmail(email);
vo.setJoinDate(joinDate);
// 각 MemberVO를 ArrayList에 추가(저장)
memList.add(vo);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
rs.close();
pstmt.close();
con.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return memList;
}
// 회원 정보 등록하는 메소드 : memberInsert()
public void memberInsert(MemberVO vo){
//sql문 values에 들어갈 데이터 설정
try {
con = dataSource.getConnection();
String sql = "insert into member values(?, ?, ?, ?, default)";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, vo.getId());
pstmt.setString(2, vo.getPwd());
pstmt.setString(3, vo.getName());
pstmt.setString(4, vo.getEmail());
// 쿼리문 실행 : 영향을 받은 행의 수 반환
//select : executeQuery - 결과 행 resultSet 반환.
//insert / update / delete : executeUpdate() - 영향을 받은 행의 수 반환
int result = pstmt.executeUpdate();
if(result > 0) {
System.out.println("데이터 입력 성공!");
}
// 모든 객체 close() : 리소스 반납
pstmt.close();
con.close();
} catch (Exception e) {
System.out.println("오류 발생!");
e.printStackTrace();
}
}
// delete
public void memberDelete(String id){
//sql문 values에 들어갈 데이터 설정
try {
con = dataSource.getConnection();
String sql = "delete from member where memId=?";
PreparedStatement pstmt = con.prepareStatement(sql);
pstmt.setString(1, id);
// 쿼리문 실행 : 영향을 받은 행의 수 반환
//select : executeQuery - 결과 행 resultSet 반환.
//insert / update / delete : executeUpdate() - 영향을 받은 행의 수 반환
int result = pstmt.executeUpdate();
if(result > 0) {
System.out.println("회원정보 삭제 성공!");
}
// 모든 객체 close() : 리소스 반납
pstmt.close();
con.close();
} catch (Exception e) {
System.out.println("오류 발생!");
e.printStackTrace();
}
}
}
MemberServlet.java
package com.example.servlet01.sec06;
import com.example.servlet01.sec04.MemberDAO;
import com.example.servlet01.sec04.MemberVO;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
@WebServlet(name = "MemberInsertServlet", value = "/MemberInsertServlet")
public class MemberInsertServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doProcess(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doProcess(request, response);
}
protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// (1) 요청 받음
request.setCharacterEncoding("utf-8");
String id = request.getParameter("id");
String pwd = request.getParameter("pwd");
String name = request.getParameter("name");
String email = request.getParameter("email");
// MemberVO에 저장
MemberVO vo = new MemberVO();
vo.setId(id);
vo.setPwd(pwd);
vo.setName(name);
vo.setEmail(email);
// 또는 생성자 사용
//MemberVO vo = new MemberVO(id, pwd, name, email);
// 회원 정보 등록 : memberInsert() 호출 -> DB에 저장
MemberDAO dao = new MemberDAO();
dao.memberInsert(vo);
}
}
join.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원가입 폼</title>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<style>
#id, #pwd { width: 100px;}
table { margin:0 auto; width:600px; }
#checkMember {
visibility: hidden;
}
</style>
</head>
<body>
<div id="wrap">
<h3 align="center">회원 가입</h3>
<hr>
<form id="joinForm" name="joinForm" method="post" action="MemberInsertServlet">
<table>
<tr><td> ID</td><td><input type="text" id="id" name="id"></td></tr>
<tr><td>비밀번호</td><td><input type="password" id="pwd" name="pwd"></td></tr>
<tr><td> 성명</td><td><input type="text" id="name" name="name"></td></tr>
<tr><td> 이메일</td><td><input type="text" id="email" name="email"></td></tr>
<tr><td colspan="2"> <input type="submit" value="회원정보 입력"> <input type="reset" value="회원정보보기"></td></tr>
</table>
</form>
</div>
</body>
</html>