DB에서 데이터들은 일반적으로 HDD에 저장되는데 레코드를 한번의 I/O Call에 한줄(하나의 데이터 블록)씩 읽어오는 걸 의미한다. 인덱스를 이용한 실행계획일 때 Single Block I/O 방식으로 이용하게 된다. 그 이유는 인덱스는 블록간 논리적 순서는 물리적으로 데이터파일에 저장된 순서가 달라 그 블록들이 논리적 순서로는 한참 뒤쪽에 위치할 수 있습니다. 소량데이터를 읽을때 주로 사용하는 방식입니다.
📝Multi Block I/O
DB에서 데이터들은 일반적으로 HDD에 저장되는데 I/O Call 시점에 인접한 블록들을 같이 읽어 메모리에 적재하는 것을 의미합니다. 테이블 전체를 스캔할때 이 방식을 사용하며 테이블이 클수록 Multiblock I/O 단위도 크면 좋고 많은 데이터 블록을 읽을때 주로 사용합니다. 하지만 OS가 일반적으로 허용하는 I/O 단위가 1MB이면 1MB만큼만 읽습니다.
db file sequential read : Single Block I/O방식으로 I/O요청할때 발생 db file scattered read : Multiblock I/O 방식으로 I/O요청할때 발생
대량의 데이터를 Multiblock I/O방식으로 읽을 때 Single Block I/O보다 성능상 유리한 것은 I/O Call 발생횟수를 줄여주기 때문입니다.
📝바인드 변수
고정적인 쿼리문이 있을 뿐더러 동적으로 쿼리문이 들어가는 경우도 존재합니다. 이렇게 ?와 같이 동적으로 변하는 부분의 변수는 바인드 변수라고 합니다. 예) select * from tab where id = ?
해당 변수의 목적은 Hard Parse 를 줄이기에 있습니다. 바인드 변수를 사용할 경우 변수의 값이 달라져도 같은 문장으로 인식하기 때문에 불필요한 Hard Parse를 줄일 수 있습니다.
SQL 문의 경우 Hard parsing 이 많아지면 자원소모가 많아지며 실행이 느려지게 됩니다.
자바로 설명하겠습니다.
Connection con = null; String query = "select * from shop where id = " + id;
select * from shop where id = 1; select * from shop where id = 2; select * from shop where id = 3;
다른 문장으로 처리가 되기 때문에 Hard Parse를 하게 되어 느려지게 됩니다.
하지만 아래와 같이 바인드변수를 이용할 경우 모두 같은 실행 계획을 세우게 되고 Soft Parse가 되기 때문에 실행계획을 안 세워서 빠르게 결과를 받을 수 있습니다. select * from shop where id = ?
아래와 같은 방식으로 처리할 경우 바인드 변수 처리가 되지 않습니다. String query = "select * from shop where id = " + id; PreparedStatement pstmt = con.prepareStatement(query);
아래와 같은 형식으로 처리해야 바인드변수 처리가 됩니다. 고로 바인드 변수는 무조건 이용하되 라이브러리가 지원해주는 형식으로 사용하는 방법을 찾아서 써야합니다. String query = "select * from shop where id = ?" PreparedStatement pstmt = con.prepareStatement(query); pstmt.setInt(1, id);
Parse해 실행계획을 처리할 경우 CPU 점유율 차이가 20%나 차이가 난다고 합니다. 또한 바인드 변수 사용할 때 해당 바인드 변수를 이용하는 조건절따위에 인덱스가 걸려있어도 형변환으로 인해 인덱스를 안 탈 수도 있기 때문에 주의해야한다.
📝SQL 실행 순서
SQL을 입력 받을시 제일 먼저 실행하려고 문법 오류를 검사한다. 그 후 SQL 이전 실행한 적 있는지 메모리(SHARED POOL)를 검사한다 있다면 기존에 실행한 방식으로 실행한다 이것을 SOFT PARSING이라고 한다. 하지만 그와 같은 기록이 없다면 SQL을 어떤 방식으로 처리할 것인지 실행 계획을 세운다. 이것을 HARD PARSING이라고 한다. 그 이후에 SHARED POOL에 실행 게획을 저장한다.
📝SQL 동작방식
Soft Parse : Parsing→syntax→semantic → Execution
Hard Parse : Parsing → syntax → semantic → Query Transformer →Estimator → Plan Generator → Execution
Query Transformer
PARSING 과정을 거친 SQL은 PARSING 트리 형태로 변형되어 Query Transformer는 넘겨 받은 SQL을 보고 같은 결과를 도출하되 좀 더 나은 실행 계획을 갖는 SQL로 변형이 가능한지 판단해 변환 작업을 수행 예를 들면 복잡한 서브쿼리나 뷰를 사용한 SQL을 일반적인 조인 형태의 SQL로 변환해 실행 계획을 도출하기 좋은 상태로 만든다.
Estimator
Query Transformer를 통해 변환 작업을 마치고난 SQL은 Estimator로 넘겨지게 되는데 이때 수행하는데 드는 총 비용을 계산한다.
Plan Generator
Estimator를 통해 계산된 값들을 토대로 후보군 실행계획 도출 Row Source Generator를 통해 출력이 가능한 코드 형태로 바꾼다.
📝실행시간 (Execution time) vs 패치시간 (Fetch time)
실행 시간은 SQL문을 Parse해 실행 계획을 세워 실행하는데까지 걸리는 시간이다.
패치 시간은 실행해서 나온 결과를 전송하는 데 걸리는 시간을 측정한다 (Binary로 전달하기 때문에 네트워크 연결에 의존한다)
페이지 렌더링 전에 필요한 데이터를 모두 받아서 정의된 Template에 넣어 Html을 그려 클라이언트에 전달해준다 → 서버에서 처리함
📝Client Side Template Engine
페이지가 모두 렌더링 된 상태에서 필요한 부분만 다시 정의하기 위해서 프론트단에서 HTTP 통신해 데이터를 다시 받아와 그려주는 역할을 한다 → 클라이언트에서 요청 후 다시 그려준다
📝Template Engine (템플릿엔진)
HTML만 가지고서 데이터를 받아와 화면에 보여주고 하는 그런 행위는 불가능하다 정해진 템플릿 양식이 있으면 데이터를 받아와 화면에 보여줄 HTML을 재구성해서 보여주는 소프트웨어를 의미한다.
📝템플릿 엔진(Template Engine)의 필요성
많은 코드를 줄일 수 있다
대부분의 Template Engine은 기존의 HTML에 비해서 간단한 문법을 사용한다.
재사용성이 높다
웹페이지 혹은 웹앱을 만들 때 똑같은 디자인의 페이지에 보이는 데이터만 바뀌는 경우가 굉장히 많다.
유지보수에 용이하다
하나의 Template을 만들어 여러 페이지를 렌더링하는 작업에는 또 다른 이점이 있다.
📝템플릿엔진 종류
레이아웃 템플릿 엔진
페이지 마다 include를 두는게 아닌 최상위 레이아웃이 있어서 해당 레이아웃에 Include해서 사용하게 도와준다 페이지 별 Include가 있을 시 헤더 또는 푸터 등의 경로 변경이 있거나 전체적인 레이아웃 구조가 변경될 때 모든 페이지를 다 수정해야하는 불상사를 최상위 레이아웃만 수정해서 간단하게 처리 가능 (모듈화) 예) Apache Tiles
텍스트 템플릿 엔진
템플릿 양식에 적절한 데이터를 넣어 결과를 문서에 출력합니다. 예) JSP, Thymeleaf
초창기 서블릿, JSP는 성능이 너무 안 좋았습니다. 속도 수준이 PHP >= ASP >>>> JSP 수준이였고 JDBC의 성능도 최악 JVM 성능도 최악이었습니다 그러다 보니 High Cost인 DB 연결을 DBCP (Connection Pool)을 도입 및 고도화로 인해 PHP = JSP > ASP 까지 끌어 올렸습니다
📝J2EE & Java EE
J2EE 및 JavaEE의 경우 어플리케이션을 만들기 위해 필요한 스펙의 집합으로 다양한 구성요소가 있습니다 → Servlet, JSP, EJB, JNDI, JMS, JDBC 등
하지만 EJB(Enterprise Java Beans)라는 기술이 핵심인데 사용하다보니 너무 불편했고 그로 인해 사람들을 무료면서 불편사항들을 모두 해결할 수 있는 Spring을 사용하게 됩니다.
📝Spring (MVC 패턴)
JSP의 가장 안 좋은 점은 비즈니스 코드 + 화면 코드 + 모델(POJO)가 한군데 모여있다는 점으로 유지보수가 매우 힘들다는 점이 있습니다. 그 외에 데이터베이스 연동 세션 관리등 많은 문제점을 개선한 Spring FrameWork가 나오게 됩니다.
Spring MVC의 경우 Model View Controller로 분리 시켜서 프로젝트의 유지보수 및 개발 생산성을 높여주는 역할을 합니다. 또한 의존성 주입(DI), IoC를 이용해 더 효율적인 비즈니스 로직을 이용할 수 있게합니다.
MVC 변화 1
// @WebServlet은 URL 접근경로와 이름을 설정해서 URL로 접근할 수 있게 해주는 역할입니다.
@WebServlet(name = "mvcMemberSaveServlet", urlPatterns = "/servlet-mvc/members/save")
public class MvcMemberSaveServlet extends HttpServlet {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
Member member = new Member(username, age);
System.out.println("member = " + member);
memberRepository.save(member);
//Model에 데이터를 보관한다.
request.setAttribute("member", member);
String viewPath = "/WEB-INF/views/save-result.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
Data Model과 로직을 컨트롤러에 두고 View를 JSP로 분리시켜 명확하게 역할이 구분 되지만 중복이 많고 필요하지 않는 코드들도 많이 보입니다.
⚠️단점
forward로 model 정보를 view에 전달하는 코드가 각 controller마다 존재하는 중복 코드
jsp가 아닌 thymeleaf같은 다른 템플릿 엔진으로 변경시 코드 전체 수정 필요
request, response객체가 필요 없는 곳에서도 반드시 사용해야하며 테스트 코드 작성도 어려움
공통코드가 있을 때 forward처럼 각 controller마다 다 입력해야 함
MVC 변화 2
MVC 변화 1의 문제점을 해결하기 위해서는 FrontController 패턴을 도입해 공통코드를 앞에서 다 처리하게끔 한다.
/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV1", urlPatterns = "/front-controller/v1/*")
public class FrontControllerServletV1 extends HttpServlet {
private Map<String, ControllerV1> controllerMap = new HashMap<>();
// controller mapping 관리
public FrontControllerServletV1() {
controllerMap.put("/front-controller/v1/members/new-form", new MemberFormControllerV1());
controllerMap.put("/front-controller/v1/members/save", new MemberSaveControllerV1());
controllerMap.put("/front-controller/v1/members", new MemberListControllerV1());
}
// front controller service (공통 처리)
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("FrontControllerServletV1.service");
String requestURI = request.getRequestURI();
ControllerV1 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
controller.process(request, response);
}
}
/** ──── Controller Interface ──── **/
public interface ControllerV1 {
void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
/** ──── Controller ──── **/
public class MemberListControllerV1 implements ControllerV1 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public void process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
String viewPath = "/WEB-INF/views/members.jsp";
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
FrontController에서 Controller랑 매핑시킬 Map을 만듭니다. 그리고 공통 처리에 필요한 건 service에 작성합니다.
service에서 공통적으로 처리하기 위해 Controller Interface를 상속받아 구체적인 Controller에 구현합니다.
요약하면 사용자가 FrontController에 있는 URL Pattern에 해당하는 URL을 요청하면 service 함수가 작동하고 service에 있는 공통 처리 이후에 controllerMap에서 해당 URL에 해당하는 값을 찾아 해당 상세 controller를 실행(process 함수)시킵니다.
⚠️단점
view로 이동시키는 forward 공통코드 분리 필요
MVC 변화 3
/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV2", urlPatterns = "/front-controller/v2/*")
public class FrontControllerServletV2 extends HttpServlet {
private Map<String, ControllerV2> controllerMap = new HashMap<>();
public FrontControllerServletV2() {
controllerMap.put("/front-controller/v2/members/new-form", new MemberFormControllerV2());
controllerMap.put("/front-controller/v2/members/save", new MemberSaveControllerV2());
controllerMap.put("/front-controller/v2/members", new MemberListControllerV2());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV2 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
MyView view = controller.process(request, response);
view.render(request, response);
}
}
/** ──── MyView ──── **/
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
}
/** ──── Controller Interface ──── **/
public interface ControllerV2 {
MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
/** ──── Controller ──── **/
public class MemberListControllerV2 implements ControllerV2 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public MyView process(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
List<Member> members = memberRepository.findAll();
request.setAttribute("members", members);
return new MyView("/WEB-INF/views/members.jsp");
}
}
dispatcher.forward로 해당 view로 화면 전환시키는 과정이 중복되게 되는데 이걸 분리시켜 공통작업인 FrontController에게 위임합니다. (render 함수) 상세 Controller에서 처리한 view정보를 MyView라는 객체에 담아서 반환해줍니다.
⚠️단점
Request, Response 객체 필요 없을 때도 사용해야 함 → 서블릿 종속 제거
뷰의 공통 Path 중복 제거 → WEB-INF/views
MVC 변화 4
/** ──── Front Controller ──── **/
@WebServlet(name = "frontControllerServletV3", urlPatterns = "/front-controller/v3/*")
public class FrontControllerServletV3 extends HttpServlet {
private Map<String, ControllerV3> controllerMap = new HashMap<>();
public FrontControllerServletV3() {
controllerMap.put("/front-controller/v3/members/new-form", new MemberFormControllerV3());
controllerMap.put("/front-controller/v3/members/save", new MemberSaveControllerV3());
controllerMap.put("/front-controller/v3/members", new MemberListControllerV3());
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String requestURI = request.getRequestURI();
ControllerV3 controller = controllerMap.get(requestURI);
if (controller == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
Map<String, String> paramMap = createParamMap(request);
ModelView mv = controller.process(paramMap);
String viewName = mv.getViewName();
MyView view = viewResolver(viewName);
view.render(mv.getModel(), request, response);
}
private Map<String, String> createParamMap(HttpServletRequest request) {
Map<String, String> paramMap = new HashMap<>();
request.getParameterNames()
.asIterator()
.forEachRemaining(paramName -> paramMap.put(paramName, request.getParameter(paramName)));
return paramMap;
}
private MyView viewResolver(String viewName) {
return new MyView("/WEB-INF/views/" + viewName + ".jsp");
}
}
/** ──── ModelView ──── **/
public class ModelView {
private String viewName;
private Map<String, Object> model = new HashMap<>();
public ModelView(String viewName) {
this.viewName = viewName;
}
public String getViewName() {
return viewName;
}
public void setViewName(String viewName) {
this.viewName = viewName;
}
public Map<String, Object> getModel() {
return model;
}
public void setModel(Map<String, Object> model) {
this.model = model;
}
}
/** ──── MyView ──── **/
public class MyView {
private String viewPath;
public MyView(String viewPath) {
this.viewPath = viewPath;
}
public void render(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
public void render(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
modelToRequestAttribute(model, request);
RequestDispatcher dispatcher = request.getRequestDispatcher(viewPath);
dispatcher.forward(request, response);
}
private void modelToRequestAttribute(Map<String, Object> model, HttpServletRequest request) {
model.forEach((key, value) -> request.setAttribute(key, value));
}
}
/** ──── Controller Interface ──── **/
public interface ControllerV3 {
ModelView process(Map<String, String> paramMap);
}
/** ──── Controller ──── **/
public class MemberSaveControllerV3 implements ControllerV3 {
private MemberRepository memberRepository = MemberRepository.getInstance();
@Override
public ModelView process(Map<String, String> paramMap) {
String username = paramMap.get("username");
int age = Integer.parseInt(paramMap.get("age"));
Member member = new Member(username, age);
memberRepository.save(member);
ModelView mv = new ModelView("save-result");
mv.getModel().put("member", member);
return mv;
}
}
서블릿종속을 제거하기 위해 데이터를 담는 곳을 Request, Response가 아닌 별도의 Model에 담는다. (ModelView = View에 해당하는 모델정보와 View정보가 들어간다) 또한 별도 Model을 분리시킨 ModelView를 가지고 Render하기 위해서 ModelView를 매개변수로 받는 MyView에서 Render함수를 추가 작성합니다.
공통 Path 중복코드를 제거하기 위해 FrontController의 viewResolver를 통해 뷰 공통 Path를 제거시킵니다.
기업환경의 시스템을 구현하기 위한 서버측 컴포넌트 모델이다. 즉, EJB는 애플리케이션의 업무 로직을 가지고 있는 서버 애플리케이션이다. EJB 사양은 Java EE의 자바 API 중 하나로주로 웹 시스템에서 JSP는 화면 로직을 처리하고, EJB는 업무 로직을 처리하는 역할을 한다
스프링과 같이 백엔드 프레임워크라고 생각하면 쉽다
📝JBoss
Jboss란 Red Hat의 자회사인 Jboss가 개발한 Jboss Application Server이다 EJB를 포함해 트랜잭션 처리, 보안 분산 컴퓨팅등의 기능을 제공해주는 서버측 컴포넌트 모델
📝Jboss와 Tomcat의 차이점
Jboss와 Tomcat은 모두 Java Servlet Application 서버지만 Jboss는 훨씬 더 기능이 많다. 이 둘의 가장 큰 차이점은 Jboss가 EJB 및 엔터프라이즈 Java 응용 프로그램에서 작업하는 개발자에게 유용한 기타 기술을 포함하여 완전한 Java Enterprise Edition(JEE) 스택을 제공한다는 것이다
Tomcat은 훨신 더 제한적이다
Jboss가 Servlet Container와 Web server를 포함하는 JEE 스택인 반면 Tomcat은 대부분 Servlet Container와 Web Server이다
📝J2EE
J2EE (Java 2 Platform, Enterprise Edition)는 서버용 Java 플랫폼인 Java EE의 이전 이름 자바 기술로 기업환경의 어플리케이션을 만드는데 필요한 스펙들을 모아둔 스펙 집합 → 프레임워크 JSP, EJB, JDBC, RMI, JNDI 등 제공
📝자카르타 EE
이전에는 J2EE라 불리었으나 버전 5.0 이후로 Java EE로 개칭되었으며 2017년 프로젝트가 이클립스 재단으로 이관됨에 따라 자카르타EE로 변경되었다.