딸기말차
[Java web] 8. Oracle JDBC, DispatcherServlet 본문
엔코아 플레이데이터(Encore Playdata) Backend 2기 백엔드 개발 부트캠프 (playdata.io)
백엔드 개발 부트캠프
백엔드 기초부터 배포까지! 매력있는 백엔드 개발자 포트폴리오를 완성하여 취업하세요.
playdata.io
1. Connection Pool
데이터베이스와 연결된 커넥션을 미리 만들어 놓고 이를 pool로 관리하는 것으로, 클라이언트의 요청이 올 때마다 커넥션 풀 내의 커넥션을 이용하고 반환하는 기법이다.
이처럼 미리 만들어 놓은 커넥션을 이용하면 Connection에 필요한 비용이 줄어, DB에 빠르게 접속할 수 있다.
1. 웹 컨테이너(WAS)가 실행되면서 connection 객체를 미리 서버 내 pool에 생성한다.
2. HTTP 요청에 따라 pool에서 connection객체를 가져다 쓰고 반환한다.
3. 즉, pool에 미리 connection이 생성되어 있기 때문에 connection을 생성, DB를 연결하는 비용이 줄어든다.
4. 결과적으로 물리적인 데이터베이스 connection 부하가 줄어든다.
5. 참고로 커넥션을 계속해서 재사용하기 때문에, 무한정 커넥션을 생성할 필요 없이 제한적으로 생성하여 사용한다.
2. JNDI (Java Naming and Directory Interface)
디렉터리 서비스에서 제공하는 데이터 및 객체를 발견하고, 참고(look up) 하기 위한 Java Api이다.
즉, 필요한 자원을 Key, Value 쌍으로 저장한 후, 필요할 때마다 키를 이용해 값을 얻는 방법으로,
대표적인 예시로 클라이언트에서 name, value 쌍으로 전송한 데이터를 request.getParameter()로 가져오는 경우가 있다.
해당 기술을 활용하면, 우리가 연결하고 싶은 데이터베이스의 DB Pool의 이름을 미리 정해줄 수 있다.
우리가 저장해놓은 WAS의 데이터베이스 정보에 JNDI를 설정해 놓으면 웹 애플리케이션은 JNDI만 호출하면 간단해진다.
즉, 톰캣 서버가 Connection Pool 객체를 생성할 때 이 객체에 대한 JNDI 이름을 설정해 둔다면,
웹 어플리케이션에서 DB 연동 작업 시 JNDI 이름으로 접근 해 작업을 수행할 수 있다.
1. 기존의 Java 내에서 DB를 연결하는 코드
private static final String driver = "oracle.jdbc.driver.OracleDriver"; // ojdbc6.jar 필요
private static final String url = "jdbc:oracle:thin:@localhost:1521:XE";
private static final String user = "scott";
private static final String pwd = "tiger";
private void connDB() {
try {
Class.forName(driver);
System.out.println("Oracle 드라이버 로딩 성공");
con = DriverManager.getConnection(url, user, pwd);
System.out.println("Connection 생성 성공");
stmt = con.createStatement();
System.out.println("Statement 생성 성공");
} catch (Exception e) {
e.printStackTrace();
}
}
2. JNDI 도입, Server 폴더 내 context.xml와 DAO 내에 들어가는 변경 된 코드
<Resource
name = "jdbc/oracle"
auth = "Container"
type = "javax.sql.DataSource"
driverClassName = "oracle.jdbc.OracleDriver"
url = "jdbc:oracle:thin:@localhost:1521:XE"
username = "scott"
password = "tiger"
maxActive = "50"
maxWait = "-1"/>
private DataSource dataSource;
public MemberDAO() {
try {
// context.lookup의 반환타입은 Object 이다. 때문에 형변환을 해야한다.
Context context = new InitialContext(); // context.xml
Context envContext = (Context) context.lookup("java:/comp/env"); // <Context> 를 찾기 위한 경로
dataSource = (DataSource) envContext.lookup("jdbc/oracle"); // <Context> 내부 <Resource> 를 찾기 위해
}catch (Exception e) {
e.printStackTrace();
}
}
3. DispatcherServlet
기존에 프로젝트를 구성할 때는 어떤 페이지의 어떤 기능을 실행할 때 무조건 Servlet을 거치게 구성했기 때문에, 각 기능별로 Servlet을 생성해야 했었다.
하지만 이렇게 여러 Servlet을 구성하면 어떤 기능을 호출할 때마다 WAS 서버를 거치게되고, 때문에 프로그램이 느려지게 된다.
이를 해결하기 위해 Spring은 DispatcherServlet이라는 개념을 사용해, 하나의 servlet을 통해서만 요청을 해결할 수 있게 구성되어있다.
Dispatcher Servlet
1. 클라이언트의 request
2. requestURL을 통한 HandlerMapping
3. Handler를 처리할 수 있는 HandlerAdapter 조회
4. HandlerAdapter를 통해 Handler(Controller) 호출
5. Controller에서 호출한 로직을 수행 후 ModelAndView 반환
6. ModelAndView 내 논리주소를 viewResolver로 보내, 물리주소로 변환
7. 변환 된 물리주소로 model을 전달, 최종 view Rending
4. DispatcherServlet 실습_1) VO
어떤 기능요청이 들어왔을 때, 기존엔 해당 기능을 실행하는 페이지에 대응하는 Servlet이 존재했다.
해당 부분을 하나의 Servlet을 사용하여, Servlet내에서 해당 기능을 실행하기 위한 method를 호출하는 방식으로 변경하는 실습을 진행하였다.
DB 레코드에 대응하는 VO 클래스이다.
/*
* VO (value object)
* 1. 조회 된 결과 또는 사용자가 입력한 각각의 데이터를 하나의 객체로 만든다.
* 2. DB의 레코드에 대응된다. (table의 필드명 == vo 객체의 멤버 == form 태그의 name)
* 3. servlet <-> DAO & servlet <-> JSP
*/
public class MemberVO {
private String id;
private String pwd;
private String name;
private String email;
private Date joinDate;
public MemberVO() {
System.out.println("MemberVO 생성자 호출");
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getJoinDate() {
return joinDate;
}
public void setJoinDate(Date joinDate) {
this.joinDate = joinDate;
}
@Override
public String toString() {
return "MemberVO [id=" + id + ", pwd=" + pwd + ", name=" + name + ", email=" + email + ", joinDate=" + joinDate
+ "]";
}
}
5. DispatcherServlet 실습_2) DAO
Select, Insert, Update, Delete 기능들을 구현한 DAO 클래스이다.
public class MemberDAO {
private Connection con;
private PreparedStatement pstmt;
private DataSource dataSource;
public MemberDAO() {
System.out.println("MemberDAO 생성자 호출");
try {
// context.lookup의 반환타입은 Object 이다. 때문에 형변환을 해야한다.
Context context = new InitialContext(); // context.xml
Context envContext = (Context) context.lookup("java:/comp/env"); // <Context> 를 찾기 위한 경로
dataSource = (DataSource) envContext.lookup("jdbc/oracle"); // <Context> 내부 <Resource> 를 찾기 위해
}catch (Exception e) {
e.printStackTrace();
}
}
// 모든 멤버를 가져오는 메서드
public List<MemberVO> findAll() {
List<MemberVO> members = new ArrayList<MemberVO>();
String query = "select * from t_member";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
MemberVO memberVO = new MemberVO();
memberVO.setId(rs.getString("id"));
memberVO.setPwd(rs.getString("pwd"));
memberVO.setName(rs.getString("name"));
memberVO.setEmail(rs.getString("email"));
memberVO.setJoinDate(rs.getDate("joinDate"));
members.add(memberVO);
}
rs.close();
pstmt.close();
con.close(); // Connection Pool에 사용한 Connection 객체 반납
}
catch (Exception e) {
e.printStackTrace();
}
return members;
}
// 새 멤버를 추가하는 메서드
public void addMember(MemberVO memberVO) {
String query = "insert into t_member(ID, PWD, NAME, EMAIL) values (?, ?, ?, ?)";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
pstmt.setString(1, memberVO.getId());
pstmt.setString(2, memberVO.getPwd());
pstmt.setString(3, memberVO.getName());
pstmt.setString(4, memberVO.getEmail());
pstmt.executeUpdate();
pstmt.close();
con.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
// 멤버를 삭제하는 메서드
public void delMember(String id) {
String query = "delete from t_member where id = ?";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
pstmt.setString(1, id);
pstmt.executeUpdate();
pstmt.close();
con.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
// 기존 멤버 정보를 수정하는 메서드
public void modifyMember(MemberVO memberVO) {
String query = "update t_member set PWD = ?, NAME = ?, EMAIL = ? where ID = ?";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
pstmt.setString(1, memberVO.getPwd());
pstmt.setString(2, memberVO.getName());
pstmt.setString(3, memberVO.getEmail());
pstmt.setString(4, memberVO.getId());
pstmt.executeUpdate();
pstmt.close();
con.close();
}
catch (Exception e) {
e.printStackTrace();
}
}
// 특정 멤버를 ID를 통해 조회
public MemberVO findById(String id) {
MemberVO member = new MemberVO();
String query = "select * from t_member where id = ?";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
pstmt.setString(1, id);
ResultSet rs = pstmt.executeQuery();
// 조회 예상값이 하나일 땐 while 빼고 if로 바꿔도 된다.
if(rs.next()) {
member.setId(rs.getString("id"));
member.setPwd(rs.getString("pwd"));
member.setName(rs.getString("name"));
member.setEmail(rs.getString("email"));
member.setJoinDate(rs.getDate("joinDate"));
}
rs.close();
pstmt.close();
}
catch (Exception e) {
e.printStackTrace();
}
return member;
}
// DB 테이블의 총 레코드 개수를 가져오는 메서드
public int countRecords() {
int count = 0;
String query = "select count(*) from t_member";
try {
con = dataSource.getConnection();
pstmt = con.prepareStatement(query);
ResultSet rs = pstmt.executeQuery();
rs.next();
count = rs.getInt(1);
rs.close();
pstmt.close();
}
catch (Exception e) {
e.printStackTrace();
}
return count;
}
}
6. DispatcherServlet 실습_3) Servlet
다음 servlet의 실행 구조는 다음과 같다.
1. get & post로 들어온 요청을 doHandle() 로 보낸다.
2. MemberDAO 객체를 생성해, DB를 연결하고 내부 메서드를 실행할 수 있게 준비한다.
3. request.getParameter("command")를 통해 받은 클라이언트의 요청을 if문을 통해 구별해 실행한다.
4. 기본 페이지 구성을 위해 findAll()을 통해 DB에서 Member 데이터들을 가져와 화면에 뿌려준다.
@WebServlet(description = "회원정보(가입 / 삭제 / 목록)", urlPatterns = "/member2")
public class MemberServlet2 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doHandle(request, response);
}
private void doHandle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
MemberDAO memberDAO = new MemberDAO();
PrintWriter out = response.getWriter();
String command = request.getParameter("command");
if (command != null && command.equals("addMember")) {
MemberVO memberVO = new MemberVO();
memberVO.setId(request.getParameter("id"));
memberVO.setPwd(request.getParameter("pwd"));
memberVO.setName(request.getParameter("name"));
memberVO.setEmail(request.getParameter("email"));
memberDAO.addMember(memberVO);
}
else if (command != null && command.equals("delMember")) {
String id = request.getParameter("id");
memberDAO.delMember(id);
}
else if (command != null && command.equals("modifyMember")) {
MemberVO memberVO = new MemberVO();
memberVO.setId(request.getParameter("id"));
memberVO.setPwd(request.getParameter("pwd"));
memberVO.setName(request.getParameter("name"));
memberVO.setEmail(request.getParameter("email"));
System.out.println(memberVO + " modify");
memberDAO.modifyMember(memberVO);
}
List<MemberVO> members = memberDAO.findAll();
out.print("<html><body>");
out.print("<table border = 1>");
out.print("<tr align='center' bgcolor='lightgreen'>"
+ "<td>아이디</td>"
+ "<td>비밀번호</td>"
+ "<td>이름</td>"
+ "<td>이메일</td>"
+ "<td>가입일</td>"
+ "<td>삭제</td>"
+ "<td>수정</td>"
+ "</tr>");
for(MemberVO m : members) {
out.print("<tr>"
+ "<td>" + m.getId() + "</td>"
+ "<td>" + m.getPwd() + "</td>"
+ "<td>" + m.getName() + "</td>"
+ "<td>" + m.getEmail() + "</td>"
+ "<td>" + m.getJoinDate() + "</td>"
+ "<td><a href='/day2_jdbc/member2?command=delMember&id=" + m.getId() + "'>삭제</a></td>"
+ "<td><a href='/day2_jdbc/memberModify.jsp?id=" + m.getId() +"'>수정</a></td>"
+ "</tr>");
}
out.print("</table>");
out.print("</body></html>");
out.print("<a href='/day2_jdbc/memberForm.html'>새 회원 등록하기</a>");
}
}
7. 20일차 후기
실제 배포되는 서비스를 이용할 때 들어오는 요청마다 일일이 DB를 연결하는데에 자원을 쏟아부으면, 메모리가 굉장히 낭비된다. 때문에 커넥션 풀이라는 공간을 만들어, 자원 사용량을 많이 축소시킬 수 있다.
서블릿을 여러개 사용하면 그만큼 서버와 클라이언트 사이의 문이 많이 생긴 것과 마찬가지이다. 이렇게 되면 프로그램이 무거워지고, 서버의 부하가 늘어난다.
이를 해결하기 위해 Spring은 DispatcherServlet 이라는 단 하나의 서블릿에서, 모든 요청을 해결할 수 있는 구조로 되어있다. Spring 구조의 핵심이라 볼 수 있기 때문에, 반드시 익혀 이 후 개발 과정을 이해하는데 도움이 됐으면 좋겠다고 생각한다.
'Bootcamp > Java web' 카테고리의 다른 글
[Java web] 10. Cookie, Session, Filter, Listener (0) | 2023.07.28 |
---|---|
[Java web] 9. Servlet Context, Servlet Config (0) | 2023.07.27 |
[Java web] 7. Web 복습, Oracle (0) | 2023.07.24 |
[Java web] 6. Mini Project_2 (0) | 2023.07.22 |
[Java web] 5. EL, JSTL, Java Beans (1) | 2023.07.18 |