티스토리 뷰
KH 정보교육원 [ Java ]
KH 91일차 - Servlet 고급 ( FrontController / Command 패턴 ) (****)
monimoni 2022. 7. 7. 16:07[ 1. FrontController 패턴 - command에 따라서 다르게 실행 ] (**)
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.");
// =======================================
String contextPath = req.getContextPath();
String requestURI = req.getRequestURI();
String requestURL = req.getRequestURL().toString();
// =======================================
log.info("\t + 0. contextPath : {}", contextPath); // /maven01 or /myapp
// + contextPath가 그냥 /(루트)일 경우 빈문자열이기에, 출력되지 않는다.
// + 즉 그냥 루트일 경우에는 substring할 필요 없이 URI를 사용하면 된다.
// =======================================
log.info("\t + 1. requestURI : {}", requestURI); // /*.do
log.info("\t + 2. requestURL : {}", requestURL); // http://localhost:8080/*.do
// 현재 모든 URL 중 .do로 끝나는 모든 요청을 여기에 집중시키고 있다.
// =======================================
// substring으로 URI에서 컨텍스트 페스를 제외한 문자열을 돌려준다.
// String command = requestURI.substring(contextPath.length());
String command = requestURI;
log.info("\t + 3. command : {}", command); // /*.do
// + command는 contextPath가 그냥 루트일 경우 URI와 동일하게 나온다.
// =======================================
// command에 따라서 로그가 다르게 찍힌다.
switch( command ) {
case "/insert.do":
log.info("\t + 4. insert request.");
break;
case "/update.do":
log.info("\t + 4. update request.");
break;
case "/delete.do":
log.info("\t + 4. delete request.");
break;
case "/select.do":
log.info("\t + 4. select request.");
break;
} // switch
// =======================================
} // service
} // end class
[ 2. Command 패턴 - command에 따라서 다르게 실행 ] (****)
[ 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. EmpDTO 생성 : 사용자가 입력한 값을 뒤로 보내준다, CRUD할 수 있다. - 받고 ]
package org.zerock.myapp.domain;
import lombok.Data;
@Data // @Data = DTO, @Value = VO
public class EmpDTO {
// DTO는 화면에서 입력한 값을 뒤로 보내준다.
private Integer empno; // PK
private String ename; // NULL 허용
private Double sal; // NULL 허용
private Integer deptno; // NULL 허용
} // end class
[ 2 - 3. Service 인터페이스 생성 : 제공할 Service의 틀을 잡는다. ]
package org.zerock.myapp.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.exception.BizProcessException;
public interface Service {
public static final String BIZ_RESULT = "__RESULT__";
public static final String DTO = "__DTO__";
// + 서비스에 따라서 되돌려주는 값이 달라질 수 있기에 Object타입으로 만들었다.
public abstract void execute (HttpServletRequest req, HttpServletResponse res) throws BizProcessException;
} // end interface
[ + 전용 예외 클래스 만들기 ]
package org.zerock.myapp.exception;
import lombok.NoArgsConstructor;
@NoArgsConstructor
public class BizProcessException extends Exception {
private static final long serialVersionUID = 1L;
public BizProcessException(String msg) {
super(msg); // 부모에게 메세지 날린다.
} // constructor : 메세지
public BizProcessException(Exception e) {
super(e); // 예외를 부모에게 넘겨준다.
} // constructor2 : 예외
} // end class
[ 2 - 4. Service 인터페이스 상속받는 service 생성 : 제공할 Service를 생성한다. ]
[ 1 ) Select Service : 조회 전용 ]
package org.zerock.myapp.service;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.domain.EmpVO;
import org.zerock.myapp.exception.BizProcessException;
import org.zerock.myapp.persistence.EmpDAO;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
// select.do 요청을 실제 처리할 비지니스 로직
public class SelectService implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BizProcessException {
log.trace("execute(req, res) invoked.");
// =======================================
// 실제 로직
// =======================================
try {
// 영속계층에서 데이터를 가지고 오는 DAO객체 생성
EmpDAO dao = new EmpDAO();
// + dao의 selectAll메소드를 한 결과를 반환한다.
// + 즉, sql문대로 실행한 결과를 List<EmpVO>타입으로 반환한다.
// 비지니스 수행결과 데이터 발생
List<EmpVO> list = dao.selectAll();
// 공유데이터 영역에 비지니스 제이터를 속성 바인딩시킨다.
ServletContext sc = req.getServletContext(); // Application scope 객체
sc.setAttribute(Service.BIZ_RESULT, list); // 인터페이스에서 정적객체 가지고 오기
} catch(SQLException e) {
throw new BizProcessException(e);
} // try - catch
} // execute
} // end class
[ 2 ) Insert Service : 삽입 전용 ]
package org.zerock.myapp.service;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.domain.EmpDTO;
import org.zerock.myapp.domain.EmpVO;
import org.zerock.myapp.exception.BizProcessException;
import org.zerock.myapp.persistence.EmpDAO;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
public class InsertService implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BizProcessException {
log.trace("execute(req, res) invoked.");
try {
EmpDTO dto = (EmpDTO) req.getAttribute(Service.DTO);
EmpDAO dao = new EmpDAO();
int insertRows = dao.insert(dto);
req.getServletContext().setAttribute(Service.BIZ_RESULT, insertRows);
log.info("\t + insertRows : {}", insertRows);
} catch(SQLException e) {
throw new BizProcessException(e);
} // try - catch
} // execute
} // end class
[ 3 ) Update Service : 수정 전용 ]
package org.zerock.myapp.service;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.domain.EmpDTO;
import org.zerock.myapp.domain.EmpVO;
import org.zerock.myapp.exception.BizProcessException;
import org.zerock.myapp.persistence.EmpDAO;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
public class UpdateService implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BizProcessException {
log.trace("execute(req, res) invoked.");
try {
EmpDTO dto = (EmpDTO) req.getAttribute(Service.DTO);
EmpDAO dao = new EmpDAO();
int updateRows = dao.update(dto);
req.getServletContext().setAttribute(Service.BIZ_RESULT, updateRows);
log.info("\t + insertRows : {}", updateRows);
} catch(SQLException e) {
throw new BizProcessException(e);
} // try - catch
} // execute
} // end class
[ 4 ) Delete Service : 삭제 전용 ]
package org.zerock.myapp.service;
import java.sql.SQLException;
import java.util.List;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.domain.EmpDTO;
import org.zerock.myapp.domain.EmpVO;
import org.zerock.myapp.exception.BizProcessException;
import org.zerock.myapp.persistence.EmpDAO;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
public class DeleteService implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BizProcessException {
log.trace("execute(req, res) invoked.");
try {
EmpDTO dto = (EmpDTO) req.getAttribute(Service.DTO);
EmpDAO dao = new EmpDAO();
int deleteRows = dao.delete(dto);
req.getServletContext().setAttribute(Service.BIZ_RESULT, deleteRows);
log.info("\t + insertRows : {}", deleteRows);
} catch(SQLException e) {
throw new BizProcessException(e);
} // try - catch
} // execute
} // end class
[ 5 ) Unknown Service : 지원되지 않는 서비스 ]
package org.zerock.myapp.service;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.zerock.myapp.exception.BizProcessException;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
@Log4j2
@NoArgsConstructor
// 잘못된 요청(command)에 대한 처리를 수행하는 서비스 객체
public class UnknownReqService implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BizProcessException {
log.trace("execute(req, res) invoked.");
ServletContext sc = req.getServletContext(); // Application scoe 객체
sc.setAttribute(Service.BIZ_RESULT, "Bad Request"); // 인터페이스에서 정적객체 가지고 오기
} // execute
} // UnknownReqService
[ 2 - 5. 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.EmpDTO;
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 - 모든 레코드를 반환
// ====================================
public int insert(EmpDTO dto) throws SQLException {
log.trace("insert({}) invoked.", dto);
try {
Connection conn = dataSource.getConnection();
String sql = "INSERT INTO emp ( empno, ename, sal, deptno ) VALUES ( ?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, dto.getEmpno());
pstmt.setString(2, dto.getEname());
pstmt.setDouble(3, dto.getSal());
pstmt.setInt(4, dto.getDeptno());
// insert문은 영향을 받은 행의 수를 반환한다.
int affectedLines = pstmt.executeUpdate();
try( conn; pstmt; ) {
return affectedLines;
} // try - with - resources
} catch(Exception e) {
throw new SQLException(e);
} // try - catch
} // insert : 삽입문의 경우 삽입된 행의 수를 출력한다.
// ====================================
public int update(EmpDTO dto) throws SQLException {
log.trace("update({}) invoked.", dto);
try {
Connection conn = dataSource.getConnection();
String sql = "DELETE FROM emp SET ename = ?, sal = ?, deptno = ? WHERE empno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, dto.getEname());
pstmt.setDouble(2, dto.getSal());
pstmt.setInt(3, dto.getDeptno());
pstmt.setInt(4, dto.getEmpno());
// insert문은 영향을 받은 행의 수를 반환한다.
int affectedLines = pstmt.executeUpdate();
try( conn; pstmt; ) {
return affectedLines;
} // try - with - resources
} catch(Exception e) {
throw new SQLException(e);
} // try - catch
} // update : 수정문의 경우 수정된 행의 수를 출력한다.
// ====================================
public int delete(EmpDTO dto) throws SQLException {
log.trace("delete({}) invoked.", dto);
try {
Connection conn = dataSource.getConnection();
String sql = "DELETE FROM emp WHERE empno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, dto.getEmpno());
// insert문은 영향을 받은 행의 수를 반환한다.
int affectedLines = pstmt.executeUpdate();
try( conn; pstmt; ) {
return affectedLines;
} // try - with - resources
} catch(Exception e) {
throw new SQLException(e);
} // try - catch
} // delete : 삭제문의 경우 삭제된 행의 수를 출력한다.
// ====================================
} // end class
[ 2 - 6. Servlet 생성 ]
package org.zerock.myapp;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
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.EmpDTO;
import org.zerock.myapp.exception.BizProcessException;
import org.zerock.myapp.service.DeleteService;
import org.zerock.myapp.service.InsertService;
import org.zerock.myapp.service.SelectService;
import org.zerock.myapp.service.Service;
import org.zerock.myapp.service.UnknownReqService;
import org.zerock.myapp.service.UpdateService;
import lombok.Cleanup;
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.");
// =======================================
// 1. ContextPath와 URL/URI(요청)확인
// =======================================
String contextPath = req.getContextPath();
String requestURI = req.getRequestURI();
String requestURL = req.getRequestURL().toString();
// =======================================
log.info("\t + 0. contextPath : {}", contextPath); // /maven01 or /myapp
// + contextPath가 그냥 /(루트)일 경우 빈문자열이기에, 출력되지 않는다.
// + 즉 그냥 루트일 경우에는 substring할 필요 없이 URI를 사용하면 된다.
// =======================================
log.info("\t + 1. requestURI : {}", requestURI); // /*.do
log.info("\t + 2. requestURL : {}", requestURL); // http://localhost:8080/*.do
// 현재 모든 URL 중 .do로 끝나는 모든 요청을 여기에 집중시키고 있다.
// =======================================
// substring으로 URI에서 컨텍스트 페스를 제외한 문자열을 돌려준다.
// String command = requestURI.substring(contextPath.length());
String command = requestURI;
log.info("\t + 3. command : {}", command); // /*.do
// + command는 contextPath가 그냥 루트일 경우 URI와 동일하게 나온다.
// =======================================
// 2. 전송파라미터를 DTO객체로 수집
// =======================================
String empno = req.getParameter("empno");
String ename = req.getParameter("ename");
String sal = req.getParameter("sal");
String deptno = req.getParameter("deptno");
// 수집된 각 전송 파라미터를 DTO 객체에 저장
// DTO 객체는 웹3계층(표현/비지니스/영속 계층)의 뒤로 전달되면서 사용된다.
EmpDTO dto = new EmpDTO();
// null이 아니고 빈문자열도 아니어야 한다.
if( empno != null && !"".equals(empno) ) { dto.setEmpno(Integer.parseInt(empno)); } // if - empno
if( ename != null && !"".equals(ename) ) { dto.setEname(ename); } // if - ename
if( sal != null && !"".equals(sal) ) { dto.setSal(Double.parseDouble(sal)); } // if - sal
if( deptno != null && !"".equals(deptno) ) { dto.setDeptno(Integer.parseInt(deptno)); } // if - deptno
// =======================================
// 3. 요청 URI에 따라, command(요청유형) 결정
// =======================================
// Request Scope 공유 데이터 영역에 DTO 객체를 속성으로 바인딩
// * 주의 * : 모든 Service 객체의 비지니스 로직 수행에 필요한 전송파라미터를
// 전달해주는 DTO 객체를, 또 다른 공유데이터 영역인 "Request Scope"을 통해 전달
// ( accessed by HttpServletRequest )
// -> 비지니스 로직 수행에 필요한 데이터를 가지고 있는 것이 DTO 객체이기 때문에 이를 넘겨줘야 한다.
req.setAttribute(Service.DTO, dto);
// =======================================
// 4. command 에 따라, 적합한 비지니스 서비스 객체 선택 및 비지니스 로직 수행
// =======================================
try {
// command(요청유형)에 따라, 각 요청을 처리하는 서비스 객체의 생성 및 비지니스 로직 수행(execute 메소드)
// * 주의 * : 비지니스 로직 수행 결과 데이터는, 공유데이터 영역인 "Application Scope"(accessed by ServletContext)에 바인딩
switch( command ) {
// 아래의 모든 service 객체에 필요한 전송파라미터를 전달해주는
// DTO 객체는 3번째 공유데이터 영역인 Request Scope을 통해 전달된다.
case "/insert.do":
log.info("\t + 4. insert request.");
new InsertService().execute(req, res); // 객체 생성 후 메소드 실행
break;
case "/update.do":
log.info("\t + 4. update request.");
new UpdateService().execute(req, res); // 객체 생성 후 메소드 실행
break;
case "/delete.do":
log.info("\t + 4. delete request.");
new DeleteService().execute(req, res); // 객체 생성 후 메소드 실행
break;
case "/select.do":
log.info("\t + 4. select request.");
new SelectService().execute(req, res); // 객체 생성 후 메소드 실행
break;
default : // == if-else
log.info("\t + 4. unknown request.");
new UnknownReqService().execute(req, res); // 객체 생성 후 메소드 실행
break;
} // switch
} catch(BizProcessException e) {
;;
} // try - catch
// =======================================
// 응답 문서 준비
// =======================================
res.setContentType("text/html; charset=utf8");
@Cleanup
PrintWriter out = res.getWriter();
out.println("<html><head></head><body>");
// 위의 각 command 별로 수행되는 Service 개체의 execute 메소드의 수행결과를
// 가지고 응답문서를 만들어 준다!
ServletContext sc = req.getServletContext();
Object bizResult = sc.getAttribute(Service.BIZ_RESULT);
out.println("<p>" + bizResult + "</p>");
out.println("</body></html>");
out.flush();
} // service
} // end class
[ 2 - 7. 화면 생성 ]
<!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태그의 경우 8090이라는 다른 사이트로 요청을 보냈으나, 없을 경우 연결되지 못한다. -->
<a href="http://localhost:8090/08Chapter/up.do">수정하기</a><br>
<a href="select.do">조회하기</a><br>
<a href="insert.do?empno=1981&ename=ABCDEF&sal=10000&deptno=10">삽입하기</a><br>
<a href="update.do?empno=1981&ename=ABCDEF&sal=20000&deptno=40">수정하기</a><br>
<a href="delete.do?empno=1981">삭제하기</a><br>
<!-- 링크는 단순히 이동하는 것이 아니라, GET 방식으로 요청을 보낸것이다. (***) -->
<!-- <a href="https://www.naver.com/">NAVER</a><br> -->
<!-- <a href="http://localhost:7000">NETCAT</a><br> -->
</body>
</html>
728x90
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 93일차 - MVC 패턴 (*****) (0) | 2022.07.11 |
---|---|
KH 92일차 - 공유데이터 영역 (***) (0) | 2022.07.08 |
KH 90일차 - Servlet ( JDBC와 커넥션풀로 연결 ) / 패턴 (****) (0) | 2022.07.06 |
KH 89일차 - Servlet / 커넥션 풀 (****) (0) | 2022.07.05 |
KH 88일차 - ServletContext 2 (*****) + 리스너 / 필터 생성 (0) | 2022.07.04 |
댓글