2Bbear's knowledge workshop

이 포스팅은 오라클 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초 이상 수행 시간이 단축되는 것을 확인 할 수 있었다.





이 포스팅은

스프링 프로젝트, 오라클 DB를 이용하여 만들어집니다.


MySQL이나 다른 DB는 안쓰니 다른 정보를 검색해주세요.


추가로 마이바티스도 안쓰고 작성하는 겁니다. 마이바티스를 쓰는 걸 보고 싶으신 분은 구글에 검색해 보시면 됩니다.


============================================================

1. 이클립스로 스프링 프로젝트를 만듭니다

레그씨 프로직트를 만듭니다.


이후 MVC 형태의 스프링 프로젝트를 선택해주고


간단하게 패키지 명을 정해줍니다. 보통 com.company.자신이 원하는 이름

이런 식으로 만든다고 하네요


만들고 잠시 관련 라이브러리를 설치하는 것을 기다려 줍니다.



2. 오라클 jdbc 라이브러리를 가져옵시다.

C:\app\오라클 설치한 사용자 계정\product\11.2.0\dbhome_1\jdbc\lib
보통 이 경로상에 있으니 찾아보시면 됩니다


그 후 이렇게 이클립스 라이브러리에 추가해줍니다.


3. DB 연결을 위한 class를 만들어 줍니다.

package com.company.db;
import java.sql.*;
public class DBConnection {
public static Connection dbConn;
public static Connection getConnection() {
Connection conn=null;
try {
String user="scott";
String pw="0000";
String url="jdbc:oracle:thin:@localhost:1521:orcl";
Class.forName("oracle.jdbc.driver.OracleDriver");
conn=DriverManager.getConnection(url, user, pw);
System.out.println("Database에 연결되었습니다. \n");
}catch (ClassNotFoundException cnfe) {
System.out.println("DB 드라이버 로딩 실패 :"+cnfe.toString());
} catch (SQLException sqle) {
System.out.println("DB 접속실패 : "+sqle.toString());
} catch (Exception e) {
System.out.println("Unkonwn error");
e.printStackTrace();
}
return conn;
}
}



4. 만들어진 스프링 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
package com.company.jjh;
 
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.jjh.*;
 
/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
    
    //
    //byte로 처리
    private int callCount=0;
    //
    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
    
    @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 : CallDBConnection() )
        {
            response.getWriter().append("<p>"+t+"</p>");
        }
        callCount++;
    }
    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



5. 테스트 해봅시다 저는 C++로 respone 받은 데이터를 출력 시켜볼껍니다.


DB에서 잘 받아오는 것을 확인 할 수 있습니다.

기존에 했었던 rest SDK 예제 코드를 이용하여 수정하였다.

============================================================

이번에는 respon된 값을 파일로 출력하는 것이 아닌 string으로 출력한다.


#include <cpprest/http_client.h>

#include <cpprest/filestream.h>


using namespace utility;

using namespace web;

using namespace web::http;

using namespace web::http::client;

using namespace concurrency::streams;


int main()

{

pplx::task<void> requestTask2{ []() 

{

std::cout << "1" << std::endl;

http_client_config conf;

conf.set_timeout(seconds(5));


http_client client(U("http://localhost:8080"));

uri_builder builder(U("/webgameserver/Login"));


http_response response = client.request(methods::GET, builder.to_string()).get();

std::cout << "2" << std::endl;

printf("Receive response status code:%u\n", response.status_code());


char ch = 0;

std::string str;

ch = response.body().read().get();

while (ch != -1)

{

str.push_back(ch);

ch = response.body().read().get();

}


std::cout << "respone : " << str << std::endl;


}};

try

{

requestTask2.wait();

}

catch (const std::exception &e)

{

printf("Error exception:%s\n", e.what());

}

       result 0;

}



원하는 형태로 값이 출력된 것을 확인 할 수 있다.


드디어 찾았다 흙흙...

============================================================

1. cpprestsdk 를 다운받기 위해 Window용 vcpkg를 다운 받아야합니다.

참고 : https://upglay.tistory.com/12


이 블로그를 따라서 vcpkg를 다운 받아 봅시다.


window 8,7,10 이상의 os가 필요하고

visual studio 2015, 2017이 필요합니다

Git을 설치해야합니다

Cmake를 설치해야합니다.


1. vcpkg 설치 (윈도우 기준)

https://upglay.tistory.com/12 로 들어가서 Zip 파일로 파일을 내려 받습니다.


2.C 드라이브에 vcpkg라는 폴더를 만들어 그 곳에 다운 받은 Zip압축을 풀어줍니다.


3. CMD 창으로 가서 c:\vcpkg\boostrap-vcpkg.bat를 치면 설치가 시작됩니다.


여기까지 됐으면 스탑.!


2. 이제 cpprestsdk라이브러리를 설치합시다.

참고 : https://github.com/Microsoft/cpprestsdk


1. CMD 창에서 vcpkg install cpprestsdk cpprestsdk:x64-windows 명령어를 칩니다.


2. 27분을 기다려줍니다.....설치하는게 너무 많....용량 9기가...


3. 설치가 다 되면 현재 켜진 모든 Visual Studio를 종료합니다.


4. 다시 CMD 창으로 돌아가서 vcpkg integrate install 명령어를 쳐줍니다.

이 명령어로 모든 Visual studio에 라이브러리가 통합됩니다. 한 마디로 굳이 링커 연결을 안해주어도 헤더파일을 끌어다 쓸 수 있따는 겁니다!!! 하와와와



3. Visual Studio에 새로운 c++ 프로젝트를 만들어서 코드를 테스트 해봅시다.


#include <cpprest/http_client.h>

#include <cpprest/filestream.h>


using namespace utility;

using namespace web;

using namespace web::http;

using namespace web::http::client;

using namespace concurrency::streams;


int main()

{

auto fileStream = std::make_shared<ostream>();


pplx::task<void> requestTask = fstream::open_ostream(U("result.html")).then([=](ostream outFile)

{

*fileStream = outFile;


http_client_config conf;

conf.set_timeout(seconds(5));


http_client client(U("http://localhost:8080"));

uri_builder builder(U("/webgameserver/Login"));

builder.append_query(U("q"), U("Casablanca CodePlex"));


return client.request(methods::GET, builder.to_string());

}).then([=](http_response response)

{

printf("Receive response status code:%u\n", response.status_code());


return response.body().read_to_end(fileStream->streambuf());

}).then([=](size_t nVal)

{

printf("Size is %u\n", nVal);


return fileStream->close();

});


try

{

requestTask.wait();

}

catch (const std::exception &e)

{

printf("Error exception:%s\n", e.what());

}


return 0;

}

 


이 코드가 하는 일이 뭐냐면...해당 포트와 패스로 접속해서 접속된 응답을 result.http로 바꾸는 코드입니다. 흠...고쳐야 할 부분이 많지만 일단 써봅시다.


client url 적는 부분에 호스트와 포트번호를 합쳐서 넣어줍니다.

그 뒤 builder에는 Path를 즉 경로를 넣어서 센드 해주면 

짜잔 하고 완성됩니다.


애초에 궁금하다면 웹 어플리케이션 서비스에 해당 접속이 이루어지면 반응하게 끔 하시면 알기 쉬울꺼에요




Login에 요청을 보내자 반응하는 모습입니다.






=====================================================

헐 이렇게 어렵게 설치하는 것 보다 이미 있는 rest sdk를 쓰는 게 좋지 않았을까.?..

https://psychoria.tistory.com/211

이분이 사용하시는거 보니...ㅠㅠ

기존 winsock을 이용하여 하는 것이 영 탐탁치 않았던 나는 boost asio를 이용하여 네트워크 통신을 만들어 보기로했다.


============================================================

참고 : https://lunapiece.net/Article/14007572

참고 : https://qiita.com/nia_tn1012/items/77c3c1b006f7c69d30ac


1. boost를 자신의 프로젝트에 추가한다. boost -vc141으로 합시다.

- v120의 경우 Visual Studio 2013 버전

- v140의 경우 Visual Studio 2015 버전

- v141의 경우 Visual Studio 2017 버전

- 단순히 Nuget을 이용하여 추가하는 것이 정신건강에 좋으니 그렇게 하기로 하자


- 다만 이렇게 설치할 경우 

패키지 파일들이 어디에 저장되냐면 솔루션 폴더 내의 package 폴더에 저장되게 됩니다.


신기한건 프로젝트 옵션에 Path나 lib , include를 설정해주지 않아도 링크가 되어있는 점인데요


이것은 프로젝트 파일에 해당 위치가 포함되어 있기 때문입니다.


ExtensionTargets를  추가해서 빌드 targets 파일을 추가 해 놓는 겁니다.


다만 5G가 프로젝트 생성될때마 추가되면 쓸때없이 프로젝트 용량이 계속 증가한다는 이야기가 있네요.... 테스트 용으로만 쉽게 쓰고 버려야 하는 걸까요...



2. 모르겠다... libcurl 를 쓰라는 말도 있고, boost를 이용해서 하라는 말도 있고

참고 : https://curl.haxx.se/libcurl/c/https.html




다양한 방법으로 c++로 http에 접근하는 방법이 있는거 같으나, 간단하게 request하고 response 받는 방법이 필요하기에 이해 할 수 있는 범위 내에서 찾아보자.



다른 방법 없을까..


https://wiki.unrealengine.com/UE4.10_How_To_Make_HTTP_GET_Request_in_C%2B%2B


와아아아 이거 뭐야머야머야?!!!


아냐..이것도...내가 원하는 건 아니야/....거의 맞긴한데...



게임 요구 사항 분석


사용자는

게임을 실행 시킬 수 있어야 한다.

게임을 종료 시킬 수 있어야 한다.


웹서버에 "서버접속"메세지를 통해서, 웹 서버에 접속 할 수 있어야 한다.

웹서버에 "서버 접속해제"메세지를 통해서, 웹 서버 접속을 끊을 수 있어야 한다.


웹서버에 "모든 방 조회"메세지를 통해서, 현재 모든 방을 조회 할 수 있어야 한다.

웹서버에 "??번호 방에 접속"메세지를 통해서, ??방에 접속 할 수 있어야 한다.

웹서버에 "방 생성"메세지를 통해서, 방을 만들 수 있어야 한다.

웹서버에 "자기 방 삭제"메세지를 통해서, 자신이 만든 방을 삭제 할 수 있어야 한다.

웹서버에 "자기 방 게임시작"메세지를 통해서, 자신이 만든 방에서 게임을 시작 할 수 있어야 한다.



웹서버에 "모든 캐릭터 HP조회"메세지를 통해서, 게임 속 모든 캐릭터의 HP 상태를 볼 수 있어야 한다. <이거 가능한 걸까?...초당 몇번을 호출해야해?..>


게임 속 자신의 캐릭터를 이동 시킬 수 있어야한다. <와 이건....초당30~40정도만 해도 어마어마한데..>

게임 속 자신의 캐릭터로 "발사"를 할 수 있어야한다.


웹서버 요구사항 분석



게스트 사용자, 호스트 사용자는 사용자에게 포함된다.

사용자는

웹 서버에 로그인 처리를 한뒤 인증 받고 접속 할 수 있어야한다.

방 리스트를 조회 할 수 있어야 한다.


호스트 사용자로써 방 만들기를 할 수 있어야 한다.

호스트 사용자로써 방 게임을 시작 할 수 있어야한다.

호스트 사용자로서 방 삭제를 할 수 있어야한다.


게스트 사용자로써 방에 접속 할 수 있어야한다.


관리자는

웹 서버를 구동 시킬 수 있어야 한다.

웹 서버를 종료 시킬 수 있어야 한다.



MO 형식의 게임에서 어떻게 데이터를 처리 할 것인지.


1. 필요한 데이터들을 나열 합니다.


오브젝트

- 위치 데이터 (유상태, 30ms, 라이프 사이클 게임 시작-종료, 데이터 매우작음)


플레이어 데이터

- 아이디(플레이어 닉네임) (무상태, 한번만 업데이트, 라이프 사이클 게임시작 - 종료, 데이터 매우 작음)

- HP (무상태, 10ms, 라이프 사이클 게임 시작- 종료, 데이터 매우작음)

- 아이템 수 (무상태, 10ms, 라이프 사이클 게임 시작-종료, 데이터 매우작음)


시스템 데이터


게임 룸 데이터 (여러개 존재 해야함) (무상태 ,?? , 라이프 사이클 방 생성-삭제, 데이터 작음)

- 룸 이름 

- 룸 개설자 

- 최대 인원수

- 현재 입장한 플레이어 아이디

- 게임 진행 상황


게임 로비 데이터 (여러개 존재해야함) (무상태, 요청에 의해서만, 라이프 사이클 서버 시작- 종료, 데이터 작음)

- 현재 로비에 개설된 룸 갯수

- 현재 로비에 있는 유저의 수


인증 데이터 (미러링을 통해 여러 DB를 놓아야 할지도...?) 

- 유저 로그인 처리