DBCP - DataBase Connection Pool

 

- 순간 접속자가 다량으로 발생했을 시, 미리 생성해둔 Connection 객체를 대여하는 방식으로 접속자는 제한하는 기술


웹서버에서 동시 접속자가 폭주하는 경우, close를 잘해놔도 서버 다운

 

DBCP는 connection을 여러개 만들어 놓고 대여해줌

ex) 30개의 커넥션 대여 - 31번째 접속자 대기시킴 , DB가 꺼지진 않음

 


DBCP 라이브러리

- APACHE DBCP

 

표준화된 라이브러리를 모아놓은 사이트

https://mvnrepository.com/

 

Maven Repository: Search/Browse/Explore

Dropwizard Utility Classes Last Release on Mar 29, 2020

mvnrepository.com


BasicDataSource

import java.sql.Connection;

import org.apache.commons.dbcp2.BasicDataSource;

public class TempMain {
	public static void main(String[] args) {
		BasicDataSource bds = new BasicDataSource();
		//Class.forName("oracle.jdbc.driver.OracleDriver");
		// XE - Expression -> 100
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30); // 초기화 사이즈, 많을수록 더 많은 사용자가 접속 가능
		
		try (Connection con = bds.getConnection();){

		}catch(Exception e) {

		}
	}
}


라이브러리가 다른 라이브러리를 의존해서 만들어진 경우 - 의존성을 가진다.

org.apache.commons.pool2.ObjectPool 


maven repository에서 apache pool2 검색

org/apache/commons/logging/LogFactory

의존하는게 하나가 아니구나!


의존성 문제 해결


DAO에 옮겨서 이식 (Manager로 붙여넣기)

	public Connection makeConnection() throws Exception {

		BasicDataSource bds = new BasicDataSource();
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);

		return bds.getConnection();
	}

문제1] makeConnection 호출 할때마다 커넥션을 30개 만들고 사라짐(지역변수!)

 

멤버필드로 빼놔야 함

	private BasicDataSource bds = new BasicDataSource();
	
	public Connection makeConnection() throws Exception {
	
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);

		return bds.getConnection();
	}

문제2] 콜 할때마다 재설정, 반복될 필요 없는 코드

 

한번만 실행될만한 생성자 안에 넣기

	private BasicDataSource bds = new BasicDataSource();
	
	public CafeManager() {
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);
	}
	
	public Connection makeConnection() throws Exception {
		return bds.getConnection();
	}

문제3] 동시접속시 접속 할때마다 CafeManager를 new로 생성

커넥션30개 * 10명 접속 = 300 커넥션

 

동시접속시 좋은 코드 아님!

CafeManager 인스턴스를 여러개 만들지 못하게 통제해야 함

 

디자인패턴 중 하나인 Singleton Pattern

- 인스턴스 생성을 통제하는 기법 (인스턴스를 한개 이상 생성하지 못하게 막는 기술)


Singleton Pattern

 

 생성자가 private이면 new를 못함

매서드 제공

	private BasicDataSource bds = new BasicDataSource();
	private static CafeManager instance = null;

	public static CafeManager getInstance() {
		if(instance == null) {
			instance = new CafeManager(); //class내부 private영향 안받음
		}
		return instance;
	}// 인스턴스를 한번만 만들기

	private CafeManager() {
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);
	}

	public Connection makeConnection() throws Exception {
		return bds.getConnection();
	}
import java.util.List;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {

		CafeManager cm = CafeManager.getInstance();

	}
}

동시 접속자가 너무 많을 경우 - 두명의 사용자가 동시에 들어오는 순간

쓰레드는 병렬처리 - 동시에 작동함

if(instance == null) {

까지 동시에 들어오면? 30+30 = 60개 생성


예) 카드& 통장으로 인출

public class Account {
	private static int money = 1000;

	public void withdraw() {
		if(money>0) {
			money -= 200;
			System.out.println("현재 잔액 : " + money);
		}
	}

}
public class SyncPractice {
	public static void main(String[] args) {
		
		Account card = new Account();
		Account bankbook = new Account();
		
		card.withdraw();
		bankbook.withdraw();

	}
}

//현재 잔액 : 800
//현재 잔액 : 600

동시에 인출한다면?

public class Account {
	private static int money = 1000;

	public void withdraw() {
		if(money>0) {
			try {Thread.sleep(100);}catch(Exception e) {}
			money -= 200;
			System.out.println("현재 잔액 : " + money);
		}
	}

}
public class SyncPractice {
	public static void main(String[] args) {

		Account card = new Account();

		card.withdraw();
		bankbook.withdraw();

		new Thread() {
			public void run() {
				while(true) {
					card.withdraw();
				}
			}
		}.start();

		new Thread() {
			public void run() {
				while(true) {
					card.withdraw();
				}
			}
		}.start();

	}
}

쓰레드 스케줄링에 따라 음수가 나올 수도 있음


synchronized

동기화된

한번에 하나의 쓰레드만 접근 하도록 함

public class Account {
	private static int money = 1000;

	public synchronized void withdraw() {
		if(money>0) {
			try {Thread.sleep(100);}
			catch(InterruptedException e) {}
			money -= 200;
			System.out.println("현재 잔액 : " + money);
		}
	}

}
public class CafeManager {

	private BasicDataSource bds = new BasicDataSource();
	private static CafeManager instance = null;

	public synchronized static CafeManager getInstance() {
		if(instance == null) {
			instance = new CafeManager(); //class내부 private영향 안받음
		}
		return instance;
	}// 인스턴스를 한번만 만들기

	private CafeManager() {
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);
	}

	public Connection makeConnection() throws Exception {
		return bds.getConnection();
	}

 

Thread 동기화


public class Bank {
	int money = 1000;
	public void withdraw() {
		while(this.money > 0) {
			try {Thread.sleep(1000);
			}catch(InterruptedException e) {}
			this.money -= 200;
			showMoney();
		}
	}
	
	public void showMoney() {
		System.out.println(this.money);
	}
	
}
class BankThread extends Thread{
	private Bank b;
	public BankThread(Bank b) {
		this.b = b;
	}
	public void run() {
		b.withdraw();
	}
}

public class Main {
	public static void main(String[] args) {
		Bank b = new Bank();
		new BankThread(b).start();
		new BankThread(b).start();
	}
}

DBCP 반영하기

 

Manager 코드

더보기
import java.math.BigInteger;
import java.security.MessageDigest;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.dbcp2.BasicDataSource;

public class Manager {
	
	private BasicDataSource bds = new BasicDataSource();
	private static Manager instance = null;

	public static Manager getInstance() {
		if(instance == null) {
			instance = new Manager(); //class내부 private영향 안받음
		}
		return instance;
	}// 인스턴스를 한번만 만들기

	private Manager() {
		bds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
		bds.setUsername("kh");
		bds.setPassword("kh");
		bds.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
		bds.setInitialSize(30);
	}

	public Connection makeConnection() throws Exception {
		return bds.getConnection();
	}
	
	//id만 중복체크
	public boolean isIdExist(String id) throws Exception{

		String sql = "select sid from member where sid = ?";
		try(
				Connection con = makeConnection();
				PreparedStatement pstat = con.prepareStatement(sql);
				){
			pstat.setString(1, id);
			try(ResultSet rs = pstat.executeQuery();){
				return rs.next();
			}
		}
	}
	//DAO
	public int insert(String sid, String spw, String sname) throws Exception {
		String sql = "insert into member values(?,?,?,sysdate)";
		try(
				Connection con = makeConnection();
				PreparedStatement pstat = con.prepareStatement(sql);
				) {
			pstat.setString(1, sid);
			pstat.setString(2, spw);
			pstat.setString(3, sname);
			int result = pstat.executeUpdate();
			con.commit();
			return result;
		}
	}
	
	public static String getSHA512(String input){

		String toReturn = null;
		try {
			MessageDigest digest = MessageDigest.getInstance("SHA-512");
			digest.reset();
			digest.update(input.getBytes("utf8"));
			toReturn = String.format("%0128x", new BigInteger(1, digest.digest()));
		} catch (Exception e) {
			e.printStackTrace();
		}

		return toReturn;
	}
	
	public List<Member> login(String id, String pw) throws Exception {
		String sql = "select * from member where sid = ? and spw = ?";
		try(
				Connection con = this.makeConnection();
				PreparedStatement pstat = con.prepareStatement(sql);
				){
			pstat.setString(1, id);
			pstat.setString(2, pw);
			try(
					ResultSet rs = pstat.executeQuery();
					){
				List<Member> sdt = new ArrayList<>();
				while(rs.next()) {
					String login_id =rs.getString("sid");
					String login_pw =rs.getString("spw");
					String login_name =rs.getString("sname");
					String login_date =rs.getString("sdate");
					sdt.add(new Member(login_id,login_pw,login_name,login_date ));	
				}
				return sdt;
			}
		}
	}
}

 

SocketThread 코드

더보기
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.List;

public class SocketThread extends Thread{

	private Socket sock;

	public SocketThread(Socket sock) {
		this.sock = sock;
	}

	public void run() {
		System.out.println(sock.getInetAddress() + " 에서 연결하였습니다.");
		try {
			DataInputStream dis = 
					new DataInputStream(sock.getInputStream());
			DataOutputStream dos = 
					new DataOutputStream(sock.getOutputStream());

			Manager manager = Manager.getInstance();

			while(true) {
				//첫 메뉴창에서 입력 받기
				String menu = dis.readUTF();

				if(menu.contentEquals("1")) {
					//1번 선택시 회원 가입
					boolean idCheck = true;
					String id = "";
					while(idCheck) {
						//1-1. 아이디 받기
						id = dis.readUTF();

						//1-2. 아이디 중복 체크
						idCheck = manager.isIdExist(id);
						System.out.println(idCheck); //test
						dos.writeBoolean(idCheck);
						dos.flush();
						
						if(idCheck == false) {						
							break;
						}
					}
					//1-3. 비밀번호 받기
					String pw = dis.readUTF();

					//1-4. 이름 받기
					String name = dis.readUTF();

					//1-5. 받은 정보 DB에 삽입 & 결과 반환 받기
					int result = manager.insert(id, pw, name);
					if(result > 0) {
						dos.writeBoolean(true); //성공
					}else {
						dos.writeBoolean(false); //실패
					}
					dos.flush();

				}else if(menu.contentEquals("2")){
					//2번 선택시 로그인

					//2-1 아이디 받기
					String id = dis.readUTF();

					//2-2 비밀번호 받기
					String pw = dis.readUTF();

					//2-3 로그인하여 결과 Member List 에 저장
					List<Member> members = manager.login(id, pw);
					if(members.size() == 1) {
						dos.writeBoolean(true); //성공
					}else {
						dos.writeBoolean(false); //실패
					}
					dos.flush(); //보내기


				}


			}


		}catch(Exception e) {
			e.printStackTrace();
		}

	}

}

 

Server 코드

더보기
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

	public static void main(String[] args) {
		
		try {
			ServerSocket server = new ServerSocket(20000);
			System.out.println("서버가 구동되었습니다.");
			
			while(true) {
				Socket sock = server.accept();
				new SocketThread(sock).start();
			}

		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
}

 

Client 코드

더보기
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {

	public static void main(String[] args) {
		try {
			Socket sock = new Socket("127.0.0.1", 20000);
			//		Socket sock = new Socket("192.168.60.31", 25000);

			DataInputStream dis = 
					new DataInputStream(sock.getInputStream());
			DataOutputStream dos = 
					new DataOutputStream(sock.getOutputStream());

			Scanner sc = new Scanner(System.in);

			while(true) {

				//0. 메뉴 선택 보내기
				System.out.println("1. 회원가입");
				System.out.println("2. 로그인");
				System.out.print(">> ");
				String menu = sc.nextLine();
				dos.writeUTF(menu);
				dos.flush();

				//1. 회원가입
				if(menu.contentEquals("1")) {
					System.out.println("=== 회원가입 ===");

					boolean idCheck = true;
					//idCheck를 while문 밖에 놓고 idCheck로 반복

					while(idCheck) {
						//1-1. 아이디 보내기
						System.out.print("ID: ");
						String id = sc.nextLine();
						dos.writeUTF(id);
						dos.flush();

						//1-2. 아이디 중복 여부 확인
						idCheck = dis.readBoolean();
						if(idCheck == false) {
							System.out.println("가입가능한 아이디입니다.");
							break;
						}else {
							System.out.println("중복된 아이디입니다.");
						}
					}

					// 1-3. 비밀번호 보내기
					System.out.print("Password: ");
					String pw = sc.nextLine();
					dos.writeUTF(pw);
					dos.flush();

					// 1-4. 이름 보내기
					System.out.print("이름: ");
					String name = sc.nextLine();
					dos.writeUTF(name);
					dos.flush();

					// 1-5. 회원가입 성공 여부 알림
					boolean signupOk = dis.readBoolean();
					if(signupOk) {
						System.out.println("회원가입에 성공하셨습니다.");
					}else {
						System.out.println("회원가입에 문제가 발생하였습니다.");
					}

					//2. 로그인
				}else if(menu.contentEquals("2")) {
					System.out.println("=== 로그인 ===");

					//2-1. 아이디 보내기
					System.out.print("ID: ");
					String id = sc.nextLine();
					dos.writeUTF(id);
					dos.flush();

					//2-2. 비밀번호 보내기
					System.out.print("Password: ");
					String pw = sc.nextLine();
					dos.writeUTF(pw);
					dos.flush();

					//2-3. 로그인 성공 여부 알림
					boolean loginOk = dis.readBoolean();
					if(loginOk) {
						System.out.println("로그인에 성공하셨습니다.");
					}else {
						System.out.println("로그인이 실패하였습니다.");
					}
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
	}

}

 

+ Recent posts