9. 서버 어플리케이션 서비스에 DBConnectionPool을 만들자
이 포스팅은 오라클 DB, 자바 스프링을 이용합니다.
============================================================
왜 DBConnectionPool을 사용해야 하는가
참고 : https://all-record.tistory.com/104
단일 DBConnection을 이용하여 DB 연결을 시도할 경우 동시 다량의 Request 요청에 응답 하지 못할 수가 있다.
따라서 일정한 간격으로 응답하거나 안정적인 응답을 해주기 위하여 DBCP를 만들게 되는데.
가장 큰 요점은 미리 Connection객체를 만들어 놔서 매번 만들어 쓰지 말고 돌려쓰자는 개념이다. 따라서 최소 Connection 객체 수와 최대 Connection 객체 수가 정해져야하는데 이 범위는 각 프로그램의 성능과 하드웨어적 요인과 더불어 프로그래머가 적절히 선택해야 한다고 생각한다.
1. 아파치 Commons dbcp2를 다운 받는다.
참고 : https://www.baeldung.com/java-connection-pooling
이후 다운 받은 jar을 이클립스 프로젝트 라이브러리에 추가시킨다.
2. 코드 작성
HomeController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | package com.company.jjh; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.DateFormat; import java.util.Date; import java.util.Locale; import java.util.Vector; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.company.db.DBConnection; import com.company.db.BasicConnectionPool; import com.company.db.ConnectionPool; import com.company.jjh.*; /** * Handles requests for the application home page. */ @Controller public class HomeController { private ConnectionPool connectionPool=null; // //byte로 처리 private int callCount=0; // private static final Logger logger = LoggerFactory.getLogger(HomeController.class); HomeController(){ try { connectionPool=BasicConnectionPool.create("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "0000"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @RequestMapping(value = "/", method = RequestMethod.GET) public void home(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test : 누가 root에 접속했습니다"); response.getWriter().append("test Served at: ").append(request.getContextPath()); System.out.println(response); } @RequestMapping(value = "/Login", method = RequestMethod.GET) public void Process_LogIn(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: 누가 login에 접속했습니다"); response.getWriter().append("test Served at Login: "); } @RequestMapping(value = "/TestDB/getEmpTable", method = RequestMethod.GET) public void Process_TestDBGetEmpTable(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: DB test GetEmpTalbe이 실행되었습니다"); response.getWriter().append("call count="+callCount+" test Served at TestDBGetEmpTable: "); for(String t : CallDBConnectionPool() ) { response.getWriter().append("<p>"+t+"</p>"); } callCount++; } @RequestMapping(value = "/TestDB/getEmpTable2", method = RequestMethod.GET) public void Process_TestDBGetEmpTable2(HttpServletRequest request,HttpServletResponse response) throws IOException { //System.out.println("test: DB test GetEmpTalbe이 실행되었습니다"); response.getWriter().append(" test Served at TestDBGetEmpTable: "); for(String t : CallDBConnectionPoolNotPrint() ) { response.getWriter().append("<p>"+t+"</p>"); } } @RequestMapping(value = "/TestAppService/getValue", method = RequestMethod.GET) public void Process_TestAppServiceGetValue(HttpServletRequest request,HttpServletResponse response) throws IOException { System.out.println("test: TestAppServiceGetValue이 실행되었습니다"); response.getWriter().append("call count="+callCount+" test Served at TestDBGetEmpTable: "); callCount++; } Vector<String> CallDBConnectionPoolNotPrint() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn =connectionPool.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ //System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); //System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; //System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){connectionPool.releaseConnection(conn); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } Vector<String> CallDBConnectionPool() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn =connectionPool.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){connectionPool.releaseConnection(conn); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } Vector<String> CallDBConnection() { Connection conn = null; // DB연결된 상태(세션)을 담은 객체 PreparedStatement pstm = null; // SQL 문을 나타내는 객체 ResultSet rs = null; // 쿼리문을 날린것에 대한 반환값을 담을 객체 Vector<String> function_result=new Vector<String>(); try { // SQL 문장을 만들고 만약 문장이 질의어(SELECT문)라면 // 그 결과를 담을 ResulSet 객체를 준비한 후 실행시킨다. String quary = "SELECT * FROM EMP"; conn = DBConnection.getConnection(); pstm = conn.prepareStatement(quary); rs = pstm.executeQuery(); /* EMP 테이블의 데이터 타입 * EMPNO NOT NULL NUMBER(4) -- int ENAME VARCHAR2(10) -- String JOB VARCHAR2(9) -- String MGR NUMBER(4) -- int HIREDATE DATE -- Date SAL NUMBER(7,2) -- float/double COMM NUMBER(7,2) -- float/double DEPTNO NUMBER(2) -- int */ System.out.println("EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO"); System.out.println("============================================"); while(rs.next()){ int empno = rs.getInt(1); //int empno = rs.getInt("empno"); 숫자 대신 컬럼 이름을 적어도 된다. String ename = rs.getString(2); String job = rs.getString(3); int mgr = rs.getInt(4); java.sql.Date hiredate = rs.getDate(5); // Date 타입 처리 int sal = rs.getInt(6); int comm = rs.getInt(7); int deptno = rs.getInt(8); String result = empno+" "+ename+" "+job+" "+mgr+" "+hiredate+" "+sal+" "+comm+" "+deptno; System.out.println(result); function_result.add(result); } } catch (SQLException sqle) { System.out.println("SELECT문에서 예외 발생"); sqle.printStackTrace(); }finally{ // DB 연결을 종료한다. try{ if ( rs != null ){rs.close();} if ( pstm != null ){pstm.close();} if ( conn != null ){conn.close(); } }catch(Exception e){ throw new RuntimeException(e.getMessage()); } } return function_result; } } | cs |
ConnectionPool interface 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package com.company.db; import java.sql.Connection; import java.sql.SQLException; import java.util.List; public interface ConnectionPool { Connection getConnection() throws SQLException; boolean releaseConnection(Connection connection); String getUrl(); String getUser(); String getPassword(); void shutdown() throws SQLException; List<Connection> getConnectionPool(); } | cs |
BasicConnectionPool 클래스 작성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | package com.company.db; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class BasicConnectionPool implements ConnectionPool { private final String url; private final String user; private final String password; private final List<Connection> connectionPool; private final List<Connection> usedConnections = new ArrayList<>(); private static final int INITIAL_POOL_SIZE = 10; private final int MAX_POOL_SIZE = 50; public static BasicConnectionPool create(String url, String user, String password) throws SQLException { List<Connection> pool = new ArrayList<>(INITIAL_POOL_SIZE); for (int i = 0; i < INITIAL_POOL_SIZE; i++) { pool.add(createConnection(url, user, password)); } return new BasicConnectionPool(url, user, password, pool); } private BasicConnectionPool(String url, String user, String password, List<Connection> connectionPool) { this.url = url; this.user = user; this.password = password; this.connectionPool = connectionPool; } @Override public Connection getConnection() throws SQLException { if (connectionPool.isEmpty()) { if (usedConnections.size() < MAX_POOL_SIZE) { connectionPool.add(createConnection(url, user, password)); } else { throw new RuntimeException("Maximum pool size reached, no available connections!"); } } Connection connection = connectionPool.remove(connectionPool.size() - 1); usedConnections.add(connection); return connection; } @Override public boolean releaseConnection(Connection connection) { connectionPool.add(connection); return usedConnections.remove(connection); } private static Connection createConnection(String url, String user, String password) throws SQLException { return DriverManager.getConnection(url, user, password); } public int getSize() { return connectionPool.size() + usedConnections.size(); } public List<Connection> getConnectionPool() { return connectionPool; } @Override public String getUrl() { return url; } @Override public String getUser() { return user; } @Override public String getPassword() { return password; } public void shutdown() throws SQLException { usedConnections.forEach(this::releaseConnection); for (Connection c : connectionPool) { c.close(); } connectionPool.clear(); } } | cs |
3. 코드 설명
해당 코드는 jdk 1.8에서 작동하며 프로젝트가 jdk버전에 맞지 않으면 오류가 발생 할 수 있다.
DBCP는 BasicConnectionPool 로서, 해당 객체를 이용하여 Connection 객체들을 관리한다.
때문에 HomeController는 해당 BasicConnectionPool을 이용하여 DB에 접속한다.
특이하게 static으로 만들어진 이 DBCP 클래스는 생성조차 범상치 않다...
connectionPool=BasicConnectionPool.create("jdbc:oracle:thin:@localhost:1521:orcl", "scott", "0000");
이런 식으로 만들어지는데. 나는 처음 보는 문법이므로 설명을 할 수가 없다.
다만 이해를 하자면 static으로 만들어진 블럭을 생성하는 문법으로 람다를 이용 한 것 처럼 보인다고 생각 할 뿐이다. 그러므로 매번 저 create를 사용 할 경우 원하는 db 연결로 변경 해 줄 수 있다고 생각한다.
커넥션 할당은 이렇게 하였고
conn =connectionPool.getConnection();
커넥셜 풀링은
if ( conn != null ){connectionPool.releaseConnection(conn); }
이렇게 해주었다. conn에 무언가 있을 경우 DBCP의 release 메소드를 호출하여 conn값을 넘기면 conn의 객체를 릴리즈 시켜준다.
===========================================================
이후는 단순히 성능 테스트 이므로 보지 않아도 무관하다.
1000회 DB 읽기 작업을 request 하여 걸린 시간
1회 초당 서버 어플리케이션 서비스에 있는 변수를 읽어들인 횟수
2회 초당 서버 어플리케이션 서비스에 있는 변수를 읽어들인 횟수
단순한 C++ 코드로 반복적인 성능 테스트를 한 결과
1. 일반 Connection 보다는 ConnectionPool이 안정적이고 속도는 2배 차이가 난다. 운영체제 HW에 따라 달라진다고 예상된다.
2. ConnectionPool의 max값은 일정 값 이상이 될 경우 속도의 차가 없으며 min값도 일정 값 이상이 될 경우 속도의 차가 없다.
3. 화면 출력 code를 제거시 속도 향상을 보인다.
outstream을 이용하지 않아 일어나는 현상이라 생각한다.
추가 적으로 DB에 접근하지 않고 즉 DBCP를 이용하지 않고 그냥 어플리케이션의 request 성능을 테스트 한결과
1. 초당 100~130 정도의 request 수행 능력을 보여주었다.HW와 OS에 따라 달라진다고 생각한다.
2. 역시 outstream을 이용하지 않을 경우 2초 이상 수행 시간이 단축되는 것을 확인 할 수 있었다.
'중단한 프로젝트 > WebServerGameProject(Team)' 카테고리의 다른 글
8. 스프링 웹 어플리케이션 서비스에 request하면 db에 접속하는 기능을 만들자 (0) | 2019.01.30 |
---|---|
7. rest SDK를 이용하여 request해서 respon 된 값 출력하기 (0) | 2019.01.29 |
6. cpprest , rest 라이브러리로 C++로 http 통신 구하기 (0) | 2019.01.29 |
5. Boost asio를 이용한 네트워크 통신 만들어보기 (0) | 2019.01.28 |
4. c++로 webservice에 연결하는 방법 (0) | 2019.01.24 |