스킨 편집에서 HTML에 아무데나 다음을 붙여 넣는다.

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    tex2jax: {
      inlineMath: [ ['$','$'], ["\\(","\\)"] ],
      processEscapes: true
    }
  });
</script>
<script>
  (function () {
  var script = document.createElement("script");
  script.type = "text/javascript";
  script.src  = "https://mathjax.rstudio.com/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML";
  document.getElementsByTagName("head")[0].appendChild(script);
  })();
</script>

 

편집기에서 다음과 같이 입력하면

수식 $$n^2$$
이건 인라인 수식 $n^2$
수식 $$n^2$$
이건 인라인 수식 $n^2$

요렇게 변환이 된다. 

 

더 복잡한 수식은 MathJax 공식 문서를 참조하면 되지만 간단하게 사용하실 분들은

https://math.meta.stackexchange.com/questions/5020/mathjax-basic-tutorial-and-quick-reference 여기만 봐도 될 것 같다.

정규화, 역정규화란 무엇일까?

정규화는 데이터를 중복되게 관리하지 않기 위한 방법이다. 그렇게 함으로써 데이터의 일관성(Consistency)을 높일 수 있고, 모델의 응집도(Cohesion)을 높여 객체지향 설계에 더 가까워진다.

역정규화는 성능향상을 위해 일부로 데이터를 중복된 상태로 만드는 방법이다.

학생의 담당지도교수를 관리하는 테이블을 예로 하나씩 살펴보자.

http://www.gitta.info/LogicModelin/en/html/DataConsiten_Norm2NF.html

1. 정규화는 일관성(Consistency)을 높인다.

데이터가 한 곳에서만 관리된다는 건 변경할 때도 한 곳만 수정해줘도 된다는 장점이 있다.

반대로 데이터가 중복되어 여러 곳에 흩어져있으면 변경할 때 그 모든 테이블에 변경사항을 똑같이 적용해줘야 한다.

그렇지 않으면 변경사항이 일부 테이블에만 반영되서 데이터 일관성이 흐트러질 수 있다.

예를 들어 교수가 개명을 했다고 하면 그 교수의 ROW를 모두 찾아 100개면 100개 전부를 수정해줘야 한다. 하나라도 수정되지 않으면 데이터의 일관성이 깨질 수 있다(어떤 ROW는 개명전 이름이, 어떤 ROW는 개명 후 이름이 남아있을 수 있다). 정규화된 모델에서는 Professor에서 딱 한 ROW만 수정하면 된다. 일관성을 보장하기가 더 쉬워지는 것이다.

2. 정규화된 테이블이 객체지향적인 설계에 더 가깝다.

객체지향에서 관심사를 분리하는 것은 매우 기본적이다. 관심사를 분리함으로써 서로간의 결합(Coupling)을 낮출 수 있다(한 객체의 변경이 다른 객체에 영향을 미치지 않게 한다).

정규화 전 모델에서 교수에 관한 컬럼이 하나 추가된다고 하면 Students도 영향을 받게된다. 정규화 된 모델에서는 개별로 관리되므로 Professors의 변경이 Students에 영향을 주지 않는다. 변경에 더 자유롭고, 유지보수하기 쉬워진다.

3. 정규화를 통해 일관성을 얻는 대신 JOIN 비용이 증가한다.

해당 테이블에 정보가 없으므로 다른 테이블과 JOIN을 통해 정보를 가져와야 한다. 대용량 데이터를 처리하는 경우 JOIN으로 인한 성능저하는 큰 문제가 될 수 있다. 이런 경우 데이터가 INSERT후에 변경되지 않을 데이터(예를 들면 로그성 데이터)라면 일관성이 문제가 되지 않으므로 차라리 역정규화를 해서 JOIN을 없애 성능향상을 가져갈 수 있다.

Java8에서 도입된 람다표현식은 추상메서드가 1개인 인터페이스를 사용할 때 익명클래스를 사용하지 않고 코드의 구현부만 넘길 수 있개 해주는 문법으로써 코드의 간결성을 높일 수 있다.

다음 상황을 가정해보자.

1. 유저 리스트를 정렬하고 싶다.

2. 나이순으로, 이름순으로 각각 한번씩!

코드로 이렇게 작성할 수 있을 거 같다.

List<User> users = ...

Collections.sort(users, new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return o1.getName().compareTo(o2.getName());
    }
});

// Do something...

Collections.sort(users, new Comparator<User>() {
    @Override
    public int compare(User o1, User o2) {
        return Integer.compare(o1.getAge(), o2.getAge());
    }
});

코드에서 Comparator 인터페이스의 익명클래스 두 개를 만들어 인스턴스를 생성했다.

그런데 구현해야할 추상 메서드가 오직 하나인 인터페이스를 사용하는데에는 이 정도도 과해보인다. 정렬 기준이 추가될때 마다 저 코드가 반복된다고 생각하면 더 그렇다. 람다는 이런 번거로운 작업을 좀 더 간소화해준다. 람다를 사용하면 익명클래스를 사용하지 않고도 함수(Function)를 파라미터로 넘길 수 있다. 클래스를 사용하지 않기 때문에 메서드가 아닌 함수라는 용어를 사용한다.

람다를 사용한 코드는 다음과 같은 형태이다.

List<User> users = ...

Collections.sort(users, (o1, o2)->{
    return o1.getName().compareTo(o2.getName());
});

// Do something...

Collections.sort(users, (o1, o2)->{
    return Integer.compare(o1.getAge(), o2.getAge());
});

(파라미터) -> {구현부}의 형태를 띄고 있다.

그럼 람다로 넘길 수 있는 지 아닌지는 어디서 결정되는 걸까?

Comparator 인터페이스를 살펴보면 알 수 있다.

@FunctionalInterface
public interface Comparator<T> {
  int compare(T o1, T o2);
  // Others...
 }

구현해야 할 추상메서드가 1개인 인터페이스에 @FunctionalInterface 어노테이션을 붙여주면 람다식으로 사용할 수 있다. 추상메서드는 1개여야 하지만 default나 static 메서드에는 제한이 없다.

참고로 Comparator 인터페이스를 다시보면 헷갈릴 수 있는 부분이 있는데

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
    // Others...
}

default, static이 붙은 걸 제외해도 equals 메서드를 추상메서드로 제공하고 있다는 점이다. 즉 2개의 추상메서드가 있다. 그렇다면 내가 넘긴 람다식이 compare에 매칭되는지 equals에 매칭되는지 어떻게 알겠는가? 여기에 대해서는 다음과 같이 말하고 있다.

Functional Interface의 정의에서 Object 클래스의 public 메서드는 제외한다.

equals는 이미 모든클래스의 조상인 Object클래스에 이미 구현되어 있으므로 이런 경우에는 추상메서드로써 남겨놔도 FunctionalInterface로 사용할 수 있게끔 해서 편의성을 가져가는 것이 아닐까 한다.

Anoymous Inner Class와의 차이

public class Main {
    public static void main(String[] args) {
        List<Integer> iList = new ArrayList<>();
        iList.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer integer, Integer t1) {
                return 0;
            }
        });
    }
}

위의 코드를 컴파일하면 Main$1.class, Main.class 두 개의 class 파일이 생성된다.

public class Main {
    public static void main(String[] args) {
        List<Integer> iList = new ArrayList<>();
        iList.sort((i1, i2) -> i2 - i1);
    }
}

람다로 작성 후 컴파일하면 Main.class만 생성된다. 별도의 클래스를 생성하지 않고 outer class의 private static 메서드로 컴파일된다.

 

어떻게 컴파일되는지 좀 더 자세하게 알아보고 싶다면 아래 글을 읽어보는 것도 좋을 것이다.
https://dzone.com/articles/how-lambdas-and-anonymous-inner-classesaic-work

 

람다에 대한 딥다이브를 원한다면 참고할 수 있는 오라클 강의도 있다.

https://www.infoq.com/presentations/lambda-invokedynamic/

 

하이퍼링크를 통해서 파일을 다운로드할 때 파일이름을 바꾸고 싶으면

<a href="/test.png" donwload="newname.png"></a>

으로 하면 newname.png으로 다운로드가 되지만,

<a href="http//blahblah.com/file/test.png" donwload="newname.png"></a>

cross-origin 요청에 대해서는 더 이상 download attribute를 지원하지 않는다.

 

결국 request에 파일명을 쿼리스트링으로 전달한다음 nginx 자체에서 처리하도록 바꿨다.

location /file {
  autoindex      on;
  alias /data01/file/;
  # 이 설정을 추가해준다. 
  add_header Content-Disposition 'attachment;filename=$arg_filename'; 
}
<a href="http//blahblah.com/file/test.png?filename="newname.png"></a>

'개발 > 기타' 카테고리의 다른 글

[PHP] Call to undefined function dl()  (0) 2020.02.10
카프카(Kafka)  (0) 2020.01.30
Vue.js에서 Props가 안넘어가는 문제 (undefined)  (0) 2020.01.08
[Java] 원격 디버깅(Remote debugging)  (0) 2019.12.18
[Nginx] 'Server' 헤더 제거  (0) 2019.02.07
<report-list :reports="reports" :fields="fields" :clickHandler="moveToReport"></report-list>

var ReportList = {
	Props: ['clickHandler']
};

new Vue({
  .. 
  methods:  {
    moveToReport: function() {
       ....
    }
  }
});

 

clickHandler를 넘기고 싶은데, report-list에서 props 를 찍어보면 계속 undefined로 떠서 도대체 왜 이러나 싶었다.

<report-list :reports="reports" :fields="fields" :handler="moveToReport"></report-list>

var ReportList = {
	Props: ['handler']
};

new Vue({
  .. 
  methods:  {
    moveToReport: function() {
       ....
    }
  }
});

대소문자를 섞어쓰지 않고 소문자로만 props명을 만드니 잘 작동했다.

 

관련해서 가이드가 있었다.

https://vuejs.org/v2/guide/components-props.html#Prop-Casing-camelCase-vs-kebab-case

 

Props — Vue.js

Vue.js - The Progressive JavaScript Framework

vuejs.org

HTML attribute names are case-insensitive, so browsers will interpret any uppercase characters as lowercase. That means when you’re using in-DOM templates, camelCased prop names need to use their kebab-cased (hyphen-delimited) equivalents:

DOM 템플릿에서는 camelCase->kebab-case로 변환해서 사용해줘야 한다. 

<report-list :reports="reports" :fields="fields" :click-handler="moveToReport"></report-list>

var ReportList = {
	Props: ['clickHandler']
};

new Vue({
  .. 
  methods:  {
    moveToReport: function() {
       ....
    }
  }
});

Spring에서 서블릿을 등록하려면 아래와 같이 해야한다.

@WebServlet(name = "myServlet", urlPatterns = "/app2")
public class MyServlet extends HttpServlet {

  @Override
  protected void doGet (HttpServletRequest req,
                        HttpServletResponse resp)
            throws ServletException, IOException {

      System.out.println("-- In MyServlet --");
      PrintWriter writer = resp.getWriter();
      writer.println("dummy response from MyServlet");
  }
}

GET /app2 로 들어오는 요청을 처리할 서블릿을 정의하는 코드다.
Java EE에서 Http 요청을 처리하기 위해서는 다음과 같이 작업해야 한다.

  1. HttpServlet 클래스를 상속받아 요청을 처리할 로직을 작성한다.
  2. 작성한 클래스를 서블릿컨테이너에 등록한다.

하지만 SpringMVC를 사용하다보면 서블릿을 등록할 일이 없다.
대신 다음과 같은 코드를 작성하게 된다.

@Controller
public class GreetingController {

    @GetMapping("/greeting")
    public String greeting(@RequestParam(name="name", required=false, defaultValue="World") String name, Model model) {
        model.addAttribute("name", name);
        return "greeting";
    }

}

이건 서블릿은 아니지만 서블릿 처럼 요청에 대한 핸들러 역할을 한다.
SpringMVC는 모든 요청을 하나의 서블릿, DispatcherServlet이 받는다.
DispatcherServlet이 받아서 우리가 정의한 컨트롤러 맵핑에 맞게 요청을 넘겨준다.

바햐흐로..

바야흐로 그 옛날엔 웹 페이지가 정적 컨텐츠로만 구성된 시절이 있었을 것이다. 요청에 매칭되는 파일을 찾아 응답을 내려주면 된다.

그러나 시간이 흐르면서 정적 컨텐츠만으론 충분하지 않음을 느꼈을 것이다. DB에 저장된 데이터을 읽어서 HTML 포함 시키고 싶다고 하면 어떻게 해야할까? 모든 데이터에 맞게 HTML을 미리 생성해 놓을 순 없는 노릇이다. 이러한 이유로 동적으로 HTML을 생성하고 싶은 요구가 생겼고 Java에서는 이를 서블릿과 서블릿컨테이너를 통해 이를 지원하게 된다.

간략히 프로세스를 설명하면 다음과 같다.

  1. 서블릿 클래스를 상속받아 요청에 대해 어떤 응답을 줄 지에 대한 코드를 작성한다.
  2. 컨테이너의 설정파일인 web.xml에 어떤 uri에 어떤 서블릿이 매칭되는지를 작성한다.
  3. 내 어플리케이션을 컨테이너에게 제출한다(Submit).
  4. 컨테이너가 실행되어 Request를 받으면...
    1. web.xml을 참고하여 Request를 처리할 서블릿 클래스를 찾는다.
    2. 찾은 클래스의 인스턴스를 생성한다.
    3. 그 인스턴스에 구현된 메소드를 호출하여 응답값을 돌려받는다.

즉 서블릿은 우리가 원하는 로직을 작성할 수 있는 클래스이고, 서블릿 인터페이스에 맞게 작성하여 컨테이너에 등록해두면, 컨테이너가 이를 가져다 사용하는 식이다.

사실 SrpingMVC를 주로 사용하는 입장에선 서블릿을 직접 작성할 케이스가 많지 않다.

참고자료

https://javaee.github.io/servlet-spec/downloads/servlet-4.0/servlet-4_0_FINAL.pdf

https://engkimbs.tistory.com/755

http://www.pearsonitcertification.com/articles/article.aspx?p=29786&seqNum=3

1. Java 실행 옵션에 다음 추가

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000

ex)

java -Dsun.misc.URLClassPath.disableJarChecking=true -server -Xms2g -Xmx2g -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 -Dfile.encoding=UTF-8 -Dconsole=true -jar test.jar --spring.profiles.active=alpha

2. IDE로 디버깅

Eclipse

Eclipse -> Run -> Debug Configuration -> Remote Java Application -> Build 에서
Host : 원격 서버 ip
Port : address로 넣어준 port (여기선 8000)
입력 후 Debug 시작

Intellij

Edit configuration -> Add new configuration -> remote
Host : 원격 서버 ip
Port : address로 넣어준 port (여기선 8000)
입력 후 Debug 시작

 

+(4.55555).toFixed(2);
//-> 4.56

+(4).toFixed(2);
//-> 4

 

+ Recent posts