9. 커넥션풀, 싱글톤패턴


1. 커넥션 풀 개요

  • 데이터베이스에 연결하기 위한 커넥션 객체는 새로 만들어질 때 많은 시스템 자원이 요구
    • 메모리에 객체를 할당할 자리를 만드는 작업
    • 객체가 사용할 자원들을 초기화하는 작업
    • 객체가 필요없어지면 거두어들이는 작업
    • 매번 새로운 데이터베이스 연결 요청이 들어올때마다 해당 작업을 수행해야된다면 많은 부담
  • 커넥션 객체 생성 관리 방법
    • service method (doGet, doPost)에서 커넥션 객체 생성
      • 데이터베이스와 연동하기 위해 사용한 방법
      • 커넥션 객체의 레퍼런스 변수가 지역변수에 할당
        • 요청당 한개씩 커넥션 객체 생성, 시스템 부하
        • 메모리 낭비
        • 커넥션 시간이 요청시간에 포함
    • init method에서 커넥션 객체 생성
      • 커넥션 객체의 레퍼런스 변수는 전역변수에 할당
      • 커넥션 시간이 걸리지 않는다
      • 하나의 커넥션을 쓰기때문에 쿼리가 쌓이게 되어 응답시간 증가
    • 커넥션 풀에서 객체 생성
      • 자원을 빌려쓰고 회수하는 방법 사용
      • 미리 여분의 커넥션을 만들어놓는다
      • 사용자의 요청이 있을 때 미리 만들어져있는 커넥션 부여
      • 사용된 커넥션 객체는 다시 풀로 회수
      • 커넥션 생성 개수 결정 가능

커넥션 풀

  • 끊임없이 생성되는 커넥션의 문제를 해결하기 위한 목적
  • 반드시 컨테이너에 1개만 만들어지도록 패턴을 만들어야 한다
  • service()메서드당 1개씩은 가지고 쓰게 한다
  • 커넥션 개수를 제한한다
  • 커넥션 객체 관리자가 다쓰면 자원을 회수한다
package com.herbmall.db;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;

public class ConnectionPoolMgr1 {
	private String url,user,pwd;
	private HashMap<Connection, Boolean> hmap;
	private int increment; //증가치
	
	//생성자
	public ConnectionPoolMgr1(){
		increment=5;//5만큼씩 증가
		hmap=new HashMap<Connection,Boolean>(10);	
		Connection con=null;	
		
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
			System.out.println("드라이버 로딩 성공!");
			url="jdbc:oracle:thin:@aa:1521:xe";	
			user="herb"; 	
			pwd="herb123";
			
			//커넥션 객체를 미리 생성해 놓기 - 10개
			for(int i=0;i<10;i++){	
				con=DriverManager.getConnection(url,user,pwd);		
				//해시맵의 key에 커넥션 저장
				
				hmap.put(con, Boolean.FALSE);		
				//해시맵의 value에 true, false 저장, false - 쉬는 커넥션이라는 표시	
			}//for
			
			System.out.println("ConnectionPool 생성!");
		}catch (ClassNotFoundException e) {			
			e.printStackTrace();
			System.out.println("Class Not Found!"); 
		}catch (SQLException e) {			
			e.printStackTrace();
			System.out.println("sql 예외발생!"); 
		}
	}//생성자
		
	public synchronized Connection getConnection() //jsp - 요청시 Thread로 처리
			throws SQLException{
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){ //hmap에 key가 있는 동안 반복	
			con=iterKeys.next();//key값	
			Boolean b=hmap.get(con);//value값	
			//만약 쉬고있는 컨넥션이라면 일하는 컨넥션으로 표시해주고 반환한다.	
			if(b==Boolean.FALSE){	
				hmap.put(con, Boolean.TRUE);//일한다고 표시		
				return con; //일하러 나감	
			}//if	
		}//while
		
		//쉬고 있는 컨넥션이 없으면 일할 Connection을 5개 증가시킨다	
		for(int i=0;i<increment;i++){	
			Connection con2=DriverManager.getConnection(url,user,pwd);	
			hmap.put(con2, Boolean.FALSE);	
		}//for
		
		return getConnection();//재귀호출
	}
	
	//커넥션을 사용하고 난 후 다시 되돌려주는 메소드	
	public void returnConnection(Connection returnCon){
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){	
			con=iterKeys.next();		
			if(con==returnCon){	//con의 주소값이 일치하면
				hmap.put(con, Boolean.FALSE);  //쉬는 커넥션으로 표시	
				break;
			}//if
		}//while
		
		try{	
			removeConnection(); //쉬고있는 커넥션 10개를 유지해주는 메소드	
		}catch(SQLException e){	
			e.printStackTrace();	
			System.out.println("sqlerror:" + e.getMessage());
		}
	}
	
	//Connection 10개만 유지해주는 메서드
	public void removeConnection() throws SQLException{
		Connection con=null;
		Iterator<Connection> iterKeys=hmap.keySet().iterator();
		int count=0;//false인 커넥션 개수
		while(iterKeys.hasNext() ){ 	
			con=iterKeys.next();	
			Boolean b=hmap.get(con);
			boolean b_pre=b.booleanValue();
			if(!b_pre){//쉬고있는 커넥션 개수 세기 - false인 경우
				count++;
				if(count>10){ //쉬고 있는 커넥션이 10개가 넘어가면
					//해시맵에서 삭제
					hmap.remove(con);
					con.close();
				}
			}//if
		}//while
	}
	
	//모든 커넥션 close하는 메서드
	public void closeAll() throws SQLException{
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){ 	
			con=iterKeys.next();	
			con.close();
		}//while
	}
	
	
	//자원해제하는 메서드
	public void dbClose(PreparedStatement ps,  Connection con) throws SQLException{
		if(ps!=null) ps.close();
		if(con!=null)returnConnection(con);
	}
	
	public void dbClose(ResultSet rs,  PreparedStatement ps,  
			Connection con) throws SQLException{
		if(rs!=null)rs.close();
		if(ps!=null) ps.close();
		if(con!=null)returnConnection(con);				
	}
	
	public void dbClose(CallableStatement cs,  Connection con) throws SQLException{
		if(cs!=null) cs.close();
		if(con!=null)returnConnection(con);			
	}
}//class


2. 싱글톤 패턴

  • 객체 지향 개념에 따른 설계 중 재사용할 경우 유용한 설계들을 디자인 패턴으로 정립
  • 디자인 패턴
    • 여러 문제에 대한 설계 사례를 분석
    • 서로 비슷한 문제를 해결하기 위한 설계들을 분류
    • 각 문제 유형별로 가장 적합한 설계를 일반화
    • 패턴으로 정립
  • 싱글톤 패턴
    • 인스턴스를 하나만 생성해서 사용하는 패턴
    • 인스턴스의 개수를 하나로 제한하는 패턴
public class Singleton{
  
  private static Singleton instance;
  
  private Singleton(){} //private 생성자
  
  public static Singleton getInstance(){
    if(instance==null){
    instance = new Singleton();
    }
    return instance;
  }
}
  • 객체의 무분별한 생성을 막기위해 생성자를 private
  • 메서드를 이용해 인스턴스 생성
    • 인스턴스가 없을 경우에만 새로운 인스턴스 생성
      • 인스턴스 변수에 저장
    • 인스턴스가 있을 경우 이미 만들어진 인스턴스 리턴

싱글톤 패턴 예

person.java

package com.herbmall.test;

/* 싱글톤 패턴(songleton)
 * 인스턴스를 하나만 생성해서 사용하는 패턴*/
public class Person {
	
	//static 변수-클래스 차원에서 하나만 생성되어 모든 객체가 공유한다
	private static Person instance;
	
	private Person() { //private 생성자
		
	}
	
	public static Person getInstance() {
		if(instance==null) {
			instance = new Person();
		}
		return instance;
	}
	
	public void showInfo() {
		System.out.println("person 메서드");
	}

}

singleTonTest.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="com.herbmall.test.*"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

<%
	Person p = Person.getInstance();
	out.print(p);
	
	p.showInfo();
%>

</body>
</html>

3. 커넥션풀의 싱글톤패턴 적용

커넥션풀

package com.herbmall.db;

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;

public class ConnectionPoolMgr1 {
	private String url,user,pwd;
	private HashMap<Connection, Boolean> hmap;
	private int increment; //증가치
	
	private static ConnectionPoolMgr1 instance;
	
	//생성자
	public ConnectionPoolMgr1(){
		increment=5;//5만큼씩 증가
		hmap=new HashMap<Connection,Boolean>(10);	
		Connection con=null;	
		
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
			System.out.println("드라이버 로딩 성공!");
			url="jdbc:oracle:thin:@localhost:1521:xe";	
			user="herb"; 	
			pwd="herb123";
			
			//커넥션 객체를 미리 생성해 놓기 - 10개
			for(int i=0;i<10;i++){	
				con=DriverManager.getConnection(url,user,pwd);		
				//해시맵의 key에 커넥션 저장
				
				hmap.put(con, Boolean.FALSE);		
				//해시맵의 value에 true, false 저장, false - 쉬는 커넥션이라는 표시	
			}//for
			
			System.out.println("ConnectionPool 생성!");
		}catch (ClassNotFoundException e) {			
			e.printStackTrace();
			System.out.println("Class Not Found!"); 
		}catch (SQLException e) {			
			e.printStackTrace();
			System.out.println("sql 예외발생!"); 
		}
	}//생성자
	
	//static 메거스
	public static ConnectionPoolMgr1 getInstance() {
		if(instance==null) {
			instance=new ConnectionPoolMgr1();
		}
		return instance;
	}
		
	public synchronized Connection getConnection() //jsp - 요청시 Thread로 처리
			throws SQLException{
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){ //hmap에 key가 있는 동안 반복	
			con=iterKeys.next();//key값	
			Boolean b=hmap.get(con);//value값	
			//만약 쉬고있는 컨넥션이라면 일하는 컨넥션으로 표시해주고 반환한다.	
			if(b==Boolean.FALSE){	
				hmap.put(con, Boolean.TRUE);//일한다고 표시		
				return con; //일하러 나감	
			}//if	
		}//while
		
		//쉬고 있는 컨넥션이 없으면 일할 Connection을 5개 증가시킨다	
		for(int i=0;i<increment;i++){	
			Connection con2=DriverManager.getConnection(url,user,pwd);	
			hmap.put(con2, Boolean.FALSE);	
		}//for
		
		return getConnection();//재귀호출
	}
	
	//커넥션을 사용하고 난 후 다시 되돌려주는 메소드	
	public void returnConnection(Connection returnCon){
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){	
			con=iterKeys.next();		
			if(con==returnCon){	//con의 주소값이 일치하면
				hmap.put(con, Boolean.FALSE);  //쉬는 커넥션으로 표시	
				break;
			}//if
		}//while
		
		try{	
			removeConnection(); //쉬고있는 커넥션 10개를 유지해주는 메소드	
		}catch(SQLException e){	
			e.printStackTrace();	
			System.out.println("sqlerror:" + e.getMessage());
		}
	}
	
	//Connection 10개만 유지해주는 메서드
	public void removeConnection() throws SQLException{
		Connection con=null;
		Iterator<Connection> iterKeys=hmap.keySet().iterator();
		int count=0;//false인 커넥션 개수
		while(iterKeys.hasNext() ){ 	
			con=iterKeys.next();	
			Boolean b=hmap.get(con);
			boolean b_pre=b.booleanValue();
			if(!b_pre){//쉬고있는 커넥션 개수 세기 - false인 경우
				count++;
				if(count>10){ //쉬고 있는 커넥션이 10개가 넘어가면
					//해시맵에서 삭제
					hmap.remove(con);
					con.close();
				}
			}//if
		}//while
	}
	
	//모든 커넥션 close하는 메서드
	public void closeAll() throws SQLException{
		Iterator<Connection> iterKeys=hmap.keySet().iterator();	
		Connection con=null;	
		while(iterKeys.hasNext() ){ 	
			con=iterKeys.next();	
			con.close();
		}//while
	}
	
	
	//자원해제하는 메서드
	public void dbClose(PreparedStatement ps,  Connection con) throws SQLException{
		if(ps!=null) ps.close();
		if(con!=null)returnConnection(con);
	}
	
	public void dbClose(ResultSet rs,  PreparedStatement ps,  
			Connection con) throws SQLException{
		if(rs!=null)rs.close();
		if(ps!=null) ps.close();
		if(con!=null)returnConnection(con);				
	}
	
	public void dbClose(CallableStatement cs,  Connection con) throws SQLException{
		if(cs!=null) cs.close();
		if(con!=null)returnConnection(con);			
	}
}//class

DAO

package com.herbmall.reboard.model;

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

import com.herbmall.db.ConnectionPoolMgr;

public class ReBoardDAO {
	private ConnectionPoolMgr pool;

	public ReBoardDAO() {
		pool=new ConnectionPoolMgr();
	}

	public int insertReBoard(ReBoardVO vo) throws SQLException {
		Connection con=null;
		PreparedStatement ps=null;

		try {
			//1,2
			con=pool.getConnection();

			//3
			String sql="insert into reBoard(no, name, pwd, title, email, content)"
					+ "values(reBoard_seq.nextval, ?,?,?,?,?)";
			ps=con.prepareStatement(sql);
			ps.setString(1, vo.getName());
			ps.setString(2, vo.getPwd());
			ps.setString(3, vo.getTitle());
			ps.setString(4, vo.getEmail());
			ps.setString(5, vo.getContent());

			//4
			int cnt=ps.executeUpdate();
			System.out.println("글등록 결과 cnt="+cnt+", 매개변수 vo="+vo);

			return cnt;
		}finally {
			pool.dbClose(ps, con);
		}
	}

	public List<ReBoardVO> selectAll(String condition, String keyword) 
				throws SQLException {
		/*
		select * from reBoard
		where title like '%안%';

		select * from reBoard
		where name like '%길%';

		select * from reBoard
		where content like '%k%';
		*/
		
		Connection con=null;
		PreparedStatement ps=null;
		ResultSet rs=null;

		List<ReBoardVO> list=new ArrayList<ReBoardVO>();
		try {
			//1,2
			con=pool.getConnection();

			//3
			String sql="select * from reBoard";
			if(keyword!=null && !keyword.isEmpty()) {
				sql+=" where "+condition+" like '%' || ? || '%'";
			}
			sql+=" order by no desc";
			ps=con.prepareStatement(sql);
				
			if(keyword!=null && !keyword.isEmpty()) {
				ps.setString(1, keyword);
			}
			
			//4
			rs=ps.executeQuery();
			while(rs.next()) {
				int no=rs.getInt("no");
				int readcount=rs.getInt("readcount");
				String name=rs.getString("name");
				String pwd=rs.getString("pwd");
				String title=rs.getString("title");
				String email=rs.getString("email");
				String content=rs.getString("content");
				Timestamp regdate=rs.getTimestamp("regdate");

				ReBoardVO vo = new ReBoardVO(no, name, pwd, title, 
						email, regdate, readcount, content);
				list.add(vo);
			}

			System.out.println("전체 조회 결과 list.size="+list.size()
				+", 매개변수 condition="+condition+", keyword="
				+ keyword);
			
			return list;
		}finally {
			pool.dbClose(rs, ps, con);
		}
	}

	public ReBoardVO selectByNo(int no) throws SQLException {
		Connection con=null;
		PreparedStatement ps=null;
		ResultSet rs=null;

		ReBoardVO vo = new ReBoardVO();
		try {
			//1,2
			con=pool.getConnection();

			//3
			String sql="select * from reBoard where no=?";
			ps=con.prepareStatement(sql);
			ps.setInt(1, no);

			//4
			rs=ps.executeQuery();
			if(rs.next()) {
				vo.setNo(no);
				vo.setTitle(rs.getString("title"));
				vo.setContent(rs.getString("content"));
				vo.setName(rs.getString("name"));
				vo.setEmail(rs.getString("email"));
				vo.setPwd(rs.getString("pwd"));
				vo.setRegdate(rs.getTimestamp("regdate"));
				vo.setReadcount(rs.getInt("readcount"));				
			}

			System.out.println("글 상세보기 결과 vo="+vo+", 매개변수 no="+no);

			return vo;
		}finally {
			pool.dbClose(rs, ps, con);
		}
	}

	public int updateReBoard(ReBoardVO vo) throws SQLException {
		Connection con=null;
		PreparedStatement ps=null;

		try {
			//1,2
			con=pool.getConnection();

			//3
			String sql="update reBoard"
					+ " set name=?, title=?, email=?, content=?"
					+ " where no=? and pwd=?";
			ps=con.prepareStatement(sql);
			ps.setString(1, vo.getName());
			ps.setString(2, vo.getTitle());
			ps.setString(3, vo.getEmail());
			ps.setString(4, vo.getContent());
			ps.setInt(5, vo.getNo());
			ps.setString(6, vo.getPwd());

			//4
			int cnt=ps.executeUpdate();
			System.out.println("글수정 결과 cnt="+cnt+", 매개변수 vo="+vo);

			return cnt;
		}finally {
			pool.dbClose(ps, con);
		}
	}

	public int deleteReBoard(int no, String pwd) throws SQLException {
		Connection con=null;
		PreparedStatement ps=null;

		try {
			con=pool.getConnection();

			String sql="delete reBoard where no=? and pwd=?";
			ps=con.prepareStatement(sql);
			ps.setInt(1, no);
			ps.setString(2, pwd);

			int cnt=ps.executeUpdate();
			System.out.println("글 삭제 결과 cnt="+cnt+", 매개변수 no="+
					no+", pwd="+pwd);

			return cnt;
		}finally {
			pool.dbClose(ps, con);
		}
	}
	
	public int updateCount(int no) throws SQLException {
		Connection con=null;
		PreparedStatement ps=null;

		try {
			con=pool.getConnection();

			String sql="update reBoard set readcount=readcount+1"
		               + " where no=?";
			ps=con.prepareStatement(sql);
			ps.setInt(1, no);

			int cnt=ps.executeUpdate();
			System.out.println("조회수 증가 결과 cnt="+cnt+", 매개변수 no="+no);

			return cnt;
		}finally {
			pool.dbClose(ps, con);
		}
	}
}

  • 커넥션 객체 대여
    • ConnectionPoolMgr pool = ConnectionPoolMgr.getInstance();
    • Connection con = pool.getConnection();
  • 커넥션 객체 반납