기록은 기억의 연장선

더 많은 것을 기억하기 위해 기록합니다


  • Home

  • Tags

  • Categories

  • Archives

  • Search

[spring] HandlerMapping, HandlerAdapter, HandlerInterceptor

Posted on 2018-01-26 | Edited on 2020-11-02 | In spring | Comments:

HandlerMapping, HandlerAdapter, HandlerInterceptor는 앞서 DispatcherServlet Flow에서 설명했던 확장 포인트 중 컨트롤러와 관련된 부분이다.


핸들러 매핑

HTTP 요청정보를 이용해서 컨트롤러를 찾아주는 기능을 수행한다.
HandlerMapping 인터페이스를 구현해서 생성한다.

1
2
3
public interface HandlerMapping{
HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

DispatcherServlet은 등록된 HandlerMapping 전략들에게 HttpServletRequest를 전달하면서 매칭되는 오브젝트를 찾는다.(이게 곧 세부 컨트롤러!)
스프링이 제공하는 핸들러 매핑 전략은 총 5가지이다.

BeanNameUrlHandlerMapping

HTTP 요청 URL과 빈의 이름을 비교하여 일치하는 빈을 찾아준다.
빈 이름에는 ANT패턴이라고 불리는 *, **, ? 를 이용한 패턴을 넣을 수 있다.

1
2
3
4
5
<!-- hello로 시작하면 모두 여기 매핑된다 -->
<bean name="/hello*" class="HelloController" />

<!-- **는 하나 이상의 경로를 매핑 할 수 있다 -->
<bean name="/root/**/sub" class="SubController" />

하지만 컨트롤러의 개수가 많아지면 URL정보가 XML이나 애노테이션에 분산되어 파악하기 어려우므로, 복잡한 애플리케이션에서는 잘 사용하지 않는다.

ControllerBeanNameHandlerMapping

BeanNameUrlHandlerMapping과 유사하지만 위처럼 빈 이름을 URL 형태로 짓지 않아도 된다는 것이 차이점이다.
빈 이름 앞에 자동으로 /이 붙여져 URL에 매핑된다.

1
<bean name="hello" class="HelloController" /> <!-- /hello에 매핑 -->

ControllerClassNameHandlerMapping

빈의 클래스 이름을 URL에 매핑해주는 매핑 클래스이다.
기본적으로는 클래스 이름을 모두 사용하지만 클래스 이름이 Controller 로 끝날 경우 Controller를 뺀 나머지 이름을 URL에 매핑해준다.

1
2
3
public class HelloController implements Controller{ // /hello에 매핑
// ...
}

SimpleUrlHandlerMapping

URL과 컨트롤러 매핑정보를 한곳에 모아놓을 수 있는 전략이다.
매핑정보는 SimpleUrlHandlerMapping 빈의 프로퍼티에 넣어준다.

1
2
3
4
5
6
7
8
9
10
11
12
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings"> <!-- Properties 타입으로 URL과 빈 이름을 넣어준다 -->
<props>
<prop key="/hello">helloController</prop>
<prop key="/root/**/sub">subController</prop> <!-- ANT 패턴 사용 가능 -->
</props>
</property>
</bean>

<!-- 빈 이름이 위의 value 와 매핑 -->
<bean id="helloController" />
<bean id="subController" />

mappings는 Properties 타입이므로 프로퍼티 타입 포맷을 이용하여 더 간단히 작성할 수 있다.

1
2
3
4
5
6
7
8
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/hello=helloController
/root/**/sub=subController
</value>
</property>
</bean>

매핑정보가 한군데 모여있어 URL을 관리하기 편리하여 대규모 프로젝트에서 선호하기도 한다.
하지만 매핑정보를 직접 작성하므로 오타가 발생할 수도 있다는 단점이 있다.

DefaultAnnotationHandlerMapping

@RequestMapping이라는 애노테이션을 이용해 매핑하는 전략이다.
@RequestMapping은 클래스는 물론 메서드 단위로도 URL을 매핑할 수 있다.
또한 URL외에도 method, parameter, header 등의 정보도 애노테이션을 이용해 매핑에 활용할 수 있다.
굉장히 강력하고 편리한 방법이지만 매핑 애노테이션의 사용 정책, 작성 기준을 잘 마련해놓지 않으면 매핑정보가 금방 지저분해지므로 주의해야 한다.

HandlerMapping 공통 설정정보

아래는 핸들러 매핑에서 공통적으로 사용되는 주요 프로퍼티이다.

  1. order
    핸들러 매핑은 1개 이상을 동시에 사용할 수 있다.
    1개 매핑으로 통일하는것이 가장 이상적이긴하나, 그렇지 않을 상황이 종종 있다.
    2개 이상의 핸들러 매핑이 등록되었는데 URL이 중복 매치될 경우, order 프로퍼티를 통해 매핑 우선순위를 지정할 수 있다.

  2. defaultHandler
    URL을 매핑할 빈을 찾지 못할 경우 자동으로 디폴트 핸들러(컨트롤러)를 선택하게 한다.
    원래 URL을 찾지 못하면 HTTP 404 error가 발생하는데, 이를 디폴트 핸들러로 넘겨 적절한 에러처리를 할 수 있다.

1
2
3
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="defaultHandler" ref="defaultController" />
</bean>
  1. alwaysUseFullPath
    URL 매핑은 기본적으로 애플리케이션 컨텍스트 패스, 서블릿 패스를 제외한 나머지만 가지고 비교한다.
    즉 애플리케이션이 /test에 배포되고, DispatcherServlet URL mapping이 /app/*일 경우 전체 URL은 /test/app/hello 와 같은 형태지만, 핸들러 매핑은 /hello만을 대상으로 삼는다는 의미이다.
    이는 애플리케이션이나 서블릿이 변경되어도 애플리케이션이 영향을 받지 않게 하기 위해서이다.
    하지만 alwaysUseFullPath 옵션을 true로 주면 이를 해제하고 모든 URL을 대상으로 변경할 수 있다.

  2. detectHandlersInAncestorContexts
    기본적으로 애플리케이션 컨텍스트는 계층형 구조를 가지므로, 자식 컨텍스트는 부모 컨텍스트를 참조할 수 있고, 그 반대는 안된다.
    즉 루트 컨텍스트 -> 서블릿 컨텍스트 의 부모-자식 형태의 경우
    서블릿 컨텍스트에 선언된 빈은 루트 컨텍스트에 선언된 빈을 DI할 수 있지만,
    루트 컨텍스트에 선언된 빈은 서블릿 컨텍스트에 선언된 빈을 DI할 수 없다.
    그런데 핸들러 매핑의 경우 이와 좀 다르다.
    핸들러 매핑 클래스는 매핑할 클래스를 현재 컨텍스트, 즉 서블릿 컨텍스트 내에서만 찾는다.
    컨트롤러는 서블릿 컨텍스트에만 두는 것이 바람직하기 때문이다.
    detectHandlersInAcestorContexts 옵션을 true로 주면서 이 방식을 바꿔줄 수 있긴한데,
    이 옵션은 절 대 사용하지 말자. 그냥 스프링의 극단적인 유연성을 보여주기 위한 옵션일 뿐이다.


핸들러 어댑터

HandlerMapping을 통해 찾은 컨트롤러를 직접 실행하는 기능을 수행한다.
핸들러 어댑터는 HandlerAdapter 인터페이스를 구현해서 생성한다.

1
2
3
4
5
6
7
public interface HandlerAdapter{
boolean supports(Object handler);

ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

long getLastModified(HttpServletRequest request, Object handler);
}

HandlerMapping으로 찾은 오브젝트(컨트롤러)를 등록된 HandlerAdaptor들의 supports 메서드에 대입하며 지원 여부를 살핀다.
부합할 경우 handler 메서드를 실행하여 ModelAndView를 리턴한다!
스프링 MVC가 지원하는 컨트롤러는 총 4개이므로, 핸들러 어댑터도 4개이다.

SimpleServletHandlerAdapter(Servlet interface)

표준 서블릿 인터페이스인 javax.servlet.Servlet을 구현한 클래스를 컨트롤러로 사용할 때 사용되는 어댑터이다.
이 방식의 장점은 서블릿 클래스 코드를 그대로 유지하면서 스프링 빈으로 등록할 수 있다는 점인데, 이는 서블릿 코드를 점진적으로 스프링 어플리케이션으로 포팅할 떄 유용하게 사용된다.
이 컨트롤러의 어댑터로는 SimpleServletHandlerAdapter가 사용된다.

참고사항

  1. 서블릿 라이프사이클 메서드인 init, destroy는 실행되지 않는다. 스프링 빈의 init-method나 @PostConstruct를 이용해야 한다.
  2. 이 컨트롤러는 모델과 뷰를 리턴하지 않는다. 서블릿은 원래 Response에 결과를 넣어주는 방식이기도 하고, ModelAndView의 개념을 모른다.
    ※ DispatcherServlet은 ModelAndView 리턴 대신 null을 리턴할 경우 뷰를 호출하는 작업을 생략한다.

HttpRequestHandlerAdapter(HttpRequestHandler interface)

아래의 HttpRequestHandler 인터페이스를 구현한 클래스를 컨트롤러로 사용할 때 사용되는 어댑터이다.

1
2
3
public interface HttpRequestHandler{
void handleRequest(HttpServletRequest request, HttpServletResponse resposne) throws ServletException, IOException;
}

서블릿 인터페이스와 생김새가 유사하다.
서블릿 스펙을 준수할 필요없이 HTTP 프로토콜을 기반으로 한 전용 서비스를 만들려고 할 때 사용한단다… 이정도만 알고 넘어가도 될듯.

SimpleControllerHandlerAdapter(Controller interface)

아래의 Controller 인터페이스를 구현한 클래스를 컨트롤러로 사용할 때 사용되는 어댑터이다.

1
2
3
public interface Controller{
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

스프링의 대표적인 컨트롤러 타입이다.(3.0 전까지)
DispatcherServlet과 주고받는 정보를 그대로 파라미터와 리턴값으로 갖고 있다.
하지만 이 인터페이스를 직접 구현해 컨트롤러를 만드는 것은 권장되지 않으며, 필수 기능이 구현된 AbstractController를 사용하거나 직접 확장한 Controller 클래스를 사용하는 것을 권장한다.
아래는 직접 확장의 간단한 예제이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 기반 컨트롤러
public abstract class SimpleController implements Controller{
private String[] requiredParams; // 필수 파라미터
private String viewName;

public void setRequiredParams(String[] requiredParams) {
this.requiredParams = requiredParams;
}

public void setViewName(String viewName){
this.viewName = viewName;
}

@Override
final public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
if(viewName == null){
throw new IllegalStateException();
}

Map<String, String> params = new HashMap<String, String>();
for(String param : requiredParams){
String value = request.getParameter(param);
if(value == null){
throw new IllegalStateException();
}

params.put(param, value);
}

Map<String, Object> model = new HashMap<String, Object>();

this.control(params, model); // 개별 컨트롤러가 구현할 메서드

return new ModelAndView(this.viewName, model);
}

public abstract void control(Map<String, String> params, Map<String, Object> model) throws Exception;
}

// 개별 컨트롤러
public class TestController extends SimpleController{
public TestController(){
this.setRequiredParams(new String[]{"name", "age"});
this.setViewName("/WEB-INF/view/test.jsp");
}

@Override
public void control(Map<String, String> params, Map<String, Object> model) throws Exception {
// make model using params...
}
}

기계적으로 Controller 인터페이스의 handleRequest 메서드를 사용하지 말고 위와 같이 클래스를 확장하여 사용하는 것이 좋다.

AnnotationMethodHandlerAdapter

앞의 핸들러 어댑터들과 달리 호출하는 컨트롤러의 타입이 정해져 있지 않다.
클래스와 메서드에 붙은 애노테이션, 메서드 이름, 파타미터, 리턴타입에 대한 규칙 등을 조합하고 분석해서 컨트롤러를 선별한다.
또한 다른 컨트롤러와 다르게 컨트롤러 하나가 여러 URL에 매핑될 수 있다.(이때까지는 1개의 URL에 1개의 컨트롤러가 매핑되었다)
이는 메서드 단위로 URL매핑이 가능하기 때문이다.
이 AnnotationMethodHandlerAdapter는 다른 핸들러 어댑터와는 다르게 DefaultAnnotationHandlerMapping 핸들러 매핑과 같이 사용해야 한다.
두 가지 모두 동일한 애노테이션을 사용하기 때문이다.
이 방식은 매우 강력하고 작성이 간결한 대신 꽤나 많은 규칙이 존재하고, 이를 잘 숙지하고 사용해야 한다.
@RequestMapping 바로가기


핸들러 인터셉터

핸들러 매핑은 요청정보를 통해 컨트롤러를 찾아주는 기능 외에 인터셉터를 적용해주는 기능 또한 제공한다.
핸뜰러 인터셉터는 DispatcherServlet이 컨트롤러를 호출하기 전과 후에 요청, 응답을 가공할 수 있는 일종의 필터이다.
핸들러 매핑은 DispatcherServlet으로 부터 매핑 작업을 요청받으면 등록된 핸들러 인터셉터들을 순서대로 수행하고 컨트롤러를 호출한다.
등록된 핸들러 인터셉터가 없다면 컨트롤러를 바로 호출한다.

구현

핸들러 인터셉터를 만들고자 할 때는 아래의 HandlerInterceptor 인터페이스를 구현해야 한다.

1
2
3
4
5
6
7
8
9
public interface HandlerInterceptor {
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception;

void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception;
}
  1. preHandle
    컨트롤러가 호출되기 전에 실행된다. 파라미터 handler는 컨트롤러 오브젝트이다.
    리턴값이 true이면 다음 인터셉터로 진행되고, false일 경우 다음 인터셉터들을 실행되지 못한다.

  2. postHandle
    컨트롤러를 호출한 후에 실행된다. ModelAndView가 제공되므로 작업결과를 참조하거나 조작할 수 있다.

  3. afterCompletion
    모든 작업(뷰 생성까지)이 완료된 후 실행된다.

적용

적용할 핸들러 인터셉터들을 핸들러 매핑 클래스의 속성으로 지정해줘야 하므로, 핸들러 매핑 클래스를 빈으로 등록해줘야 한다.
이후 interceptors 프로퍼티를 이용하여 인터셉터를 등록한다.

1
2
3
4
5
6
7
8
9
10
11
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping">
<property name="interceptors">
<list>
<ref bean="firstInterceptor" />
<ref bean="secondInterceptor" />
</list>
</property>
</bean>

<bean name="firstInterceptor" class="~~~FirstInterceptor" />
<bean name="secondInterceptor" class="~~~SecondInterceptor" />

보다시피 인터셉터들은 핸들러 매핑 단위로 등록된다.
즉 작성한 인터셉터들을 여러개의 핸들러 매핑에 적용시키고 싶으면 핸들러 매핑 빈 마다 반복적으로 다 등록해줘야 한다.
물론 인터셉터 빈은 한개만 등록하면 된다.

핸들러 인터셉터 vs 서블릿 필터

보다시피 핸들러 인터셉터는 서블릿 필터와 기능이 유사하지만 조금 차이가 있으니 선택에 주의를 기울여야 한다.

서블릿 필터는 web.xml에 등록하면서 웹 애플리케이션으로 들어오는 모든 요청에 적용할 수 있다는 장점이 있다.
반면에 스프링의 빈이 아니라는 점과, 핸들러 인터셉터보다 정교한 컨트롤이 어렵다는 단점이 있다.

핸들러 인터셉터는 특정 핸들러 매핑에 제한된다는 단점이 있지만,
인터셉터를 스프링 빈으로 등록할 수 있고 ModelAndView를 컨트롤 하는 등 더욱 정교한 컨트롤이 가능하다.

프로젝트의 상황에 따라 적절한 선택을 하는것이 좋다.

From
Read more »

[java] final class

Posted on 2018-01-22 | Edited on 2020-11-02 | In java | Comments:

관련글

https://hashcode.co.kr/questions/388/자바에서-final-클래스는-어디에-사용하나요

이 클래스는 상속을 염두해두지 않은 클래스라는 것을 알려줌으로써,
해당 클래스를 상속받아 예기치 못하는 일이 일어나는 것을 막을 수 있다.

Read more »

[spring] DispatcherServlet Flow

Posted on 2018-01-21 | Edited on 2020-11-02 | In spring | Comments:

스프링 웹 기술은 MVC 아키텍쳐를 근간으로 한다.

MVC 아키텍쳐
정보를 담은 모델(M), 화면 출력 로직을 담을 뷰(V), 제어 로직을 담은 컨트롤러(C)가 서로 협력하여 하나의 웹 요청을 처리하고 응답을 만들어내는 구조이다.

MVC 아키텍처는 기본적으로 프론트 컨트롤러 패턴과 함께 사용된다.

프론트 컨트롤러 패턴
클라이언트의 요청을 먼저 받고 공통적인 작업을 수행 후, 나머지 작업을 세부 컨트롤러로 위임해주는 방식

스프링은 DispatcherServlet 이라는 프론트 컨트롤러를 사용하며, 이는 스프링 MVC의 핵심이다.


DispatcherServlet flow

DispatcherServlet Flow

1) DispatcherServlet의 HTTP 요청 접수

들어오는 http 요청이 DispatcherServlet에 할당된 것이라면 이를 받는다.
대체로 아래와 같이 web.xml에 정의되어 있다.

1
2
3
4
5
6
7
8
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

요청을 받은 DispatcherServlet은 공통적으로 진행해야 할 전처리 작업이 있다면 이를 먼저 수행한다.

2) DispatcherServlet에서 컨트롤러로 HTTP 요청 위임

DispatcherServlet은 들어온 http 요청 정보(URL, parameter, method 등)을 참고로 해서 어떤 컨트롤러에게 작업을 위임할 지 결정한다.
DispatcherServlet은 프론트 컨트롤러이므로 세부 컨트롤러를 직접 찾지않고 HandlerMapping, HandlerAdapter라는 전략에 해당 행위를 위임한다.(자세한 내용은 위 링크에서 확인)
지금은 그냥 전달받은 HttpServletRequest, HttpServletResponse를 넘겨주면 세부 컨트롤러를 찾아준다 정도만 기억해도 된다.

3) 컨트롤러의 모델 생성과 정보 등록

컨트롤러는 사용자 요청을 해석하여 비즈니스 로직을 수행하고 결과를 받아온 뒤, 모델에 넣는다.
(모델은 뷰에 뿌려줄 정보를 담은 key, value 형태의 맵이다.)

MVC 패턴의 장점은 모델과 뷰가 분리되었다는 것이다. 같은 모델이라도 뷰만 바꿔주면 전혀 다른 방식으로 모델의 정보를 출력시킬 수 있다.
예를 들어 jsp뷰를 선택하면 html, 엑셀뷰를 선택하면 엑셀, pdf뷰를 선택하면 pdf로 모델정보를 출력할 수 있다.

4) 컨트롤러의 결과 리턴 : 모델과 뷰

모델이 준비되었으므로 뷰를 선택한다.
뷰 오브젝트를 직접 선택할 수도 있지만 보통은 뷰 리졸버를 사용므로 뷰의 논리적인 이름만을 리턴하면 된다.
컨트롤러가 최종적으로 리턴해주는 정보는 모델과 뷰, 2가지이다.
이를 리턴해주고 컨트롤러의 역할은 끝이난다.

5-6) DispatcherServlet의 뷰 호출과 모델 참조

다시 DispatcherServlet으로 넘어왔다.
뷰 오브젝트에 모델을 넘겨주며 최종 결과물을 생성해달라고 요청한다.
생성된 최종 결과물은 HttpServletResponse 오브젝트에 담긴다.

7) HTTP 응답 돌려주기

DispatcherServlet은 공통적으로 진행해야 할 후처리 작업이 있는지 확인하고 이를 수행한다.
수행이 끝나면 HttpServletResponse에 담긴 최종 결과를 서블릿 컨테이너에게 돌려준다.
컨테이너는 이 정보를 HTTP 응답으로 만들어 사용자의 브라우저나 클라이언트에 전송하고 작업을 종료한다.


DispatcherServlet의 변경 가능한 전략

위에 나와있듯이, DispatcherServlet은 하나의 요청에 대해 상당히 많은 작업들을 거친다
(하나의 요청에 HandlerMapping 거치고 HandlerAdapter 거치고 ViewResolver 거치고 등등…)
이 작업들을 전략이라고 부른다. 기본적으로 default 전략들이 있고, 별다른 설정을 하지 않는다면 이 기본 전략들을 사용하여 요청을 수행하게 된다
중요한것은, 스프링의 DispatcherServlet은 전략패턴이 잘 적용되어 있으므로 이 전략들을 아주 쉽게 확장(변경)할 수 있다는 점이다(!!)
간단하게 확장하고 싶은 전략들을 빈으로 등록만 해두면, DispatcherServlet이 플로우를 수행하면서 해당 전략을 기본전략 대신해서 실행하게 된다(등록된 확장 전략들이 있는가 체크하고 있으면 수행, 없으면 기본 전략 수행)

DispatcherServlet은 스프링이 관리하는 오브젝트가 아니므로 직접 DI 하는 방식으로 전략이 확장되는 것은 아니다.
DispatcherServlet은 기본적으로 DispatcherServlet.properties 파일을 통해 설정을 초기화하고,
내부적으로 가지고 있는 어플리케이션 컨텍스트를 통해 확장 가능한 전략이 있나 찾은 뒤, 이를 가져와 디폴트 전략을 대신해서 사용하는 방식이다.

아래는 확장 가능한 전략들과, default로 등록된 전략들이다.

HandlerMapping

URL과 요청 정보를 기준으로 어떤 컨트롤러를 사용할지 결정한다.

  • default
    1. BeanNameUrlHandlerMapping
    2. DefaultAnnotaionHandlerMapping

HandlerAdapter

핸들러 매핑으로 선택된 컨트롤러를 직접 호출한다.

  • default
    1. HttpReqeustHandlerAdapter
    2. SimpleControllerHandlerAdapter
    3. AnnotaionMethodHandlerAdapter

HandlerExceptionResolver

예외가 발생했을 때 이를 처리하는 로직을 갖고 있다.
예외 발생 시 에러페이지 출력, 로그 전송등의 작업은 개별 컨트롤러가 아닌 프론트 컨트롤러의 역할이다.
DispatcherServlet은 등록된 HandlerExceptionResolver중에서 발생한 예외에 적합한 것을 찾아 예외처리를 위임한다.

  • default
    1. AnnotaionMethodHandlerExceptionResolver
    2. ResponseStatusExceptionResolver
    3. DefaultHandlerExceptionResolver

ViewResolver

컨트롤러가 리턴한 논리적인 뷰 이름을 참고하여 적절한 뷰 오브젝트를 찾아준다.

  • default
    1. InternalResourceViewResolver
    2. UrlBasedViewResolver

LocaleResolver

지역정보를 결정해주는 전략이다.
디폴트는 헤더 정보를 보고 지역정보를 설정한다.
파라미터나 쿠키, xml 설정등을 통하게 할 수 있다.

  • default
    1. AcceptHeaderLocaleResolver

RequestViewNameTranslator

컨트롤러에서 뷰 오브젝트나 이름을 제공하지 않았을 경우 URL 요청정보를 참고해서 자동으로 뷰 이름을 생성해주는 전략이다.

  • default
    1. DefaultRequestToViewNameTraslator

전략의 변경을 위해 빈을 직접 등록하게 되면 해당 전략의 default 전략들이 모두 무시되므로, 유의해야 한다.
또한 디폴트 전략에 추가 옵션을 주고 싶으면 해당 빈을 등록하면서 옵션을 줘야 한다.

참고 : 이일민, 『토비의 스프링 3.1』, 에이콘출판(2012)

Read more »

[java] class, interface 상속

Posted on 2018-01-21 | Edited on 2020-11-02 | In java | Comments:

class

  1. 기본적인 오버라이딩

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Parent{
    Integer test(){
    return 0;
    }
    }
    class Child extends Parent{
    Integer test(){
    return 1;
    }
    }

    // child.test() == return 1;
  2. java는 기본적으로 이름이 같은데 리턴타입이 다른 메서드가 있을 수 없음. 뭘 호출하는지 알 수 없기 때문에.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Parent{
    Integer test(){
    return 0;
    }
    }
    class Child extends Parent{
    String test(){ // error
    return "1";
    }
    }
  3. 필드의 경우 그냥 같은 이름은 무시됨

    1
    2
    3
    4
    5
    6
    7
    8
    class Parent{
    Integer test = 0;
    }
    class Child extends Parent{
    String test = "1";
    }

    // child.test == 1;

interface

상수랑 추상메서드만 올 수 있음. 키워드는 자동으로 생략됨.

1
2
3
4
5
6
7
8
interface Team{
// public static final int memberNumber 와 동일
// public static final은 생략해도 자동으로 붙음
int memberNumber = 11;

// public abstract String printMembers() 와 동일
String printMembers();
}
  1. 상속은 기본적인 클래스 상속과 동일함. 특이점은 다중 상속이 된다는 것임.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    interface A{
    int a();
    }
    interface B{
    int b();
    }

    // 2개 구현
    class Impl implements A, B{
    @Override
    public int a(){
    return 10;
    }

    @Override
    public int b(){
    return 20;
    }
    }

    만약 메서드명이 겹치면 하나만 선언해도 됨

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    interface A{
    int a();
    }
    interface B{
    int a();
    }

    // 1개 구현
    class Impl implements A, B{
    @Override
    public int a(){
    return 10;
    }
    }

    같은 이름이지만 리턴타입이 다른 메서드를 가진 인터페이스를 다중 상속할 수 없음.
    언급했듯이 자바는 기본적으로 동일한 이름에 다른 리턴타입의 메서드를 허용하지 않음.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface A{
    int a();
    }
    interface B{
    String a();
    }

    class Impl implements A, B{ // error
    }
  2. 인터페이스 끼리 상속도 됨

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    interface A{
    int a();
    }
    interface B extends A{
    int b();
    }

    // 2개를 구현해야함
    class Impl implements B{
    @Override
    public int a(){
    return 10;
    }

    @Override
    public int b(){
    return 20;
    }
    }

    인터페이스도 오버라이딩 되는데, default 메서드를 사용할때나 의미있지 평소에는 오버라이딩이 의미없음.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    interface A{
    default int a(){
    return 10;
    }
    }
    interface B extends A{
    default int a(){
    return 20;
    }
    }

    // impl.a() == 20

    인터페이스 상속은 다중 상속을 지원함

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    interface A{
    int a();
    }
    interface B{
    int b();
    }
    interface C extends A, B{
    int c();
    }

    // 3개를 구현해야함
    class Impl implements C{
    @Override
    public int a(){
    return 10;
    }

    @Override
    public int b(){
    return 20;
    }

    @Override
    public int c(){
    return 30;
    }
    }
  3. 필드는 구현체를 따라가지 않음. 기본적으로 static 이기 때문.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    interface A{
    int a = 10;
    }
    interface B extends A{
    int a = 20;
    }
    class Impl implements B{
    }

    public class Main{
    @Test
    public void test(){
    A a = new Impl();
    System.out.println(a.a); // 10
    B b = new Impl();
    System.out.println(b.a); // 20
    }
    }

이만하면 되지 않았을까…

Read more »

[db] database connection pool

Posted on 2018-01-18 | Edited on 2020-11-02 | In db | Comments:

database connection pool 정의

https://www.holaxprogramming.com/2013/01/10/devops-how-to-manage-dbcp/

데이터베이스 작업에서 가장 비용이 많이 발생하는 부분은 커넥션을 생성하여 맺는 부분
db 작업이 일어날 때 마다 이렇게 커넥션을 생성하고 반납하면 매우 낭비가 크므로,
이러한 커넥션들을 미리 생성해서 풀에 담아두고 필요할 때 마다(db 요청이 있을 때 마다) 꺼내쓰도록 함
이를 커넥션 풀이라고 하고, Apache Commons DBCP를 사용
(정확히 어떻게 동작하는지 모르지만 LinkedList에 저장해놓고 적절히 반환하고 돌려받고 하는 듯 하다)

커넥션 풀 관련해서 여러가지 속성들(maxActive, maxIdle 등)이 있지만
이것보다 실제 커넥션 최대 개수가 성능상 가장 중요한 이슈이다

커넥션 개수를 설정하는 방법은
DBMS가 수용할 수 있는 커넥션 개수를 측정한 다음에, WAS 하나가 사용할 커넥션 풀 개수를 구하면 된다.
그리고 기본적으로 WAS의 Thread 수는 DBMS의 Connection 수보다 많아야 한다.
모든 작업이 DB를 필요로 하진 않기 때문이다.
쓰레드가 DBCP보다 한 10개 정도 많게 하면 적당하다.

DB 커넥션 풀 100개
어플리케이션이 10개라면 각각 커넥션 풀 10개
어플리케이션 각각 쓰레드풀 20개

Read more »

[db] jdbc

Posted on 2018-01-13 | Edited on 2020-11-02 | In db | Comments:

JDBC

java에서 mysql을 사용하기 위한 규약(?)
JDBC는 그냥 껍데기(interface)일 뿐이고, DBMS 벤더들이 그 규약을 구현한 Driver 들을 제공한다.
JDBC URL이 있다. 이것도 JDBC 규약에 있는 표준이다. jdbc:mysql:// 스트링은 고정이다.

요즘은 커넥션 풀을 사용하므로 아래의 코드가 익숙하지는 않곘지만…
Class.forName(driverClass).newInstance(); 를 통해 드라이버를 JVM으로 로드하고,
DriverManager.getConnection()으로 커넥션을 얻는다.
사용한 커넥션은 바로바로 반납해주는 것이 좋다.

Statement VS PreparedStatement

기본적으로 JDBC 프로그래밍을 하면 Statement가 실행되는 과정은 아래와 같다.

1
요청 -> 쿼리 분석 -> 최적화 -> 권한요청 -> 실행

쿼리 분석과 최적화에서 시간이 꽤 걸리는데,
PreparedStatement는 이 과정을 미리 메모리에 저장해두고 다음 요청에서 바로 사용함으로써 시간을 많이 줄일 수 있게 된다.
(PreparedStatement는 파라미터 바인딩을 지원하므로 완벽하게 똑같은 쿼리가 아니어도 상관없다.)

또한 PreparedStatement는 전달되는 파라미터 앞 뒤로 SQL injection에 문제될 문자들을 검사하고 escape 처리하기 때문에 개발자가 직접 처리해줘야 하는 번거로움이 없다.

Read more »

[java] 예외처리

Posted on 2018-01-12 | Edited on 2020-11-02 | In java | Comments:

예외란 프로그램 실행 도중 발생하는 문제 상황을 얘기합니다.
따라서 컴파일 시 발생하는 문법적인 오류는 예외의 범주에 포함되지 않습니다.
예를 들면 아래와 같은 상황이 있습니다.

1
2
3
4
public static void main(String[] args){
String str = null;
System.out.println(str.length());
}

이 상황은 문법적으로는 문제가 없으므로, 컴파일 오류가 발생하지 않습니다.
그런데 실행하면

exception-result1

와 같이 Exception이 발생합니다.

str변수에서 length 메서드를 호출한건데, str에는 현재 null이 들어가 있으므로(주소값이 없으므로)
비어있는 주소를 참조하여 메서드를 실행하려 했다… 뭐 이런의미로 NullPointerException이 발생한 겁니다.

근데 보다시피 해당 NullPointerException은 java.lang 패키지 내에 있는 클래스네요.
이렇듯 Exception라고 별 다른게 아니라 다 클래스들입니다. 예외적인 상황에 따라 대부분의 클래스가 정의되어 있고,
약속된 예외 상황에 따라 해당 예외 클래스가 발생합니다.

몇가지 예를 볼까요

  • NullPointerException : 참조변수가 null인 상황에서 메서드를 호출하는 상황
  • NegativeArraySizeException : 배열 선언 과정에서 배열의 크기를 음수로 지정하는 상황
  • ClassCastException : 허용되지 않는 형변환을 하는 상황

이렇듯 대부분 상황에 따라 정의되어 있습니다.

예외 클래스의 계층도

예외도 결국 클래스라고 했습니다. 그러므로 각 클래스간 계층이 존재하고, 이에 따라 예외의 종류도 나뉩니다.

java-exception

보다시피 Throwable이 예외 클래스의 최상위 클래스입니다.
그리고 아래에 Error, Exception 2가지 클래스가 있는데, 이 2개의 클래스가 예외의 큰 범주입니다.

Error

단순히 예외라고 하기에는 심각한 오류의 상황을 표현하는 예외입니다.
위의 그림에서 OutOfMemoryError, StackOverflowError 등이 보이시죠? 다들 많이 보셨을겁니다.
한 단계 올라가보면 VirtualMachineError로 자바 가상머신에 문제가 생겼음을 알려주고 있습니다.
한 단계 더 올라가보면 Error 클래스가 보이지요. 이처럼 심각한 오류들은 다 Error 예외의 하위 예외로 정의되어 있습니다.
이건 뭐… 저희가 할 수 있는 특별한 방법이 없으므로 그냥 프로그램이 종료되도록 놔두는 수밖에 없습니다.
종료 후에 원인을 찾고 해결하던가 해야합니다. 당장 애플리케이션 단에서는 저희가 조치를 취할 방법이 없습니다.

Exception

일반적으로 우리가 마주하게 되는 예외들로, 저희가 직접 처리할 수 있는 대부분의 예외들을 말합니다.
Exception 내에서도 또 종류가 2가지로 나뉩니다.

예외 처리 방법

예외처리의 대상이 되는 Exception 예외의 하위 예외를 대상으로 하며, 처리 방법에는 2가지가 있습니다.

try ~ catch [ ~finally ]

사용자가 직접 예외를 처리하는 방법입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args){
try{
String str1 = null;
String str2 = "asd";

System.out.println(str1.length());
System.out.println(str2.length());
} catch(NullPointerException e){
System.out.println("참조변수의 값이 NULL 입니다.");
} catch(Exception e){
System.out.println("다른 예외가 발생하였습니다.");
} finally{
System.out.println("마지막에 항상 실행되는 부분입니다.");
}
}

예외가 발생할 수 있는 부분을 try 구문으로 감싸고,
예외가 발생 시 발생 시점으로 부터 더 이상 try 부분의 코드는 진행되지 않고 catch 구문으로 들어가게 됩니다.

catch의 파라미터는 try 구문에서 발생한 예외 클래스를 받게 됩니다. 다형성 가능합니다.
catch 구문은 1개 이상 정의 가능하며(하나의 로직에서 발생할 수 있는 예외는 1개 이상이기 때문에),
위에서 부터 순차적으로 실행됩니다.

하나의 catch 구문에 들어갔을 경우 그 아래 catch구문은 건너뛰게 됩니다. switch case 처럼요.
그리고 마지막 finally 구문은
예외가 발생하든, 발생하지 않든 언제나 실행하는 부분으로써 선택적인 사항입니다.

결과는 아래와 같습니다.

excetpion-result2

먼저 System.out.println(str1.length()); 부분에서 null값 참조로 NullPointerException이 발생하게 됩니다.
(예외가 발생했으므로 진행이 멈추게 되어 그 아래 System.out.println(str2.length()); 은 진행되지 않습니다.)
발생된 NullPointerException은 try 구문을 빠져나와 아래의 catch 구문에서 자기가 들어갈 곳을 찾게 됩니다.
위쪽부터 살펴보니 파라미터로 NullPointerException을 받는 catch 구문이 있네요.

구문에 진입하고, 구문 내의 행위를 실행합니다.
catch 구문의 선택은 switch case 와 같다고 했었죠… NullPointerException 예외 처리 구문에 들어갔으니
아래의 Exception 예외 처리 구문에는 들어가지 않게 됩니다.

근데 여기서 유의하고 넘어가야 할 부분이 있습니다.
catch 구문의 위치를 바꾸었다면 어떻게 될까요?
예를 들어 아래와 같이 예외를 처리했을 경우를 보시죠.

1
2
3
4
5
6
7
catch(Exception e){
// Exception 예외 처리
} catch(IOException e){
// IOException 예외 처리
} catch(NullPointerException e){
// NullPointerException 예외 처리
}

Exception은 모든 예외의 상위 클래스이므로, 발생하는 예외를 모두 다 받을 수 있습니다. 상속관계도 가능하다고 했었죠.
즉, 이런식으로 예외처리 지정해버리면 백날 천날 예외 터져봐야 젤 위의 Exception 받는 부분에서 다 걸리므로
아래에 IOException, NullPointerException에 대한 예외 처리는 무용지물이 됩니다. 항상 유의해야 합니다.

마지막으로 finally 구문은 선택적인 부분입니다. 써도 그만 안 써도 그만.
try 구문이 정상적으로 끝나든, 예외가 발생해서 catch 구문에 들어가고 끝나든 언제든 실행 될 부분을 적어주면 됩니다.
예를 들면 자원 반납, 로깅 처리 등이 있습니다.

throws

throws는 던지다 라는 의미를 갖고 있습니다. 그리고 예외처리에서의 throws도 동일한 의미로 사용됩니다.
발생된 예외를 자신을 호출한 쪽으로 던져버리는 것입니다. 처리를 위임한다고 표현하면 되곘네요.

사용 예는 아래와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args){
try{
System.out.println(calculator('+',1,2));
System.out.println(calculator('/',5,0));
System.out.println(calculator('*',3,3));
} catch(ArithmeticException e){
System.out.println("0으로 나눌 수 없습니다.");
}
}

static int calculator(char sign, int num1, int num2) throws ArithmeticException{
int result = 0;
switch(sign){
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
result = num1 / num2; // 0으로 나누는 경우가 발생할 수 있다
break;
}

return result;
}

간단한 계산기 프로그램입니다.
보다시피 / 연산에서 0으로 나누는 예외인 ArithmeticException이 발생할 수 있지만 그에 대한 처리가 전혀 없습니다.
근데 잘 보시면 처리가 없는 대신 위에 throws 에서 ArithmeticException을 선언해주고 있네요.

위 구문의 의미는, ArithmeticException이 발생할 경우 자신을 호출한 메서드 쪽으로 그 처리를 던지겠다는 의미입니다.
위의 상황에서 calculator를 호출한 쪽은 main 메서드이므로, 보다시피 main 메서드에서 해당 예외를 잡아 처리하고 있음을 볼 수 있습니다.

JVM의 예외처리

main 메서드는 프로그램의 시작점이지만, main 메서드도 예외를 받았을 경우 throws로 던져버릴 수 있습니다.
그러면 결국 main을 호출한 쪽으로 던져지게 됩니다. main을 호출한 영역은 가상머신(JVM) 이죠.

결과적으로 예외처리가 가상머신에 의해 이루어지게 되는 것입니다.

  • 가상머신의 예외처리 방식
    1. getMessage 메서드를 호출한다.
    2. printStackTrace 메서드를 호출해 예외상황이 발생해서 전달되는 과정을 출력해 준다.
    3. 프로그램을 종료한다.

getMessage, printStackTrace 메서드는 예외의 최 상위 클래스인 Throwable에 정의된 메서드입니다.
getMessage는 예외에 대해 정의된 간단한 메시지를 보여주며, printStackTrace는 예외 발생 과정을 상세하게 출력해줍니다.
위의 calculator에서 Throwable 제공 메서드들을 사용해보겠습니다.

1
2
3
4
5
6
7
8
try{
System.out.println(calculator('+',1,2));
System.out.println(calculator('/',5,0));
System.out.println(calculator('*',3,3));
} catch(ArithmeticException e){
System.out.println(e.getMessage());
e.printStackTrace();
}

결과는 아래와 같습니다.

exception-result3

getMessage는 간단하게 예외의 내용을 출력해주고, printStackTrace는 예외 발생 과정을 상세하게 출력해줍니다.
특히나 printStackTrace는 예외 발생 시 원인을 찾는데 상당한 도움이 됩니다.

사용자 정의 예외

앞서 언급했던 NullPointerException, ArithmeticException 등의 경우,
실행 시 문제가 되므로 자바 가상머신에서 예외로 정의해놓았습니다. 미리 정의된 예외들이죠.
하지만 이 외에도 개발자가 직접 예외를 정의하는 방법도 있습니다.

예를 들어 비즈니스 로직에서의 예외가 있을 수 있습니다.
입/출금 관련 프로그램이 있다고 가정했을 때 잔고보다 많은 돈을 출금하려는 경우 자바 프로그램 상에서는 전혀 문제가 안되지만, 입/출금 관련 업무에서는 예외 상황이 됩니다.

현실에선 마이너스 잔고라는게 없기 때문입니다. (마이너스 통장 말고 일반 통장 기준입니다…ㅋㅋ)
이럴 경우는 개발자가 직접 예외를 정의해줘야 합니다.

위의 상황을 간단하게 정의해보겠습니다.

일단 사용자 정의 예외를 만들어줍니다. 예외 클래스가 되는 조건은 아래와 같습니다.

Exception 클래스를 상속한다

Exception은 예외 클래스의 상위 클래스입니다. 따라서 이를 상속함으로써 해당 클래스는 예외 클래스가 되고, try ~ catch 구문에 활용이 가능한 예외 클래스가 됩니다.

먼저 위의 상황에 맞는 예외 클래스를 하나 만들어보겠습니다.

1
2
3
4
5
public class NoMoneyException extends Exception{
public NoMoneyException(){
super("돈이 없습니다..");
}
}

별다른 처리 없이 간단히 문자열을 출력하게 하였습니다.
Exception 클래스를 상속하였으므로, 모든 예외처리 문법에서 사용 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
static int totalMoney = 100000;

static int withdraw(int money) throws NoMoneyException{
if(money > totalMoney){ // 예외 상황 발생시
throw new NoMoneyException(); // 예외 발생!
}else{
totalMoney -= money;
}

return totalMoney;
}

전달받은 돈이 전체 돈보다 작을 경우는 위에서 우리가 언급한 예외 상황이 됩니다.
그리고 해당 예외 상황이 발생했을 경우, throw 구문을 사용하여 예외를 발생시킵니다.
(new로 해당 예외를 생성하는 순간 해당 메서드에 예외가 발생된 것이기 때문에 해당 메서드에 예외 처리 구문이 필요합니다.
위와 같이 throws를 통해 호출한 곳으로 던져줘도 되고, 해당 메서드에서 직접 처리해도 됩니다.)

main에서 예외상황을 발생시켜 보겠습니다.

1
2
3
4
5
6
7
8
9
static int totalMoney = 100000;

public static void main(String[] args){
try{
System.out.println(withdraw(10000000));
} catch(NoMoneyException e){
e.printStackTrace();
}
}

잔고는 10만원인데 1000만원 인출을 시도하고 있습니다… 대단한…

exception-result4

가슴 아픈 예외가 발생했네요…
보다시피 상황에 맞춰 정의했던 예외가 발생하고 있음을 보실 수 있습니다!

체크 예외, 언체크 예외

우리가 마주하는 일반적인 예외인 Exception 하위 예외들은 그 사이에서도 2가지로 종류가 나뉩니다.
체크 예외와 언체크 예외 라는 이름으로 나뉩니다. 이는 문법적으로도 차이가 있으니 알고 가시는게 좋습니다.

일단 해당 예외를 나누는 기준은, RuntimeException 이라는 예외의 상속 여부입니다.
Exception의 하위 예외이면서 RumtimeException을 상속하지 않았을 경우 체크 예외, RumtimeException을 상속했을 경우 언체크 예외입니다.
(RumtimeException은 Exception의 서브 클래스이므로, 상속하면 자동으로 Exception의 하위 예외가 됩니다.)

체크 예외

RumtimeException을 상속하지 않은 일반적인 예외들입니다.
반드시 try ~ catch나 throws를 통해 처리를 해줘야 하기 때문에 체크 예외라고 부릅니다.
항상 문법적으로 체크한다 라는 의미로 보시면 좋을 것 같습니다.

예를 들면 IOException, SQLException 등이 있습니다.

1
2
3
4
5
6
7
8
9
public static void main(String[] args){
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

try{
System.out.println(br.readLine());
} catch(IOException e){
System.out.println("IOException 발생");
}
}

앞서 작성했던 예외처리와 별 다를것 없어 보이지만, 이를 직접 IDE에서 코딩해보시면 차이를 볼 수 있습니다.
IOException은 체크 예외이므로 위와 같은 try ~ catch 구문이나, throws 구문이 없으면 컴파일 오류가 발생합니다.
프로그램상에서 반드시 이 예외를 처리하고 넘어가도록 강조하는 것입니다.
만약 throws를 통해 체크 예외를 던져줬다면, 호출하는 쪽에서도 해당 예외를 처리하는 구문을 필수로 입력해야 합니다.
throw를 통해 체크 예외를 발생시킬 경우도 마찬가지입니다. 처리가 없을 경우 컴파일 오류가 발생합니다.

언체크 예외

RumtimeException을 상속한 예외입니다.
체크 예외처럼 해당 예외에 대한 처리를 문법적으로 강요하지 않기 때문에 언체크 예외라고 부릅니다.
개발자가 부주의할 경우 발생할 수 있는 예외들이라, 예상하지 못한 상황에 발생할 수 있는 그런 예외들이 아니므로 명시적인 처리를 강요하지 않은 것입니다.
예를 들면 NullPointerException, IllegalStatementException 등이 있습니다. 대부분의 예외가 런타임 예외입니다.

1
2
3
4
public static void main(String[] args){
String str = null;
System.out.println(str.length());
}

보다시피 해당 예외는 null값 참조로 인한 NullPointerException이 발생하는 코드입니다.
하지만 NullPointerException은 언체크 예외이므로 위의 코드는 컴파일 오류가 발생하지 않습니다.
throws를 통해 예외를 던져줬을 경우, throw를 통해 예외를 발생시켰을 경우에도 마찬가지입니다.

throws의 경우 호출하는 쪽에 해당 예외를 처리하는 코드가 없어도 되고,
throw의 경우에도 발생하는 메서드에 해당 예외를 처리하는 코드가 없어도 됩니다.
해당 예외를 처리하는 것은 선택사항입니다.

Read more »

[git] fetch, pull 차이

Posted on 2018-01-03 | Edited on 2020-11-02 | In git | Comments:

fetch는 원격 저장소의 변경 내역을 가져오는 것이고 직접 로컬 branch에 반영하진 않는다
반면 pull은 fetch 한 내역을 로컬 branch에 merge까지 한다

그러므로 pull은 branch를 지정해야 가져올 수 있고,
fetch는 branch를 지정해도 되고, 그냥 원격 저장소만 지정해도 된다

fetch로 가져온 내용은 checkout할 수 있다

1
2
git checkout origin/develop
git checkout another-origin/develop

위처럼 말고도 fetch_head 로도 checkout 할 수 있는데, 정확히 무슨 기준으로 checkout fetch_head가 결정되는지는 모르겠다(1개 이상의 branch를 fetch 했을 경우)

fetch 한 상태에서 git merge 혹은 git pull 입력 시 기존의 git pull과 동일한 행위를 하게 된다.
(branch를 입력하지 않을 경우 config를 참조하게 됨)

Read more »

[db] where과 on절 차이

Posted on 2018-01-02 | Edited on 2020-11-02 | In db | Comments:

아래의 두 문장은 어떻게 다를까?

1
2
3
4
5
6
7
8
9
10
select count(*) 
from dept_emp de
left outer join departments d
on de.dept_no = d.dept_no
where d.dept_name = 'Development';

select count(*)
from dept_emp de
left outer join departments d
on de.dept_no = d.dept_no and d.dept_name = 'Development';

select 절의 처리 순서를 찾아보면, where 이 on 보다 먼저 실행되는 것을 볼 수 있다

첫번째 문장의 경우
부서명이 Development 인 부서만을 들고온 상태에서 dept_emp 테이블과 조인하므로
부서명이 Development 가 아닌 부서는 결과에 포함되지 않는다
즉 필터 후 조인 이다

두번째 문장의 경우
모든 부서를 들고온 후 전달된 조건(부서번호가 같고 부서명이 Development)으로 조인하는 것이므로
dept_emp 의 로우가 departments 의 모든 로우를 돌면서 결과를 찾게 된다
이 상태에서 조인의 형태가 left join 이므로 dept_emp 의 모든 데이터가 다 남게된다
이 쿼리의 결과로는 부서명이 Development 가 아닌 부서도 결과에 포함된다

이렇게 둘 간의 처리 방식의 차이가 있기 때문에
left join 시 결과가 다르게 나오는 상황이 발생한다(inner join 시에는 같다)

아무래도 join 은 on 절에 명시된 조건으로 driving table 이 driven table을 모두 체크하는 구조이다 보니, 첫번째 쿼리처럼 where 로 driven 테이블의 로우를 줄여주고 시작하는 것이 좋다

Read more »

[git] hexo와 github pages로 블로그 만들기

Posted on 2017-12-28 | Edited on 2020-11-02 | In git | Comments:

이번엔 앞서 작성한 github-pages에 블로그 서비스를 하기 위해

정적 사이트 생성 도구인 hexo에 대해 알아보겠습니다.


Hexo

블로그 형태의 정적사이트를 생성하는데 사용되는 도구입니다.

hexo는 사용자가 작성한 포스트(markdown 등)을 읽어서,

정적파일 생성기를 통해 웹서버에 바로 서비스 할 수 있는 형태의 정적 웹사이트를 만들어냅니다.

대표적인 것으로 jekyll이 있지만 hexo가 좀 더 편해보이고 테마도 맘에 들어서 hexo를 사용하기로 했습니다 ㅎㅎ


설치

사전준비 : Node.js,npm,git

바로 설치하고 초기화 해보겠습니다.

1
2
npm install -g hexo-cli
hexo init '폴더명'

'폴더명’에 입력한 폴더를 만들고 그 폴더에 hexo 관련 파일을 초기화합니다.

(폴더를 지정하지 않으면 현재 폴더에 초기화하는데, 현재 폴더가 비어있는 상태여야 합니다.)

아래는 초기화 후 폴더 모습입니다!

image

빨간색으로 표시해둔 _config.yml에서 블로그에 대한 대부분의 설정을 할 수 있습니다.



초기화가 완료되면 간단하게 로컬에서 테스트 해보도록 할까요

해당 폴더로 이동하여

1
hexo server

라고 입력하면,

INFO Start processing

INFO Hexo is running at http://localhost:4000/. Press Ctrl+C to stop.

라는 메시지와 함께 http://localhost:4000 으로 접속 가능합니다.

기본 테마로 생성된 정적 블로그 페이지를 볼 수 있을 것입니다 ㅎㅎ


테마 적용

하지만 이대로 사용할 순 없으니 테마를 한번 적용해보도록 하죠.

적용방법은 매우 간단합니다.

https://hexo.io/themes/index.html

위의 주소에 접속한 뒤, 마음에 드는 테마를 고르시면 됩니다.

각 테마의 github 페이지에 들어가면 테마 적용 방법에 대한 상세한 설명이 있으니 별로 어려움 없으실 겁니다 ㅎㅎ

제가 고른 테마는 Material Flow 라는 테마입니다.

gitgub : https://github.com/stkevintan/hexo-theme-material-flow

보시다시피 매우 간단합니다. 소스를 clone받고 _config.yml에서 해당 테마로 지정해주기만 하면 됩니다.

1
2
3
4
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: material-flow

설정이 다 되었으면

1
2
hexo clean
hexo generate # 정적 리소스 생성

와 같이 입력하여 정적 리소스를 생성해주면 됩니다.

간혹 제대로 되지 않는 경우도 있기 떄문에 clean도 한번 해줬습니다.

이제 다시 hexo server 입력 후 들어가보시면 테마가 잘 적용되어 있음을 보실 수 있습니다!


글을 써보자

블로그를 만들었으니 글을 써야겠네요.

1
2
3
hexo new post [post_name]
# ex) hexo new post 'first post'
# ex) hexo new post first-post

과 같이 입력하면,

폴더에 아래와 같은 형태로 markdown 파일이 하나 생성됩니다.
1
2
3
4
5
6
7

```md
---
title: '[git] first post'
date: 2017-09-23 10:51:08
tags:
---

각종 폴더나 카테고리에 대한 설정도 _config.yml에서 할 수 있으니 각자 설정하시면 됩니다 ㅎㅎ



배포

이제 github에 배포해보도록 하겠습니다 ㅎㅎ

먼저 _config.yml에 deploy 관련 설정을 해 줍니다.

1
2
3
4
5
6
# Deployment
## Docs: https://hexo.io/docs/deployment.html
deploy:
type: git
repo: https://github.com/joont92/joont92.github.io.git
branch: master

저장한 뒤

1
2
3
4
5
6
hexo clean

hexo generate # 정적파일 생성하고
hexo deploy # 배포!

# hexo deploy --generate 로도 가능

와 같이 해주면 끝입니다. 매우 간단하죠??


배포시 아래와 같은 메시지와 함께 배포가 되지 않는 경우

1
ERROR Deployer not found: git

hexo-deployer-git 플러그인을 설치해주면 됩니다.

1
npm install hexo-deloyer-git --save

여기까지입니다 ㅎㅎ

블로그에 markdown을 사용할 수 있고, git의 형상관리를 블로그에 사용할 수 있다니 매우 좋은것 같네요.

들려주셔서 감사합니다~~

Read more »
1…16171819

JunYoung Park

182 posts
18 categories
344 tags
RSS
© 2020 JunYoung Park
Powered by Hexo v3.6.0
|
Theme – NexT.Muse v7.1.0