BackEnd/WEB

DBCP_웹DB_국비_DAY59

Leo.K 2022. 5. 24. 18:03

DBCP(Database Connection Pool)

ㄴ> 데이터베이스 커넥션들을 미리 준비하고 사용자의 요청이 들어오면 연결된 커넥션 객체를 바로 전달하는 구조.

ㄴ> 자바프로그램에서 사용하던 DBService는 요청이 들어올 때마다 커넥션을 연결해서 전달한다. (예: 전화를 하기 위해 번호를 누르는 시간, 연결하는 시간, 통화하는 시간, 끊는 시간 -> 통화를 위해 소요되는 서비스 지연시간이 너무 길다.)

ㄴ> 웹에서 사용하는 DB는 불특정다수가 동시에 많이 들어오기 때문에 위처럼 요청이 들어올 때마다 연결 시도, 연결, 커넥션 전달 이런식으로 진행하면 많은 사용자의 트래픽을 감당할 수 없을 정도로 서비스의 지연시간이 발생한다. 웹 서비스는 사용자의 요청이 발생하면 바로바로 요청을 해줘야 하기 때문에, DBCP는 프로그램이 시작할 때 미리 DB와 연결을 해놓은 객체를 만들어 놓고, 요청이 들어올 때마다 바로바로 연결을 해준다.

ㄴ> JDBC(손님이 와서 손님의 요청이 들어오면 그때마다 김밥을 만다.). DBService가 직접 DB에 접근해서 커넥션을 얻어온다.

ㄴ> DBCP(손님이 몇 명이 오든 미리 김밥을 말아놓고 손님의 요청이 발생하면 바로바로 김밥을 준다). DBService가 이미 DB와 연결된 커넥션을 가져온다. 직접 DB에 접근하는 것이 아니라, Dao에게 요청이 들어오면 연결된 객체를 전달만 해주는 역할이다. 커넥션을 가져와서 연결을 하고 사용을 다하고 반납하면(close) 소멸이 된다. 이때 BasicDataSource가 지속적으로 유지보수하면서 maxActive값을 유지한다. 단, 사용은 하는데 close를 하지 않으면 dbcp에서 아직 사용중인 것으로 인식하여 추가로 연결 객체를 생성하지 않는다. 따라서 maxActive가 20일때, 모두 close하지 않은 상태로 21번째, 연결 객체를 전달해달라고 요구하면 에러가 발생한다.

 

JNDI(Java Naming Directory Interface): 생성된 자원의 정보를 이름(Naming)을 통해서 검색 및 획득하는 기술

 

환경설정 아주 중요!!!

  1. 다이나믹 프로젝트 생성
  2. 하나의 jar파일만 업데이트하면 안되고 관련된 모든 파일을 업데이트 해야 한다.(commons-collections-3.2.1.jar, commons-dbcp-1.2.2.jar, commons-pool-1.4.jar, ojdbc14.jar)
  3. 위의 파일 네개를 1에서 생성한 다이나믹 프로젝트 -> src -> main -> webapp -> WEN-INF -> lib폴더에 복사 붙여넣기 한다. 
  4. context.xml파일을 META-INF폴더에 복사 붙여 넣기 한다.
  5. context.xml파일을 열어서 url정보를 내가 설치한 Oracle 버전에 맞도록 수정한다. url="jdbc:oracle:thin:@localhost:1521:xe"
  6. username과 password또한 내가 사용하는 Oracle 계정에 맞게 수정한다. -> 접속정보를 올바르게 맞춰준다.
  7. context.xml파일 내부의 Resource정보를 통해서 DBCP(DataSource ds: dbcp를 관리/유지하는 객체)를 구성한다. 
  8. 톰캣이 특정 서버를 구동하면 가장 먼저 해당 서버에 관련된 context.xml파일을 읽는다.
    1. 평소에 서버를 세팅할 때는 7의 DBCP를 구성하는 context.xml파일의 Rescource 내용을 Servers폴더의 server.xml파일 내부의 GlobalNamingResources태그 내부에 설정해주어야 한다. (서버를 직접 설정하는 경우에만...)
    2. factory = DBCP인 BasicDataSource를 생성하는 부분이다. 
    3. maxActive= 미리 연결해놓은 최대 커넥션 객체 개수. DB에서는 항상 설정한 최대 개수를 유지한다.
    4. maxIdle
    5. maxWait : -1을 주면 에러가 나도 무한정 대기하므로 0 or 1을 준다.
    6. name = 생성된 자원 정보에 이름을 부여하고 추출할 때는 이름(자원을 선택하는 기준)을 통해서 한다.(JNDI)

 

tomcat에서 실행하고자 하는 웹 프로젝트를 add or remove에서 선택하면 Servers파일의 server.xml의 내용 중 실행하고자 하는 context.xml 파일이 생겼다가(add) 소멸(remove)되었다가 한다.

 

Model2 기반의 MVC를 구현하는 방법(table단위로 폴더를 세분화한다.)

Controller

  • 사용자의 요청을 받는다. 
    • 이때 사용자의 요청을 받아서 호출되는 서블릿의 URL-Pattern 즉, annotation이름을 임의로 지정한다. 이는 여러 개의 기능으로 분할될 수 있는 사용자의 요청을 DB의 테이블 단위로 분할하여 각 테이블의 CRUD명령을 수행하기 위함이다. annotaion이름은 반드시 "/"으로 시작한다고 했다. 이는 편집 파일에서 webapp을 가리키는데 /dept/list.do라는 annotation명을 사용하는 것은 사용자의 요청이 들어오는 url주소이름이 저렇게 들어올 것이고, annotaion을 붙인 서블릿이 호출되면 이 서블릿으로부터 forwarding, 즉 호출되는 View(jsp)파일또한 같은 경로(/dept/dept_list.jsp)에 존재해야 한다. 
    • 예를 들어, dept테이블에 대한 crud명령을 수행하고자 할 때, Model2기반이므로 각 기능별로 4개의 컨트롤러(서블릿)를 만드는데, 각 서블릿의 annotation명은 다음과 같다. 
    • /dept/list.do
    • /dept/insert.do
    • /dept/delete.do
    • /dept/update.do
    • 그렇다면 위의 네가지 서블릿의 호출(포워딩)을 받아 출력을 담당하는 jsp파일들은 webapp파일 밑에 dept폴더를 만들어 두고 이 폴더에  모두 저장하면, 각 테이블단위로 기능을 나눠서 저장 및 관리할 수 있다. 
    • 만약 sawon 테이블이 추가로 들어온다면, webapp폴더 하위에 sawon폴더를 만들고, 서블릿의 어노테이션은 /sawon/list.do이런식으로 사용할 수 있을 것이다.
  • Model에게 DB데이터를 가져오라고 명령한다.
  • View에게 가져온 데이터를 출력하라고 명령한다.
    • 바인딩 & 포워딩

Model

  • Controller에게 명령을 받아서 dao가 DB와 미리 연결되어 있는 DataSource객체를 전달받아서 사용하고 연결을 끊는다(close)
  • DB에 있는 내용을 vo로 포장하고 포장한 데이터를 list에 추가해서 다시 controller에게 전달한다.

View

  • Controller로부터 바인딩된 데이터를 jstl과 el표현식을 사용해서 출력한다.

 

정리

프로젝트 생성 부터 DB 연결하여 조회까지

1. 다이나믹 프로젝트 생성

2. WEB-INF폴더 하위의 lib폴더에 jdbc연결, DBCP사용을 위해 필요한 네가지 라이브러리를 넣고, META-INF폴더에 context.xml파일을 넣어준다. 

3. context.xml파일에서 Resource부분을 내가 사용하는 oracle버전에 맞도록 계정과 url을 수정한다. 

4. DBService.java 파일을 작성한다. JDBC와 비슷한 구조이지만, 생성자 부분에 한가지가 추가된다. DBService 객체가 생성될 때, 톰캣이 미리 생성해놓은 자원 정보(DataSource 미리 연결되어 있는 객체)를 Naming방식(context.xml에 있는 name값을 통한 검색)을 통해서 준비하도록 하는 과정이다. 

5. 만들어 놓은 table을 조회하기 위한 vo를 작성한다. 

6. 테이블을 조회하기 위한 dao를 작성한다. 

7. 컨트롤러 역할을 수행할 서블릿을 생성한다. 

8. 출력을 담당할 jsp파일을 생성한다.

 

소스코드

[DBService]

더보기
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
package service;
 
import java.sql.Connection;
import java.sql.SQLException;
 
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
 
public class DBService {
    
    DataSource ds;
    
    
    //single-ton : 객체 1개만 생성해서 사용하자
    static DBService single = null;
 
    public static DBService getInstance() {
 
        //객체가 생성되어 있지 않으면 만들어라.
        if (single == null) {
            single = new DBService();
        }
        //이전에 만들어 놨던 객체를 그대로 반환한다.
        return single;
    }
 
    //외부에서 생성하지 못하도록 접근제한. 객체는 getInstance메소드를 통해서만 생성가능.
    private DBService() {
        // TODO Auto-generated constructor stub
        //JNDI(Java Naming Directory(검색기구) Interface) : 이름으로 검색해서 interface(DataSource)를 얻어내는 기술
        
        //DBService라는 객체가 만들어질 때(생성자의 영역에서), 톰캣이 미리 생성한 자원 정보를 Naming을 통해서 이미 생성된 커넥션 객체들을 가져온다.
        //DataSource 인터페이스를 구현상속한 BasicDataSource를 사용하는 방법이 명시된 DataSource의 객체 ds를 가져오는 과정이다. 
        
        
        try {
            //1. InitialContext 생성 
            InitialContext ic = new InitialContext();
            
            //2. Resource의 저장소 Context정보 구하기 (서버 내부에서 접근할 수 있는 상수 => java:comp/env)
            Context context = (Context)ic.lookup("java:comp/env");//lookup함수의 반환값이 Object이므로 강제로 형변환 해줘야 한다.
            
            //3. Context내의 Resource 정보를 획득한다.
            ds = (DataSource)context.lookup("jdbc/oracle_test");//(강제 형변환)
            
        } catch (NamingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    
    //DBCP에 저장된 커넥션 얻어오기 
    public Connection getConnection() throws SQLException {
        
        
        //DataSource인터페이스로부터 커넥션 객체를 얻어오는 과정
        //DBService를 사용하는 사용자 측에서 에러를 직접 처리하도록 예외를 위임해야 한다.
        return ds.getConnection();
    
    }
}
 
cs

[DeptVo]

더보기
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
package vo;
 
/*
    DB 컬럼명 = Vo속성명 = Web파라미터 name명
*/
 
public class DeptVo {
    int    deptno;
    String dname;
    String loc;
    
    
    
    public int getDeptno() {
        return deptno;
    }
    public void setDeptno(int deptno) {
        this.deptno = deptno;
    }
    public String getDname() {
        return dname;
    }
    public void setDname(String dname) {
        this.dname = dname;
    }
    public String getLoc() {
        return loc;
    }
    public void setLoc(String loc) {
        this.loc = loc;
    }
}
 
cs

[DeptDao]

더보기
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
package dao;
 
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
 
import service.DBService;
import vo.DeptVo;
 
public class DeptDao {
    //single-ton : 객체 1개만 생성해서 사용하자
    static DeptDao single = null;
 
    public static DeptDao getInstance() {
 
        //객체가 생성되어 있지 않으면 만들어라.
        if (single == null) {
            single = new DeptDao();
        }
        //이전에 만들어 놨던 객체를 그대로 반환한다.
        return single;
    }
 
    //외부에서 생성하지 못하도록 접근제한. 객체는 getInstance메소드를 통해서만 생성가능.
    private DeptDao() {
        // TODO Auto-generated constructor stub
    }
    
    
    //목록조회
    public List<DeptVo> selectList() {
 
        List<DeptVo> list = new ArrayList<DeptVo>();
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        String sql = "select * from dept";
 
        try {
            //1. connection 얻어오기
            conn = DBService.getInstance().getConnection();
 
            //2. PreparedStatement 얻어오기
            pstmt = conn.prepareStatement(sql);
 
            //3. ResultSet 얻어오기 
            rs = pstmt.executeQuery();
 
            //4. 포장
            while (rs.next()) {
                //rs가 가리키는 행(레코드)의 값을 읽어 온다.
 
                //Vo로 포장(반복을 1회 돌아서 새로운 데이터를 읽을 때마다 이 레코드를 저장할 vo를 만들어서 포장해햐 한다.)
                DeptVo vo = new DeptVo();
                vo.setDeptno(rs.getInt("deptno"));
                vo.setDname(rs.getString("dname"));
                vo.setLoc(rs.getString("loc"));
                
                
                //list에 추가 
                list.add(vo);
 
            }
 
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();//
        } finally {//반드시 실행하는 구문
 
            try {
 
                //연결되어 있는 상태면 끊어라.(생성 역순으로)
 
                if (rs != null)
                    rs.close(); //3
                if (pstmt != null)
                    pstmt.close();//2
                if (conn != null)
                    conn.close();//1
 
            } catch (SQLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
 
        return list;
    }
}
 
cs

[DeptListAction]

더보기
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
package action;
 
import java.io.IOException;
import java.util.List;
 
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import dao.DeptDao;
import vo.DeptVo;
 
/**
 * Servlet implementation class DeptListAction
 */
 
//url경로(서블릿을 호출하는 URL Pattern) 상의 가장 처음 등장하는 '/'는 편집 파일에서 webapp이다. 
//따라서 아래처럼 annotation명을 작성하면(annotation명은 내가 하고 싶은대로 임의로 작성해도 가능하나, 이러한 annotation명으로 호출당한 
//서블릿으로 부터 forwarding되는 View단의 jsp파일은 같은 폴더 상에 위치 시켜야 한다.) webapp밑의 dept밑의 list.do를 호출하면 이 서블릿이 호출되고,
//이 서블릿에서 포워딩 되는 jsp파일도 반드시 webapp밑의 dept폴더에 있어야 한다. -> dept테이블에 대한 crud명령을 처리하는 기능별로 폴더에 정리
//Model2 구조를 사용하면 여러 개의 서블릿이 존재할텐데, 이러한 여러개의 서블릿(Controller)을 관리하기 편하도록 아래 annotation명처럼 임의로 폴더화만 시켜두고, 출력을 담당하는(서블릿에서 forwarding)되는 jsp만 해당 폴더에 저장하면 된다.
//테이블 단위로 폴더를 나누어서 해당 테이블의 crud명령을 수행하도록 작성한다.
//controller : 서블릿, view : jsp, model : dao(vo, dbservice)
 
@WebServlet("/dept/list.do")
public class DeptListAction extends HttpServlet {
    private static final long serialVersionUID = 1L;
 
    /**
     * @see HttpServlet#service(HttpServletRequest request, HttpServletResponse response)
     */
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //부서목록 요청
        //서블릿이 사용자의 요청을 받고 dao에게 데이터를 가져오라고 함.
        List<DeptVo> list = DeptDao.getInstance().selectList();
        
        //가져온 데이터를 공유 객체에 데이터를 공유함
        //request binding
        request.setAttribute("list", list);
        
        //jsp에게 출력을 명령한다.
        //View로 forward
        RequestDispatcher disp = request.getRequestDispatcher("dept_list.jsp");
        disp.forward(request, response);
    }
 
}
 
cs

[dept_list.jsp]

더보기
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
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
 
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
부서목록 조회...
 
<table border="1" width="400">
    <!-- title -->
    <tr>
        <th>부서번호</th>
        <th>이름</th>
        <th>위치</th>
    </tr>
    
    <!-- 전달받은 list등의 컬렉션 자료구조에 Data가 없는 경우 -->
    <c:if test="${ empty requestScope.list }">
        <tr>
            <td colspan="3" align="center"><font color="red">Data가 없습니다.</font></td>
        </tr>
    </c:if>
    
    
    <!-- 데이터가 있는 경우 -->
    <!-- for(DeptVo vo : list)와 동일하다. -->
    <c:forEach var="vo" items="${list }">
        <tr>    
            <td>${pageScope.vo.deptno }</td>
            <td>${vo.dname }</td>
            <td>${vo['loc'] }</td>
        </tr>
    </c:forEach>
    
    <!-- 
        View(jsp)는 서블릿으로부터 간접적으로 호출당해야 한다. 왜냐하면, 데이터를 서블릿에서 받아서 바인딩하기 때문에
        서블릿을 거치지 않고 jsp파일을 먼저 실행하면 바인딩된 데이터가 없기 때문에 원하는 결과를 출력할 수 없다. 
        그래서 나중에 스프링을 배울때는 jsp를 직접 실행할 수 없고, 간접적으로만 실행할 수 있도록 WEB-INF파일에 저장한다. 
        WEB-INF폴더에 저장을 하면 간접적인 접근만 가능한데 이 때의 간접 접근은 dispatcher 즉 포워딩이다. 
     -->
    
</table>
</body>
</html>
cs

 

 

번외)

  • 깃에서 pull받은 프로젝트가 다이나믹 웹 프로젝트로 인식되지 않는다면?
    • 프로젝트 우클릭 -> project-facets -> dynamic web module을 3.1버전으로 설정한다. 
  • 톰캣 서버 파일은 ignore시켰기 때문에 pull해서 내려받은 협업자는 반드시 직접 톰캣 서버를 생성하고, 프로젝트 우클릭 -> buildpath -> library -> add library -> server runtime에서 사전에 생성한 tomcat서버를 프로젝트에 추가한다.
  • 초기 깃 설정은 서버파일을 ignore 시키고 나머지 모든 파일을 push한다. 
  • 컴파일러 설정하는 법 
  • 프로젝트 우클릭 -> properties -> java compiler -> jdk compilance 1.8로 수정 또는
  • 프로젝트 우클릭 -> properties -> java compiler -> enable project specific settings체크 해제 후 configure workspace settings 클릭 -> 컴파일러 버전을 15에서 1.8로 변경 후 apply

'BackEnd > WEB' 카테고리의 다른 글

AJAX_국비DAY64  (0) 2022.05.31
웹DB_비번, 줄바꿈처리,AJAX_DAY63  (0) 2022.05.30
국비_DAY58  (0) 2022.05.23
JSTL_국비_DAY57  (0) 2022.05.20
EL_국비_DAY56  (0) 2022.05.19