티스토리 뷰
1. 웹 서비스의 웹 3계층
- 1 ) 표현계층 ( Presentation Layer ) : 화면 – 정적(*.html), 동적(*.jsp) ( JSP )
- 2 ) 비즈니스 계층 ( Business Layer ) : Mission-critical Biz. Logic ( Servlet )
- 3 ) 영속성 계층 ( Persistence Layer ) : Oracle Cloud ATP ( Database )
- + Servlet이나 JSP 1개가 위의 3계층을 모두 담당하는 것을 Model 1 Architecture
- + 웹 3계층에 따라서 MVC model에 맞게 웹을 개발하는 것을 Model 2 Architecture
- + MVC란 Model / View / Controller을 의미한다.
2. DAO 패턴 / DTO 패턴 / VO 패턴
- 이 3가지는 데이터베이스를 연동하는 프로그램을 개발할 때 사용되는 개발패턴이다.
- 1 ) DAO 패턴 ( Data Access Object )
- : 데이터베이스를 연동할 때 사용되는 개발패턴 중 하나로 데이터 베이스(테이블)에 대한 조작(CRUD)을 메소드를 제공하는 객체이다.
- 2 ) DTO 패턴 ( Data Transfer Object )
- : 화면UI에서 전송된 요청(전송)파라미터들을 전송 및 뒤로 전달하는 역할을 하는 객체
- 3 ) VO 패턴 ( Value Object )
- : 영속성계층인 DB의 1개의 테이블에 1개의 레코드를 저장하는 객체로, 뒤에서 앞으로 전달하는 역할을 하는 객체이다.
3. FrontController 패턴
- 웹 어플리케이션 개발시 사용자의 요청을 처리하기 위한 최초 진입점을 정의하고 사용하는 패턴이다.
- 이를 통해 사용자의 요청을 하나로 집중시키면, 요청을 분산시켜 발생되는 중복된 코드를 제거할 수 있으며, 사용자의 요청을 일관된 방법으로 관리할 수 있다는 장점이 있다.
- 예시로는 로그인 페이지가 있다.
- FrontController 패턴을 적용할 서블릿에서는 사용자가 어떤 동작을 요청했는지 식별할 수 있게 고려해야 한다.
- 그렇기에, 서블릿에서는 사용자의 요청을 “ http://localhost:8080/context명/식별값 “과 같은 매커니즘으로 식별할 수 있게 지원해줘야 한다.
[ 1. 커넥션 풀로 JDBC와 연동하기 - DML문 실행 ] (****)
package org.zerock.myapp;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import javax.annotation.Resource;
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 javax.sql.DataSource;
import lombok.Cleanup;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/EmpInsert")
public class EmpInsertServlet extends HttpServlet { // 커넥션 풀 사용 ver.
private static final long serialVersionUID = 1L;
// =============================================================================
// JNDI LOOKup을 통해 CP 주소 획득 및 CP 객체의 주소를 변수에 저장
@Resource(name="jdbc/OracleCloudATPWithDriverSpy")
private DataSource dataSource;
// =============================================================================
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(rea, res) invoked.");
// =============================================================================
// 전송파라미터( Request Parameters ) 얻기
// =============================================================================
String empno = req.getParameter("empno");
String ename = req.getParameter("ename");
String sal = req.getParameter("sal");
String deptno = req.getParameter("deptno");
// =============================================================================
// 응답화면 생성 및 전송
// =============================================================================
// 응답문서 준비
res.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = res.getWriter();
out.println("<html><head></head><body>");
// =============================================================================
// JDBC 연동
// =============================================================================
try {
Connection conn = this.dataSource.getConnection();
log.debug("\t + conn : {}", conn);
// 커낵션 풀에서 얻은 커넥션의 정보가 나오기에, URL이 우리가 지정한 jdbcUrl이 나온다.
// 데이터베이스에 값을 넣는 sql문이다. ( DML )
String sql = "INSERT INTO emp ( empno, ename, sal, deptno ) VALUES( ?, ?, ?, ? )";
PreparedStatement pstmt = conn.prepareStatement(sql);
// ?에 값 바인딩하기
pstmt.setInt(1, Integer.parseInt(empno)); // ?의 경우 배열이 아니기에, 1번부터 시작한다.
pstmt.setString(2, ename);
pstmt.setDouble(3, Double.parseDouble(sal));
pstmt.setInt(4, Integer.parseInt(deptno));
// 쿼리를 실행해서 총 몇개가 영향을 받았는지 알려준다.
int affectedLines = pstmt.executeUpdate();
log.info("\t + affectedLines : {}", affectedLines);
try ( conn; pstmt; ){
// 응답문서로 출력
out.println( String.format("<p>affectedLines : %s</p>", affectedLines));
} // try - with - resources
} catch(Exception e) {
throw new IOException(e);
} finally {
out.println("</body></html>");
out.flush();
} // try - catch
} // service
// =============================================================================
} // end class
[ 2. VO 패턴 / DAO 패턴 / DTO 패턴 적용 ] (****)
[ 2 - 1. EmpVo 생성 ]
package org.zerock.myapp.domain;
import lombok.Value;
// + getter / setter 롬복
//@Getter @Setter
// + 생성자 롬복
//@NoArgsConstructor
//@AllArgsConstructor
// + Data 롬복으로 getter, setter, 매개변수가 없는 생성자를 한번에 만들 수 있다. + @toString
// + @Data는 DTO 패턴에서 사용된다. (***)
//@Data
// + VO패턴에서는 읽기 전용이기에, @Value를 통해서 읽기는 가능하지만, 수정은 못하도록 해야 한다.
// + @Value는 VO패턴에서 사용된다. (***)
@Value
public class EmpVO {
private Integer empno; // PK
private String ename; // NULL 허용
private Double sal; // NULL 허용
private Integer deptno; // NULL 허용
// NULL을 포함해야 될 때에는 기본타입이 아닌 Wrapper(참조) 타입을 사용해야 한다. (***)
} // end class
[ 2 - 2. EmpDAO 생성 ]
package org.zerock.myapp.persistence;
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 javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.zerock.myapp.domain.EmpVO;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
public class EmpDAO { // POJO : Plain Old Java Object
// 서블릿이 아니면, 자동으로 JNDI Lookup할 수 없다. -> 수동으로 해야 한다.
// @Resource(name="jdbc/OracleCloudATPWithDriverSpy")
// + Servlet 클래스의 내부에서는 위의 어노테이션으로 자원객체를 JNDI tree로부터
// + 얻어낼 수 있지만, 그 외의 클래스에서는 불가능하다.
// ====================================
private static DataSource dataSource;
// ====================================
static { // JNDI Lookup을 해서, DataSource 객체의 주소를 획득
try {
Context ctx = new InitialContext();
Object obj = ctx.lookup("java:comp/env/jdbc/OracleCloudATPWithDriverSpy");
dataSource = (DataSource) obj;
} catch (NamingException e) { ;; } // try - catch
} // static initializer : 원래는 생성자 안에서 해야 한다.
// ====================================
public List<EmpVO> selectAll() throws SQLException {
// selectAll() : 해당 테이터를 모두 선택해서 반환
log.trace("select() invoked.");
log.debug("\t + this.dataSource : {}", dataSource);
List<EmpVO> list = new ArrayList<>();
// EmpVO 객테타입을 보관하는 리스트(ArrayList)를 생성
try {
Connection conn = dataSource.getConnection();
String sql = "SELECT empno, ename, sal, deptno FROM emp";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
try( conn; pstmt; rs; ) {
while ( rs.next() ) {
Integer empno = rs.getInt("empno");
String ename = rs.getString("ename");
Double sal = rs.getDouble("sal");
Integer deptno = rs.getInt("deptno");
// VO(값 객체)는 1개의 테이블의 1개의 레코드를 읽기 전용으로 보관하는 객체이다.
EmpVO vo = new EmpVO(empno, ename, sal, deptno);
// EmpVO 객체로 하나의 레코드의 값을 가지고 있는 객체를 생성한다.
list.add(vo);
// EmpVO 객체를 리스트에 저장한다.
} // while : 레코드 하나씩 빼오기
} // try - with - resources
} catch(Exception e) {
throw new SQLException(e);
} // try - catch
return list;
// EmpVO 객체를 보관하고 있는 리스트를 반환한다.
} // selectAll - 모든 레코드를 반환
// ====================================
} // end class
[ 2 - 3. Servlet 생성 ]
package org.zerock.myapp;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.List;
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 org.zerock.myapp.domain.EmpVO;
import org.zerock.myapp.persistence.EmpDAO;
import lombok.Cleanup;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("/EmpAllSelect")
public class EmpAllSelectServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
// ========================================
// 이 안에서 DAO 객체를 생성하여, 영속성 계층에서 데이터 획득
EmpDAO dao = new EmpDAO();
// dao가 호출하는 것은 서블릿이다.
@Cleanup("clear")
List<EmpVO> emps = null;
try {
emps = dao.selectAll();
// List는 자원객체이기 때문에, 닫아줘야 한다.
// @Cleanup은 자원객체에 맞게 ( )에 지정하여 닫아줘야 한다.
emps.forEach(log::info);
// log.debug("emps : {}", emps);
} catch (SQLException e) {
throw new IOException(e);
} // try - catch
// ========================================
// 응답문서 준비
// ========================================
res.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = res.getWriter();
out.println("<html><head></head><body>");
out.println("<ol>");
emps.forEach( vo -> {
String tag = String.format("<li> %s, %s, %s, %s </li>", vo.getEmpno(), vo.getEname(), vo.getSal(), vo.getDeptno() );
out.println(tag);
}); // for each
out.println("</ol>");
out.println("</body></html>");
out.flush();
} // service
} // end class
[ 3. FrontController - 모든 요청이 이 서블릿으로 들어온다. ] (****)
[ 3 - 1. html 파일 준비 ]
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FrontController 패턴 연습</title>
</head>
<body>
<!-- 모두 .do로 끝나고 있다. -->
<!-- 모두 실제로는 없는 파일들이다. -->
<a href="insert.do">저장하기</a><br>
<a href="/08Chapter/delete.do">삭제하기</a><br>
<!-- 아래의 a태그의 경우 8090이라는 다른 사이트로 요청을 보냈으나, 없을 경우 연결되지 못한다. -->
<a href="http://localhost:8090/08Chapter/update.do">수정하기</a><br>
<a href="select.do">조회하기</a><br>
<!-- 링크는 단순히 이동하는 것이 아니라, GET 방식으로 요청을 보낸것이다. (***) -->
<!-- <a href="https://www.naver.com/">NAVER</a><br> -->
<!-- <a href="http://localhost:7000">NETCAT</a><br> -->
</body>
</html>
[ 3 - 2. FrontController Servlet 생성 ]
package org.zerock.myapp;
import java.io.IOException;
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 lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
@WebServlet("*.do") // 다른 서블릿이 *.do로 매핑하고 있으면 오류발생할 수 있다.
public class FrontControllerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
log.trace("service(req, res) invoked.");
} // service
} // end class
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 92일차 - 공유데이터 영역 (***) (0) | 2022.07.08 |
---|---|
KH 91일차 - Servlet 고급 ( FrontController / Command 패턴 ) (****) (0) | 2022.07.07 |
KH 89일차 - Servlet / 커넥션 풀 (****) (0) | 2022.07.05 |
KH 88일차 - ServletContext 2 (*****) + 리스너 / 필터 생성 (0) | 2022.07.04 |
KH 87일차 - ServletContext (*****) (0) | 2022.07.01 |