M = Model : 비지니스 로직 수행결과 데이터
V = View : Model 데이터를 이용한, 응답화면을 생성
C = Controller
: MVC 흐름을 제어
: 하나의 Servlet 이다
: 모든 Request Message를 받는 집중화 수행
(적용패턴:Front Controller Pattern)
이를 위해, url-pattern mapping 에 달려있다!
(1) 디렉토리 패턴 : 반드시 '/' 로 시작해야 함
(2) 확장자 패턴: 반드시 *. 확장자로 끝나야 함
출력을 위한 view코드와 로직처리를 위한 자바코드도 함께있다.
빠르게 만들 수 있지만 / 복잡할수록 유지보수성 저하
요청을 받은 서블릿이나 JSP가 자신의 역할을 한 뒤
다른 서블릿과 JSP에게 다음 역할을 위임을 구현하기 위함이다.
Request forwarding 방법(2가지): ==> 화면전환기법이다!!!!
▼리퀘스트 포워딩
-RequestDispatcher객체의 getRequestDispatcher() , forward()메소드
(1)RequestDispatcher 객체의 forward 메소드 이용하는 방법
ex)RequestDispatcher dispatcher = req.getRequestDispatcher(target);
dispatcher.forward(req, res); => URL주소를 변경하지 않음 (****)
@WebServlet("/Request")
public class RequestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 포워딩 예제
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// Step.1 비즈니스 로직수행
// Step.2 비즈니스 데이터 (= Model)을 request scope 공유 데이터 영역에 올려 놓음
request.setAttribute("name", "홍길동");
request.setAttribute("address", "서울");
// -- 응답을 만들어 낼 웹컴포넌트(예: Serlvet)에 요청을 위임 (View)
// Step.3 요청 위임 (by request forwarding)
//
RequestDispatcher dispatcher = request.getRequestDispatcher("/Response");
dispatcher.forward(request,response);
log.info("forwarded");
}// service
}// end class
@WebServlet("/Response")
public class ResponseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String name = (String) request.getAttribute("name");
String address = (String) request.getAttribute("address");
response.setContentType("text/html; charset=EUC-KR");
@Cleanup
PrintWriter out = response.getWriter();
out.print("<html><body>");
out.print("username 값:" + name +"<br>");
out.print("useraddress 값:" + address +"<br>");
out.print("</body></html>");
out.flush();
}// service
} //end class
결과
Request에서 수행했는데 Response와 연계되어 처리값을 보내준다.
▼ 다이렉션 예제2 response.sendRedirect()
(2)Response 객체의 sendRedirect 메소드를 이용하는 방법 => 리다이렉션 기법
ex)response.sendRedirect(target); => target으로 URL주소가 변경됨 (****)
@WebServlet("/RequestRedirect")
public class RequestRedirectServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 포워딩 예제#2
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.trace("service(request, response) invoked",request,response);
// ===============================
// Step.1 비지니스 데이터 (Model)을 Request Scope에 속성 바인딩
// ===============================
request.setAttribute("name", "홍길동");
request.setAttribute("address", "서울");
// ===============================
// Step.2 Redirect 응답 전송
// ===============================
response.sendRedirect("/ResponseRedirect");
log.info("Redirection forwarding 예제");
}// service
}// end class
@WebServlet("/ResponseRedirect")
public class ResponseRedirectServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
// 포워딩 예제#2
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = (String) request.getAttribute("name");
String address = (String) request.getAttribute("address");
response.setContentType("text/html; charset=EUC-KR");
@Cleanup
PrintWriter out = response.getWriter();
out.print("<html><body>");
out.print("username 값:" + name +"<br>");
out.print("useraddress 값:" + address +"<br>");
out.print("</body></html>");
out.flush();
}// service
}// end class
결과
redirect 되었기 때문에 웹 브라우저의 URL값이 ResponseRedirect 로 변경되고, 설정된 속성 (attribute)값을 가져올 수 없다
위 (1), (2)에서 인자값이 "target"은, 요청을 위임받아 나머지역할을 처리할 웹컴포넌트 의미
=> 리퀘스트 포워딩과 리다이렉션의 차이점! (조사)
Request forwarding : 하나의 요청을 처리하기 위해 재요청을 통해 몇개의 컴파운드가 동원되야하는가가 판단기준 (기존요청에 대한 연계)(데이터를 유지)
Redirection : 목적에 맞지 않는 요청에 다른 요청을 준비할 수 있게
재요청한다가 판단기준 (전혀 새로운 요청이 수행)(데이터를 유지하지 않음)
1. DTO로 테이블에 대한 스키마를 일치시켜 주고 받을 수 있는 그릇을 만듬
//POJO
// DTO는 화면상에서 오는 모든 전송 파라미터를 읽고 쓸 수 있게 해줘야함
@Data
public class EmpDTO {
private Integer empno;
private String ename;
private Double sal;
private Integer deptno;
}// end class
2. DAO로 테이블과 연결하여 쿼리문을 통해 CRUD조작 할 수 있게 하는 메소드들을 정의
@Log4j2
@NoArgsConstructor
public class EmpDAO {
private static DataSource dataSource;
static { // JNDI lookup을 통해
try {
// -- WAS가 생성한 DataSource 객체를 획득 하는 방법 #2----
// WAS의 표준 API인, JNDI API를 이용해서, 설정에 의해 자동생성된 데이터 소스 획득
// 1. JNDI tree 의 뿌리(root)에 접근하게 해주는 객체를 획득
Context ctx = new InitialContext(); // 100% 성공 (Web App 안에서 수행만 된다면)
//2. Context 객체를 가지고, 지정된 이름을 가지는 리소스=열매를 찾아서 획득
//java:comp/env/ 는 리소스에 접근하는 규약(루트)
dataSource =(DataSource)ctx.lookup("java:comp/env/jdbc/OracleCloudATP");
log.info("\t this.dataSource: {}",EmpDAO.dataSource);
} catch (Exception e) {
e.printStackTrace();
} // try- catch
} //static initializer
public List<EmpVO> select() throws SQLException{
log.trace("select() invoked");
// Scott 스키마의 `emp` 테이블을 모두 조회해서, 리스트로 반환
// 리스트 컬렉션 요소는 EmpVO 이어야함.
Connection conn = EmpDAO.dataSource.getConnection();
String sql = "SELECT * FROM emp ORDER BY empno";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
//Vector 쓰는 이유는 쓰레드가 많아졌을때를 고려하여 사용한다. 안전성
List<EmpVO> list = new Vector();
try (conn; pstmt; rs;){
while(rs.next()) {
Integer empno = rs.getInt("empno");
String ename = rs.getString("ename");
String job = rs.getString("job");
Integer mgr = rs.getInt("mgr");
Date hireDate = rs.getDate("hireDate");
Double sal = rs.getDouble("sal");
Double comm = rs.getDouble("comm");
Integer deptno = rs.getInt("deptno");
EmpVO vo =new EmpVO(empno, ename, job, mgr, hireDate, sal, comm, deptno);
list.add(vo);
}//while
}// try-with-resources
return list;
} // select
public int delete(EmpDTO dto) throws SQLException {
log.trace("delete({}) invoked", dto);
Connection conn = EmpDAO.dataSource.getConnection();
String sql = "DELETE FROM emp WHERE empno = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, dto.getEmpno());
try (conn; pstmt; ){
return pstmt.executeUpdate(); // sql update 메소드
}// try- with - resources
}// delete
// DTO를 쓰는 이유에 대해 생각해보아라.
public int insert(EmpDTO dto) throws SQLException {
log.trace("insert({}) invoked", dto);
Connection conn = EmpDAO.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());
try (conn; pstmt; ){
return pstmt.executeUpdate(); // sql update 메소드
}// try- with - resources
} // insert
public int update(EmpDTO dto) throws SQLException {
log.trace("update({}) invoked", dto);
Connection conn = EmpDAO.dataSource.getConnection();
String sql = "UPDATE 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());
try (conn; pstmt; ){
return pstmt.executeUpdate(); // sql update 메소드
}// try- with - resources
} // update
}// end class
3. 비즈니스 로직을 수행할 수 있게 하는 클래스를 각각 생성
(1) Service인터페이스
public interface Service {
public static final String DTO = "__DTO__";
public static final String MODEL = "__MODEL__";
public abstract void execute(HttpServletRequest req, HttpServletResponse res)
throws BusinessException;
} // end interface
(2) 유저 정의의 예외 설정
// User-defined Exception 으로, 비즈니스 로직 수행시 오류가 발생하면
// 이 예외를 던지도록 하는데 사용
@NoArgsConstructor
public class BusinessException extends Exception {
private static final long serialVersionUID = 1L;
public BusinessException(String message) {
super(message);
} // constructor
public BusinessException(Exception e) {
super(e);
}// constructor
} // end class
(3) Service 인터페이스를 DAO와 DTO를 사용하여 비즈니스 로직에 맞춰 수행되게 하는 클래스 생성
public class InsertServiceImpl implements Service {
@Override
public void execute(HttpServletRequest req, HttpServletResponse res) throws BusinessException {
log.trace("execute(req,res) invoked");
try { // 비즈니스 로직을 수행하고 , 그 결과를 데이터인 Model 을 Request scope의 바인딩
//Step.1 Req.Scope에서 DTO 객체 획득
EmpDTO dto = (EmpDTO) req.getAttribute(Service.DTO);
// Step.2 획득한 DTO를 이용해서 , DAO의 메소드 호출
EmpDAO dao =new EmpDAO();
// Step.3 비즈니스 로직 수행(신규사원등록)
int deletedRows = dao.insert(dto);
req.setAttribute(Service.MODEL, deletedRows);
} catch (Exception e) {
throw new BusinessException(e);
}// try- catch
}// execute
} // end class
@NoArgsConstructor
@Log4j2
// FrontController를 이용한 MVC 패턴
@WebServlet("*.do")
public class FrontControllerServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// request.setCharacterEncoding("utf8"); // MyFilter 혹은 Servers에 web.xml에서 처리
String empno = request.getParameter("empno");
String ename = request.getParameter("ename");
String sal = request.getParameter("sal");
String deptno = request.getParameter("deptno");
log.info("\t empno: {} , ename: {} , sal: {} , deptno:{}", empno, ename, sal, deptno);
// ----
// 수집된 모든 전송 파라미터를 DTO객체의 필드로 수집(저장)
// 이 DTO(데이터 전달 객체)는 웹 3계층에서 , 앞 -> 뒤로 전달된다.
EmpDTO dto = new EmpDTO();
log.info("dto" + dto);
if(empno != null && !"".equals(empno)) {
dto.setEmpno(Integer.parseInt(empno));
}
dto.setEname(ename);
if(sal != null && !"".equals(sal)) {
dto.setSal(Double.parseDouble(sal));
}// if
if(deptno != null && !"".equals(deptno)) {
dto.setDeptno(Integer.parseInt(deptno));
}// if
// 현재의 Request에 대한, Response가 나가기 전까지 살아있는
// Request Scope에 DTO 객체를 binding해 주어서, 웹 3계층의 어디에서든
//사용할 수 있도록 공유
request.setAttribute(Service.DTO, dto); // 공유 속성 이름 : __DTO__
// ===============================================
//Step.2 Request URI를 획득하여 요청을 식별
// ===============================================
String command = request.getRequestURI();
log.info("command" + command);
// ===============================================
// Step.3 얻어낸 command를 처리할 비지니스 수행 객체를 식별하고 ,처리를 위임(Delegation)
// ===============================================
try {
// command pattern에 따라, 각 Command를 처리할 객체가 반드시 구현할 메소드를
// 인터페이스로 규격화 하고, 이에 맞게 Command 처리객체를 생성할 수 있도록 클래스 구현하는 패턴
switch(command) { // 유형별로 위임 처리
case "/insert.do": //C
new InsertServiceImpl().execute(request, response);
break;
case "/update.do": //R
new UpdateServiceImpl().execute(request, response);
break;
case"/delete.do": //U
new deleteServiceImpl().execute(request, response);
break;
case "/select.do": //D
new SelectServiceImpl().execute(request, response);
break;
default : //Unknown command
new UnknownServiceImpl().execute(request, response);
break;
}// switch
} catch (Exception e) {
throw new ServletException(e);
}
// ===============================================
// Step.4 비지니스 수행결과 데이터인 Model을 함께 전달하여
// 응답결과 화면을 동적으로 생성하도록 View를 호출
// ===============================================
RequestDispatcher dispatcher = request.getRequestDispatcher("/View");
dispatcher.forward(request, response);
log.info("fowarding reqeust into /View");
}// service
}// end class
Step4.에서 보면 View 부분을 처리하기위해 리퀘스트 포워드를 한다.
@WebServlet("/View")
public class ViewServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
log.info("view요");
// ============================================================
// 최종으로 request scope에 공유된 Model(비즈니스 수행데이터)와
// 넘겨받은 request, response를 이용하여, 최종으로 동적인 응답화면 생성 및 전송
// ============================================================
try {
response.setContentType("text/html, charse=utf8");
@Cleanup("close")
PrintWriter out = response.getWriter();
// 각 command(요청유형)별, 서비스 객체의 비지니스 수행 결과 데이터를
// "Request Scope"에서 얻어서, 응답문서 생성에 사용
Object bizResult = request.getAttribute(Service.MODEL);
out.println("<p>"+bizResult+"</p>");
out.flush();
} catch(Exception e) {
throw new ServletException(e);
} // try-catch
}// service
}// end class