본문 바로가기
Back/Servlet

[Intellij]서블릿 DB연동(4) - 커넥션 풀 (DBCP: Connection Pool)

by Hyeon_ 2021. 12. 28.

커넥션 풀(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>