Vue instance:

뷰로 화면을 개발하기 위해 필수적으로 생성해야 하는 기본 단위이다.

아래는 뷰 인스턴스를 생성하는 방법 중 하나이다.

new Vue({
	el: '태그이름',
	data: {
    	something: '어떤 것'
    }
})

<div id="태그이름">
	{{something}}
</div>

인스턴스 안에 el 속성을 통해 뷰 인스턴스가 그려질 지점을 지정하게 된다.

data 속성에 화면에 보여질 데이터를 정의한다.

그 결과 html 태그에 텍스트를 보여줄 수 있게 된다.

 


CDN 방식:

웹 애플리케이션에 js framework나 라이브러리를 바로 사용할 수 있는 호스팅 서비스

(Vue Cli만 사용해 보았었는데 CDN도 마찬가지로 Vue의 기능을 모두 사용할 수 있다고 한다. CDN 방식의 경우는 빌드 등을 할 필요 없이 파일만 클릭하면 html 파일이 띄워 졌다.)

그래서 CDN은 결국 서버에 보내는 request이고, 브라우저는 한 시점에 제한된 개수(5~6개??)의 request를 요청할 수 있기 때문에 CDN으로 가져오면 request가 하나 늘어나는 것이나 다름 없으므로 url로 부터 리소스를 가져올 것이 많은 경우에는 결과적으로 페이지를 볼 수 있을 때까지 조금 더 기다리게 된다고 한다.

 

다른 이유로는

script 태그로 참조하여 사용하는 방법으로는 SPA(Single Page Application) 앱을 개발할 수 없기 때문에 대부분 Vue CLI 도구를 이용

Vue CLI를 이용해 개발하면 .vue 파일로 분리된 단일 파일 컴포넌트 단위로 컴포넌트 중심의 개발을 합니다. 개발된 컴포넌트를 조합하여 UI를 구성한다. 개발된 코드는 webpack에 의해서 빌드 및 번들링된 후 배포버전의 html, css, js 파일을 생성한다. (하나의 html 파일로 만듦)

⇒ 스크립트 태그로 참조하는 방식은 이게 불가능함

 

 

HTML id vs class?

id는 한 페이지에 하나의 style/css로 하나의 태그만 사용할 수 있다. 따라서 로고, 상단 메뉴, 하단 정보와 같은 스타일을 정의할 때 사용한다. el 지정을 할 때 .app 이런 식으로 정의해야 함

class는 한 페이지에 반복적으로 사용 되는 스타일을 정의할 수 있다. el 지정을 할 때 #app으로 정의한다.

반복적으로 사용 되는 스타일에는 class, 그 내부에 세부 스타일에는 id를 사용하면 효과적이다.

⇒ 객체를 굳이 지정하지 않은 경우에는 id를 써 주지 않도록 함!!!

* 브라우저가 id는 매우 빠르게 접근이 가능하게 별도로 관리 한다. class나 tag의 경우 css query 방식을 이용한다.

 

추가적으로 태그 등에 신경쓰도록 하자 - 시멘틱 웹

velog.io/@kimu2370/%EC%8B%9C%EB%A7%A8%ED%8B%B1-%EB%A7%88%ED%81%AC%EC%97%85

 

Semantic Web

about semantic web

velog.io

클래스 선택자는 글자색/글자 굵기 등 나중에 다른 곳에도 적용할 수 있는 스타일을 지정하고, ID 선택자는 웹 문서 안에서 요소의 배치 방법을 지정할 때 자주 사용한다.

http://www.nextree.co.kr/p8468/: 선택자 관련 내용


Vue에서 바인딩

Vue 인스턴스가 생성될 때 data 객체에 있는 모든 속성이 Vue의 반응형 시스템에 추가된다. 즉, 각 속성 값이 변경될 때 뷰가 반응해서 새로운 값과 일치하도록 업데이트된다.

데이터가 변경되면 화면은 다시 렌더링 되는데, data에 있는 속성들은 인스턴스가 생성될 때 존재한 것들만 반응형이다. 즉, 나중에 데이터를 추가된 데이터가 변경되면 화면이 갱신되지 않는다.

  • vue에서는 바인딩 methods를 이벤트와 바인딩 할 때 @를 사용한다!!
    • @는 v-on: 과 같은 역할을 한다. 그래서 클릭 이벤트를 바인딩 하고싶다면 @click을 사용하면 된다.
  • computed를 사용하는 방법
    • computed는 옵저버 패턴으로 동작한다. (watch도 마찬가지)
    • computed를 사용해서도 동일한 동작을 하도록 만들 수 있다.

데이터 변동이 없는 상태에서 computed는 이전의 계산된 값을 캐시 해 두었다가 함수 호출 시 다시 쓰게 된다. methods는 사용될 때마다 함수의 계산을 다시 하게 된다.

그래서 만약 데이터가 자주 변동되지 않는다면, computed가 좋다고 할 수 있고, 데이터가 수시로 업데이트 된다면 계속해서 캐시를 저장하는 computed보다는 methods가 더 좋을 수 있다고 한다.

 

결론(?)

  • computed: template 내부에 선언된 computed중에서 해당 함수와 연결된 값이 바뀔 때만 해당 함수 만을 실행한다.
  • methods -그런거 모르겠고 template내부에 선언된 methods중에서 update 라이프 사이클이 동작한(=아무 변수나 바뀐)다면 화면에 붙어 있는 함수를 모두 실행한다.

1. 파라미터를 받아서 호출 해야할 때 - data를 사용하지 않는다면 computed로는 아예 불가능하다.

2. 함수 안에서 다른 값을 바꿔 줘야 할 때 - computed에서는 다른 값을 바꾸는 것은 정책 위반이다. (불가능한건 아니지만) 이 경우는 methods를 사용한다.

 

 

## computed와 watch의 차이

어떻게 사용하느냐에 따라 watch가 computed의 역할까지도 할 수 있다.

  • watch : 반응형 콜백

    Vue 인스턴스의 특정 프로퍼티가 변경될때 지정한 콜백함수가 실행되는 기능이다.

    data에 선언되어 있는 값이 watch 안에도 그대로 선언이 될 수 있다. 이렇게 watch는 기존에 Vue 인스턴스 내에 선언된 값의 변화를 감시하는 역할을 하기 때문에 Vue 인스턴스 내에 선언된 값을 그대로 다시 사용하게 된다.

  • computed : 반응형 getter

    computed의 Property가 정의될때 내부적으로는 Object.defineProperty를 통해 정의되며, 이때 익명함수가 getter로 설정된다.

    1. 함수가 아니라 일반 객체처럼 사용할 수 있고,
    2. 호출될때만 계산이 이루어지며,
    3. 계산결과가 캐싱되는 특성이 있다. 값이 변해도 캐싱 때매 변경된 값을 인지하지 못하는 단점이 있다.

    ⇒ getter의 특성(methods와 차이가 있다.).

⇒ computed가 새 프로퍼티를 생성하고 그것의 getter 로 익명함수를 설정되는 것과는 달리, watch는 아무 프로퍼티도 생성하지 않고 익명함수는 단순히 콜백함수로의 역할을 한다.

⇒ computed는 이미 정의된 계산 식에 따라 결과 값을 반환할 때 사용되며, watch는 어떤 특정 조건에서 함수를 실행시키기 위한 트리거 로서 사용할 수 있다.

 

 

참고 object watch 할 때, 내부 값이 변했는지 여부

    timeUnit: {
     handler(val){
       this.inputValue = val.value;
     },
     deep: true

 


v-if vs v-show, v-for

v-if는 v-show와 같이 특정 html 요소를 보이거나 감추는 역할을 한다. 두 디렉티브의 차이는 DOM을 만드는지 여부에 달려 있는데, v-if는 아예 DOM 요소를 만들어내지 않고, v-show는 DOM 요소를 만들되 display:none 효과를 가져온다.

즉, v-if는 조건에 따라 컴포넌트가 실제로 제거되고 생성되고,

반면에 v-show 는 단순히 css 의 display 속성만 변경된다

  • v-for과 v-if 같이 사용 시 v-for이 더 높은 우선순위를 가진다.
  • Vue의 v-for 동작 방식
    • 렌더링 된 엘리먼트 목록을 갱신할 때 기본적으로 in-place patch 전략을 사용한다. 이 것은 데이터 항목의 순서가 변경된 경우, 항목의 순서와 일치하도록 DOM 요소를 이동하는 대신 Vue가 각 요소를 적절한 위치에 패치하고 해당 인덱스에서 렌더링할 내용을 반영하는지 확인하는 것이다.
    • (여기서는 필요 없겠지만??) Vue에서 개별 DOM 노드들을 추적하고 기존 엘리먼트를 재사용/재정렬하기 위해서는 v-for의 각 항목들에 고유한 key 속성을 제공해야 한다. key속성은 v-bind를 사용하여 동적 값에 바인딩 한다.
    • 반복되는 DOM 내용이 단순한 경우나 의도적인 성능 향상을 위해 기본 동작에 의존하지 않는 경우를 제외하면, 가능하면 언제나 v-for에 key를 추가하는 것이 좋다고 한다. (key 값은 primitive 속성인 것을 사용하도록)
  • 편의를 위해서, v-bind를 생략 할 수 있다. 그냥 콜론 뒤에 속성의 이름만 넣어 주면 됨

    • 예) v-bind:key ⇒ :key

 

v-for는 v-if 보다 더 높은 우선순위를 가지고 있기 때문에 두 디렉티브를 같이 사용하는 방법은 추천하지 않는다고 한다. 일반적으로 두 디렉티브를 같이 쓰고 싶을 경우는 조건부 필터링을 하고싶을 때 이다. 굳이 v-if을 사용하고싶다면 아래 두 방법을 사용하면 된다.

//1. 상위나 하위에 n
<ul v-if="shouldShowUsers">
  <li
    v-for="user in users"
    :key="user.id"
  >
    {{ user.name }}
  <li>
</ul>

Webpack

대규모 응용 프로그램 같은 경우에는 js모듈이 많기 때문에 대부분 module bundler 라는 것을 이용한다고 한다. 모듈 번들러는 웹 애플리케이션을 구성하는 자원(HTML, CSS, Javscript, Images 등)을 모두 각각의 모듈로 보고 이를 조합해서 병합된 하나의 결과물을 만드는 도구를 의미함... NPM으로 vue를 땡겨서 webpack 같은 걸로 관리하게 되는데, Vue CLI로 가져오게 되면 이런 것 크게 신경 쓸 것 없이 미리 다 세팅이 되어 있다고 함. 그리고 개발 시에 핫 리로드, lint-on-save 이런 기능도 설정이 되어 있다고 한다. 이게 뭔지는 나중에 알아보고... webpack 같은 모듈 번들러만 쓰는 경우에는 특별히 세팅 하지 않으면 빌드 후에 app.js 와 같이 모든 js가 하나의 파일로 묶이는데 이게 용량이 금방 늘어 난다고 한다. (chunk로 split 해주지 않으면)

웹팩은 entry로 설정된 시작점에서 의존성을 가진 모든 파일을 압축하여 output 지점에 하나의 자바스크립트 파일을 만들어 준다. 이때, 자바스크립트가 아닌 파일은 loaders를 이용하여 자바스크립트에서 이용가능한 모듈로 만들어 주며, plugins를 이용하여 번들된 자바스크립트를 난독화하거나 특정 텍스트를 추출하는 역할을 합니다. mode는 웹팩의 사용 목적에 따라 설정을 지정하는 역할을 합니다.

 

+추가)

웹팩은 모든 파일을 모듈로 바라본다. 자바스크립트로 만든 모듈 뿐만아니라 스타일시트, 이미지, 폰트까지도 전부 모듈로 보기 때문에 import 구문을 사용하면 자바스크립트 코드 안으로 가져올 수 있다.

이것이 가능한 이유는 웹팩의 로더 덕분이다. 로더는 타입스크립트 같은 다른 언어를 자바스크립트 문법으로 변환해 주거나 이미지를 data URL 형식의 문자열로 변환한다. 뿐만아니라 CSS 파일을 자바스크립트에서 직접 로딩할수 있도록 해준다.

 

  • 자바스크립트의 변수 유효 범위는 기본적으로 전역 범위를 갖는다...
  • 그래서 복잡한 애플리케이션을 개발할 때에는 모듈화가 필요하고 이를 위해 웹팩을 사용한다.
  • https://babeljs.io/docs/en/learn#modules: ES modules 문법
  • 웹팩은 기본적으로 필요한 자원은 미리 로딩 하는게 아니라 그 때 그 때 요청하자 → Lazy Loading

 

c.f. webpack-dev-server (서버 구성은 Node.js 환경 위에 Express 서버 프레임워크가 올라간 형태)

localhost의 8080 포트로 개발서버를 실행하게 된다. 브라우저로 http://localhost:8080 요청을 보내면, HTML, JavaScript, CSS 를 응답으로 받고 자바스크립트가 index.html 의 <div id="app"></div> 부분에 vue 컴포넌트를 동적으로 그려 준다.

웹팩 데브 서버로 빌드한 결과물은 메모리에 저장되고 파일로 생성하지는 않기 때문에 컴퓨터 내부적으로는 접근할 수 있지만 사람이 직접 눈으로 보고 파일을 조작할 순 없다. ⇒ 괜히 dist 폴더에 뭐가 안생기는지 찾았네...

암튼 그래서 컴퓨터 구조 관점에서 파일 입/출력보다 메모리 입출력이 더 빠르고 컴퓨터 자원이 덜 소모된다.


NPM 지역 설치 - dev 환경

# 개발용 라이브러리와 배포용 라이브러리 구분하기

npm install jquery --save-dev

// 결과 //
// package.json
{
  "devDependencies": {
    "jquery": "^3.4.1"
  }
}

배포용 라이브러리는 npm run build로 빌드를 하면 최종 애플리케이션 코드 안에 포함된다. 반대로 설치 옵션에 -D를 주었다면 해당 라이브러리는 빌드 하고 배포할 때 애플리케이션 코드에서 빠지게 된다.

배포할 때는 빠져도 좋은 라이브러리의 예시

  • webpack : 빌드 도구
  • eslint : 코드 문법 검사 도구
  • imagemin : 이미지 압축 도구
// npm custom 명령어
"scripts": {
  "dev": "node server.js",
  "build": "webpack --mode=none",
}

이렇게 정의해두면 npm run dev 할 경우 node server.js 이 실행된다.

커스텀 명령어에는 실행 옵션 같은것도 추가해서 정의해 둘 수 있음

 


new Vue vs export default?

vue 파일에서 const 변수 = new Vue 이런식으로 인스턴스를 정의 했을 땐 안되었는데..

모든 vue 파일에서 export default 으로 정의하니까 됐다.

두 방식의 가장 큰 차이점은 export default는 ES6의 modules 구문이라는 점이다.

기존의 ES5 방식은 변수에 컴포넌트 내용을 넣어 인스턴스 안에서 참조하는 방식이고,

같은 파일 내에 변수를 선언했기 때문에 그냥 일반 js 값을 참조하듯 참조할 수 있다.

export default는 다른 파일의 있는 내용을 참조 해 오기 위한 방식이다.

1. new Vue

new Vue({
    el: '#app',
    data () {
      return {}
    }
)}

usually a sub-root root instance of the rest of the application. This breaks the root element declared in the html document

→ 즉, 아래와 같은 형태가 된다.

<html>
  ...
  <body>
    <div id="app"></div>
  </body>
</html>

 

2. export default

export default {
    name: 'my-component',
    data () {
      return {}
    }
}

⇒ 결론

Whenever you work on the.vue file, you use the export default {} syntax

but if not, if you are using Vue in a regular HTML file

  • main.js doesn't matter whether you create a root instance from the HTML document itself or an external file (i.e. an external file)
  • .vue Files always export default { ... }use syntax.

refs 속성

$refs 속성을 이용해 DOM에 접근이 가능하다

즉, JavaScript에서 자식 요소에 직접 접근할 때 사용한다. 이 경우, ref 속성을 이용해 자식 요소에 레퍼런스 ID를 할당하여 해결한다.

주의할 점은 $refs는 랜더링 된 후 값이 채워진다. 또한 $refs는 반응형이 아니라고 함.(즉, $refs가 변경 되어도 watch, computed 등으로 감지 하지 못함) 그렇기 때문에 template에서나 computed에서 $refs를 사용할 수 없다.

 

  • vue에서 포커스 주는 법
    • 포커스 될 input 태그에 ref 값을 설정하고 원하는 methods 에 this.$refs.설정값.focus() 입력

v-model

Vue에서 양방향 바인딩을 가능하게 한다.

  • 데이터 바인딩이란 VueJs의 binding expressions을 사용하여 script와 DOM간의 데이터를 주고받는 과정을 의미
  • 단방향 바인딩은 데이터를 화면에 출력
  • 양방향 바인딩은 화면에서 입력을 받아 데이터로 다시 전달하는 과정이 추가되어, 양쪽 방향 모두 바인딩 되는 것을 의미한다.

주의할 점

v-model은 value, checked, selected와 같은 어트리뷰트들을 깔끔히 무시하고, 오로지 vue 인스턴스의 data 속성을 유일하게 보기 떄문에, 초기값 설정을 꼭 해줘야 한다.

 

## v-bind vs v-model

input 엘리먼트에 v-model을 사용하여 Vue 인스턴스의 데이터를 바인딩하면 입력한 내용에 대해 모델 바인딩 바로 되지만, v-bind를 사용했을 경우 그렇지 못하기 때문에 $event.target.value를 사용하여 값을 가져와 oninput 이벤트에 데이터 값을 변경해준다.

 


=== vs ==

input이 null인지 확인하기 위해 ==를 사용했는데, ===를 쓰도록 하자...

또한, input이 비어 있는지 확인하기 위해서는 null 뿐만 아니라 ''와도 비교해야 한다.

  • == : Equality, 동등 연산자로, 피 연산자가 서로 다른 타입 이면 타입을 강제로 변환하여 비교한다.

0 == ''     //true
0 == '0'     //true
1 == true     //true
false == '0'    //true
null == undefined    //true
false == null    //false
false == undefined    //false
  • === : Identity, 일치 연산자로, 형 변환을 하지 않고 두 피 연산자를 더 정확하게 비교한다.

0 === ''     //false
0 === false    //false
1 === true     //false
NaN === NaN     //false
null === undefined     //false

 


mixin

다중 상속이나 재사용을 위해 구현한 인터페이스

믹스인을 통해 다른 컴포넌트들은 캡슐화된 기능을 사용할 수 있다. 결과적으로 같은 동작이지만, 서로 다른 실행을 통해 처리된다.

거의 기능을 선언하는 부분으로, js문법을 통해서 만들었다. (abstract class같은 느낌)

→ 혼자서는 암것도 못하고 다른 컴포넌트에게 mixin되서 사용된다.

 

- 동일한 이름의 함수가 선언될 때

var mixin = {
  methods: {
    foo: function () {
      console.log('foo')
    },
    conflicting: function () {
      console.log('from mixin')
    }
  }
}

var vm = new Vue({
  mixins: [mixin],
  methods: {
    bar: function () {
      console.log('bar')
    },
    conflicting: function () { 
      // => 오버라이딩(?)처럼 되어서 vm.conflicting을 할 경우 vm에서 정의된 함수가 호출된다.
      console.log('from self')
    }
  }
})

mixin은 편리하지만 mixin을 사용한 vue파일에 공통적으로 추가 됩니다. 이렇게 되면 동일한 코드가 반복되서 사용되는 문제가 있습니다. 만약 상수값을 공통적으로 사용하려고 mixin안에 담는다면 동일한 코드가 반복될것입니다. 그리고 내용의 크기가 크다면 더 좋지못한 영향을 끼치게 될것입니다. 상수를 효율적으로 처리하기위해선 상수를 관리하는 vueX를 사용하기를 권고합니다.

 


Javascript 변수 종류

  • var: es6 이전의 변수 선언 방식, 매우 유연한 방식으로 변수를 선언할 수 있는 방법이다.
    • 단점: 넘 유연해서 같은 변수를 두번 선언 해도, 잘 동작한다.
  • let: es6 이후에 추가됨, 값을 재정의 가능, scoped, 같은 변수를 두 번 선언하는 것은 불가
  • const: es6 이후에 추가됨, 상수 표현, scoped

⇒ var 보다는 let, const로 명확하게 표현하도록 하자!

 


axios

  • axios는 통신할 때만 쓰는 줄 알았는데, 데이터를 읽을 때도 쓸 수 있다는 걸 알았다. ⇒ 통신맞다.. webpack dev 서버를 쓰느라...
  • response 자체는 아래와 같이 생겼다. data 말고도 다양한 필드가 존재함 ⇒ 내부 폴더에 저장되어 있는 파일을 그냥 읽는 식이 아니고, dev server를 이용하여 get으로 가져온 것
  • 엄청 옛날에는... connection을 한 번 열릴 때마다 thread를 하나씩 만들어서 처리 했다고 한다. CPU가 여러 개의 쓰레드를 처리 하려면 컨택스트 스위칭이 일어나게 되는데, 이 컨택스트 스위칭이 일어나게 되면 속도 저하가 일어난다.
  • 이후에는 task 기반이라는 것이 나오는데, thread가 아닌 태스크라는 논리적인 단위를 만들어서 각 단위를 쓰레드마다 주게 된다. 이 방법은 쓰레드를 바꾸지 않기 때문에 컨택스트 스위칭 비용이 발생하지 않게 된다. ⇒ async/await 가 task 기반으로 돌아가는 것임
    • 즉, axync, await가 쓰레드를 만드는 것 보다는 속도 성능이 좋다.
    • 그치만 async도 오버헤드가 있긴 함...

⇒ 결과적으로는 웹 서버에 처리해 줄 수 있는 thread pool에 있는 task수와 request 수가 비슷하면 뭘 쓰든 성능이 비슷하다.

그런데, 만약 request 수가 많아지게 되면, async를 쓰게 되면 request를 실행하다가 잠깐 멈추고 다른 request를 실행하는 형식으로 왔다 갔다 동작하게 된다. (1개의 job이 빨리 끝나는 것은 아니지만, 모든 사람의 job이 모두 느리게 끝나진 않는다)

⇒ 그래서 웹 프론트에서 동기 방식을 쓰면 페이지가 안뜨는 건가 보다...

async를 안 해주게 되면, 뒤에 온 request는 앞에 온 request가 끝날 때까지 기다려야 한다. 그럼 이 처리를 할 때 어떤 것이 문제가 생겨서 걸리는 시간이 길어지게 된다면? 뒤에 기다리는 작업은 time out이 발생할 수 있다. ⇒ scalability가 중요하니까 쓰는거임

 

  • async/await, promise는 non-blocking 방식이다.
  • async/await는 비동기 코드의 겉모습과 동작을 좀 더 동기 코드와 유사하게 만들어준다. 이것이 async/await의 가장 큰 장점이다.

 

  • 콜백 함수: 어떤 이벤트가 발생한 후, 수행될 함수로, 아래는 주의할 점이다.
    • 콜백함수는 클로저이다.
      • 클로저는... 이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수를 뜻한다. js는 함수형 언어이기 때문에 아래 코드와 같이 inner() 함수의 유효 범위(scope) 밖에서, 즉 outer() 내부에 존재하고 inner() 함수 외부에 존재하는 변수, 함수에 접근할 수 없지만, JS에서는 클로저를 통해 접근할 수 있으며 또한 값을 변경할 수도 있다.
function outer(){
	var x = 10;
	function inner(){
		x++; // 밖에 있는 x에 접근 가능
		console.log(x)
	};
	return inner;
}

 

  • 콜백함수 사용 시 this 객체에 유의해야 한다.
    • this는 뭐냐면... js의 경우 함수 호출 방식에 의해 this에 바인딩할 어떤 객체가 동적으로 결정된다. 다시 말해, 함수를 선언할 때 this에 바인딩할 객체가 정적으로 결정되는 것이 아니고, 함수를 호출할 때 함수가 어떻게 호출되었는지에 따라 this에 바인딩할 객체가 동적으로 결정된다.
    • 즉, 함수가 불러지기 위해 필요한?(접근해야 하는??) 대상이 this가 된다.
var person = {
    name: "victolee",
    email: "asdf@example.com",
    birth: "0225",
    foo : function(){
        console.log(this) // this == person 객체
    }
}
/*객체가 메서드를 호출할 경우, 메서드를 호출한 객체가 this이다.*/
person.foo() // foo() 함수를 실행하기 위해서는 person 객체의 foo 프로퍼티를 참조해야
// 즉, foo() 함수를 호출한 것은 person 객체이므로, this는 person 객체를 가르킵니다.
var x = 10;
function foo(){
    this.x = 20;
    x = 30;

    console.log(x);
    console.log(this.x);
    console.log(window.x);
}
/*일반 함수인 경우, 브라우저 상에서 window가 this이다.*/
foo() // 어떤 객체가 호출한 것이 아니기 때문에, this는 특정 객체를 가르키고 있지 않음
// 일반 함수일 경우에는 window 객체를 this로 갖고 있습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script
    src="https://code.jquery.com/jquery-3.2.1.min.js"
    integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
    crossorigin="anonymous"></script>
</head>
<body>
<button class="btn" value="foo">버튼</button>
</body>

<script>
		/*이벤트가 발생한 경우, 이벤트를 발생한 객체가 this이다.*/
    $(".btn").click(function(){ // 버튼 클릭 시 click 이벤트가 실행된다.
        console.log(this) // 이벤트가 발생했을 때는 이벤트가 발생한 객체가 this
    })
</script>
</html>

 

  • js는 this를 명시적으로 바인딩할 수 있는 apply, call, bind 메소드 를 제공한다... 이건 또 뭐냐면
    • vue.js, 즉 DOM에 한해서는 함수를 호출한 객체, 이벤트가 발생한 객체가 아니면 대부분의 this는 기본 값인 window가 된다. 명시적 바인딩 함수는 this를 조작 할 수 있는 메서드로서, 원하는 객체를 this로 할당하고 싶을 때 사용한다.
    • call()과 apply() 메서드의 차이
      • 함수를 호출할 때 this를 바꿔주는 기능은 같지만 매개변수로 인자들을 넘겨줌 → call
      • 배열을 넘겨 줌 → apply
var person = {
    name: "victolee",
    email: "asdf@example.com",
    birth: "0225",
    foo : function(val1, val2, val3){
        console.log(val1 + val2 + val3);
        console.log(this);
    }
}

person.foo.call(window, 3,6,9); // foo의 this 객체를 person에서 window로 변경
// 이렇게 되면 call() 메서드를 호출할 때 첫 번째 인자로 window 객체를 전달했으므로 this는 window 객체로 변경됨

배열 변수 렌더링?

boolean 배열로 변수를 정의하고 있을 경우, arr 요소 중에 boolean의 값을 바꿨을 경우, 재 렌더링을 하지 않아서 force update를 해 주었다.

이렇게 된 이유는 Vue는 data에 arr이 변경 되었을 경우에만 감지를 할 수 있는데, arr 안에 있는 bool의 값을 변경했기 때문에 arr의 주소 값은 변경 없이 그대로 이기 때문이다.

data에 바인딩 되어있는 속성의 주소 값이 변경 되어야만 Vue는 변화를 감지하고 re-rendering을 하게 된다..

 

bool의 값을 변경했을 때 다시 렌더링을 하고 싶다면 아래와 같은 방법을 쓸 수 있다.

let list = [...this.arr];
list.splice(0, 1, {first: 1, bool: false});
this.arr = list;

위와 같이 할 경우 arr에 새로운 배열이 할당되어 arr이 가리키는 주소 값이 변경되므로 Vue는 변화를 감지하여 re-rendering을 하게 된다.

 

또는 아예 객체로 만들어버린다.

 


vuex

  • vuex: Vue.js의 상태 관리 라이브러리로 애플리케이션의 모든 컴포넌트에 대한 중앙 집중식 저 역할을 하며 의도적인 방법으로 상태를 변경 및 관리할 수 있다. Vuex는 기존 Flux의 아키텍처를 따라가고 있다. Vue.js에서도 react의 Redux를 사용할 수 있지만 Vue.js는 Vuex와의 호환이 좋을 뿐만 아니라 더 직관적으로 개발할 수 있다.

  • flux 패턴: https://www.huskyhoochu.com/flux-architecture/

  • vuex의 구조

    • state

      Vue 컴포넌트에서 data같은 개념으로, 원본 소스의 역할을 하며, View와 직접적으로 연결되어있는 Model이다. 이 state는 직접적인 변경은 불가능하고 mutation을 통해서만 변경이 가능하다. mutation을 통해 state가 변경이 일어나면 반응적으로 View가 업데이트된다.

    • mutations

      Mutation은 state를 변경하는 유일한 방법이고 이벤트와 유사하다. mutation은 함수로 구현되며 첫 번째 인자는 state를 받을 수 있으며, 두 번째 인자는 payload를 받을 수 있다. 여기서 payload는 여러 필드를 포함할 수 있는 객체 형태도 가능하다. 이 mutation은 일반적으로(Helper를 쓰지 않는 경우)는 직접 호출을 할 수 없으며, commit을 통해서만 호출할 수 있다.

      대부분 실무에서는 mutations에서는 API를 통해 전달받은 데이터의 가공하여 state를 설정하는 데 많이 사용된다고 한다.

      store.commit('setData', payload)
    • action

      Action은 mutation과 비슷하지만 mutation과는 달리 비동기 작업이 가능하다. 또한 mutation에 대한 commit이 가능하여 action에서도 mutation을 통해 state를 변경할 수 있다. action에서는 첫 번째 인자를 context 인자로 받을 수 있으며 이 context에는 state, commit, dispatch, rootstate와 같은 속성들을 포함한다. 두 번째 인자는 mutation과 동일하게 payload로 받을 수 있다.

      commit을 통해 mutation을 호출했다면 Action은 dispatch를 통해서 호출한다. context의 속성을 보면 dispatch가 있는 것으로 보아 action에서는 서로 다른 action을 호출할 수 있다는 것을 볼 수 있다.

      실무에서 actions은 Axios를 통한 API 호출과 그 결과에 대해서 반환(return)을 하거나 mutation으로 commit하여 상태를 변경하는 용도로 사용된다고 한다.

      store.dispatch('setData', payload)
    • getters

      Getters는 쉽게 Vue 컴포넌트에서 Computed로 볼 수 있다. 말로 풀자면 계산된 속성인데 getter의 결과는 종속성에 따라 캐시 되고 일부 종속성이 변경된 경우에만 다시 재계산된다. 즉, 특정 state에 대해 어떠한 연산을 하고 그 결과를 View에 바인딩할 수 있으며, state의 변경 여부에 따라 getter는 재계산이 되고 View 역시 업데이트를 일으킨다. 이때 state는 원본 데이터로서 변경이 일어나지 않는다.

      실무에서도 state의 연산 처리가 필요한 내용에 대해 getter를 사용하지만 getters의 경우 대용량 처리 시에 퍼포먼스와 연관이 되어있으므로 조심해야 한다. 대용량 처리에 관련해서는[Vue.JS] 대용량 데이터의 처리 방법과 성능 최적화 방법 (Vue.js Performance) 를 참고하자.


한글 바인딩 문제

인풋 창에서 한글입력 시 이벤트가 한템포 늦게 동작하는 경우가 발생한다. 한글이 2byte로 되어있기 때문에

이런문제가 생기는데 제대로 업데이트되기 위해서는 v-model대신 input directive를 사용하라고 명시되어 있다.

<input type="email" v-model="userId" />
<!-- 위 코드를 아래처럼 바꾸도록 한다. -->
<input class="user-email" type="email" @input="userId = $event.target.value" />

이벤트 버블링

이벤트 버블링은 하나의 div속에 여러 이벤트가 겹겹이 있을 경우에 일어난다. 나의 경우에서는 가장 위에 있는 div focus, blur 만 호출되지만, click의 경우에는 click이 호출 되고 나서 focus/blur가 또 호출된다.

즉, 의도하지 않더라고 안에서 부터 밖까지 차례대로 호출되게 된다. 이를 막기 위해서는 .self 를 통해 자신이 만든 이벤트가 아니라면 실행되지 않도록 할 수 있다.

  • 이것을 바꾼 뒤에도 focus때문에 click이 진행되지 않는 문제점이 있었다. 이를 해결하기 위해서는
  1. focusout에 timeout
  2. event의 순서를 변경
  3. 마우스의 위치 이벤트 이용
  4. click 대신 mousedown을 쓰는 방법이 있었다.

컴포넌트간 통신 방법

  • Vue에서 데이터를 전달하는 방식

    https://question0.tistory.com/20 참고

    1. this.$root 혹은 this.$parent 를 이용: 제일 쉬운 방법이지만 프로젝트가 작고 상관없으나 프로젝트가 커지게 되면 이를 이용한 수정을 했을 때, 이벤트 추적이 힘들어질 수 있을 수 있다.
    2. v-bind 와 props, v-on 이벤트와 $emit 를 이용: 인과 관계가 명확하게 되므로 프로젝트가 커지더라도 이벤트의 추적이 용이해진다.
      • v-bind + props : 상위 컴포넌트에서 하위 컴포넌트로의 데이터 전달
      • v-on + $emit : 하위 컴포넌트에서 상위 컴포넌트로 데이터 전달
    3. v-model 를 이용
      • 상위 컴포넌트에서 하위 컴포넌트를 설정할때 속성이 v-model 하나로 축소
      • 하위 컴포넌트에서 model 속성 객체를 추가하여 설정
    4. EventBus 를 이용: 가장 일반적인 방법인 줄 알았는데, 자주 사용하게 되면 나중에 이벤트를 추적 관리하기 힘들어 진다고 함
    5. vuex store으로 공통 객체 이용: vuex 를 이용하는 방법은 글로벌한 공통 상태 값을 관리하기에 적합하지만 한정된 컴포넌트 간의 통신에는 부적합하다.

 

인프런에서 querydsl에 관한 강의를 듣고 토이 프로젝트에 적용해보고 싶은 생각이 들었다. 그래서 한 번 해보기로 함~~!!

 

 

먼저 gradle 설정

plugins {
    ...
    id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
    ...
}
...
dependencies {
    ...
    implementation 'com.querydsl:querydsl-jpa'
    ...
}
...
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}
sourceSets {
    main.java.srcDir querydslDir
}
configurations {
    querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

 

 

 

 

queryDSL을 기존의 spring data JPA repository를 사용하던 곳에 적용해 보려고 했다.

사용자 정의 Repository

복잡한 기능을 추가하거나, 사용자 정의 함수를 추가할 때 사용한다.

Spring data JPA는 인터페이스로 동작하기 때문에, 내가 원하는 코드를 넣으려면 사용자 정의 repository라는 것을 구현해야 한다.

출처: https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84

 

사용자 정의 repository의 사용법은 아래 단계들과 같다.

1. 사용자 정의 인터페이스 작성

2. 사용자 정의 인터페이스 구현

3. 스프링 데이터 레포지토리에 사용자 정의 인터페이스 상속

 

여기서 주의할 점은 기존에 있던 JpaRepository를 상속하는 repository 인터페이스와 같은 이름의 Impl을 구현해야 한다는 점이다.

 

이 방법 외에도 특정한 기능인 경우에는 따로 repository를 만들 수도 있다.

현재 구현하는 애플리케이션의 경우, 게시판에만 search & filter 기능을 사용하기 때문에 후자의 방법을 이용하여 구현해 보기로 했다.

 

 

 

기존 코드: stream filter, map, collect 등의 기능을 이용했었음

1. 모든 게시판 가져오면서 숨김처리한 게시물 필터링

2. 조건에 맞추어 필터링

3. 조건에 맞추어 정렬

(정렬 조건 중 까다로운 점은 현재까지 모임 신청이 approved된 인원을 뺀 remain 에 대한 정렬을 구현하는 것이었다)

여기서 나는 같은 stream을 여러번 읽어서 필터링 하고, 이 필터링 된 코드를 또 stream으로 바꾸어 sorting하는 등이 문제라고 생각했다.

private List<Board> sortBoardList(SortingType sorting, List<Address> addresses, List<Category> categories, User user) {
	List<Board> boards = filterBoardList(addresses, categories, user);

	if (sorting == SortingType.REMAIN) {
		return boards.stream()
				.sorted(Comparator.comparing(Board::getRemainRecruitNumber, Comparator.reverseOrder()))
				.collect(Collectors.toList());
	} else if (sorting == SortingType.DEADLINE) {
		return boards.stream()
				.sorted(Comparator.comparing(Board::getStartsAt, Comparator.naturalOrder()))
				.collect(Collectors.toList());
	}
	return boards.stream()
			.sorted(Comparator.comparing(BaseEntity::getCreatedAt, Comparator.reverseOrder()))
			.collect(Collectors.toList());
}

private List<Board> filterBoardList(List<Address> addresses, List<Category> categories, User user) {
	return findAllBoards(user).stream()
			.filter(board -> addresses.contains(board.getAddress()))
			.filter(board -> categories.contains(board.getCategory()))
			.collect(Collectors.toList());
}

private List<Board> findAllBoards(User user) {
	Set<Board> hiddenBoards = user.getUserHiddenBoard().stream()
			.map(HiddenBoard::getBoard).collect(Collectors.toSet());

	return boardRepository.findAll().stream()
			.filter(board -> board.getStatus().getCode() != BoardStatus.CANCELED.getCode())
			.filter(board -> !hiddenBoards.contains(board))
			.collect(Collectors.toList());
}

이러고 나서 마지막에 페이징 처리를 했음...

 

 

바뀐 코드: 

queryDSL로 바꾸기 위해서 일단 필터링 조건 들을 나누었다.

BooleanBuilder를 사용하면 훨씬 쉽게 할 수 있었지만,

where절의 조건으로 쓰면 가독성이 좋은 BooleanExpression으로 구현하고 싶었다.

BooleanExpression을 사용할 때 주의할 점은 null 에 대한 부분을 생각해 주어야 한다는 점이다.

private BooleanExpression isFilteredCategories(List<Long> categories) {
	return categories != null ? Expressions.anyOf(categories.stream().map(this::isFilteredCategory).toArray(BooleanExpression[]::new)) : null;
}

private BooleanExpression isFilteredCategory(Long categoryId) {
	return board.category.id.eq(categoryId);
}

private BooleanExpression isFilteredCities(List<Long> cities) {
	return cities != null ? Expressions.anyOf(cities.stream().map(this::isFilteredCity).toArray(BooleanExpression[]::new)) : null;
}

private BooleanExpression isFilteredCity(Long cityId) {
	return board.address.id.eq(cityId);
}

private BooleanExpression isSearchedKeywords(List<String> keywords) {
	return Expressions.allOf(keywords.stream().map(this::isSearchedKeyword).toArray(BooleanExpression[]::new));
}

private BooleanExpression isSearchedKeyword(String keyword) {
	return board.content.containsIgnoreCase(keyword);
}

private BooleanExpression isDeletedBoard() {
	return board.status.ne(BoardStatus.CANCELED);
}

BooleanExpression을 쓰면서 어려웠던 점은, 단일 파라미터에 대한 예제는 많이 나와있었지만,

여러가지 필터링을 하기 위해 List 값에 대한 BooleanExpression을 만드는 것들은 잘 나오지 않아 시간을 좀 많이 썼다...

 

 

그 다음은 이 조건들을 적용하기 전에 먼저 querydsl로 짤 쿼리문을 직접 짜 보았다.

SELECT HIDDEN_FILTERED.*, COALESCE(APPROVED_USER.approved_number, 0) as approved_number, (HIDDEN_FILTERED.recruit_count - COALESCE(APPROVED_USER.approved_number, 0)) as remain_number
FROM (SELECT b.* FROM board as b left join hidden_board as hb on (b.id = hb.board_id and hb.user_id=1) where hb.user_id is null) as HIDDEN_FILTERED
left join (select board_id, count(user_id) as approved_number from applied_user where status='APPROVED' group by board_id) as APPROVED_USER
on HIDDEN_FILTERED.id = APPROVED_USER.board_id
order by approved_number DESC;

첫번째 시도는 위와 같이 mysql에서 직접 쿼리문을 작성할 경우 결과값이 아주아주~~ 잘 나왔다.

 

 

하지만, querydsl에서는 서브쿼리를 from문에서 사용하지 못한다는것을 몰랐다... 그래서 아래와 같이 left join을 3번 해보기로 했다.

SELECT b.*, (b.recruit_count - COALESCE(count(au.user_id), 0)) as remain_count
FROM (board as b left join hidden_board as hb on b.id = hb.board_id and hb.user_id=1) left join applied_user as au on b.id = au.board_id and au.status = 'APPROVED'
where hb.user_id is null
group by b.id
order by remain_count DESC;

 

 

 

쿼리 테스트를 해 본 후, querydsl에 해당 쿼리문을 적용해 보았다.

여기서 어려웠던 점은, 정렬 조건 중 remain 인원에 대한 것이었다.

모집 인원에 대한 인원 수는 db 필드로 있었으나, remain 인원에 대한 것은 applied user의 상태를 보고 세어야 했다.

이것은 자바 함수를 쓰면 매우 간단해서 이전 방법을 사용할 때는 깨닫지 못했지만, querydsl로 바꾼 후 시간이 많이 걸리게 된 원인이다ㅠ

 

인프런에 관련 함수를 쓸 수 없을지 질문을 올렸는데, 아래는 그 답변이었다.

---

querydsl의 결과는 결국 JPQL이 만들어지고, JPQL은 또 SQL로 번역되기 때문에 SQL로 할 수 있는 로직만 실행할 수 있습니다.

해당 코드는 자바 함수이기 때문에 JPQL이나 SQL에서 사용하는 것이 불가능합니다.

대신에 JPQL이나 SQL에서 다음과 같은 방식은 사용할 수 있습니다.

order by item.count2 - item.count1 desc

querydsl에서 사용하려면 다음 코드를 참고해주세요.

---

 

그래서 결국 applied user를 직접 구해서 뺀 값에 대한 정렬을 하도록 바꾸었다.

public List<Board> filter(BoardFilterCondition boardFilterCondition, Pageable pageable) {
	return jpaQueryFactory
			.select(board)
			.from(board)
			.leftJoin(hiddenBoard).on(board.id.eq(hiddenBoard.board.id).and(hiddenBoard.user.id.eq(boardFilterCondition.getUserId())))
			.leftJoin(appliedUser).on(board.id.eq(appliedUser.board.id).and(appliedUser.status.eq(AppliedStatus.APPROVED)))
			.where(
					hiddenBoard.user.id.isNull(),
					isFilteredCategories(boardFilterCondition.getCategory()),
					isFilteredCities(boardFilterCondition.getCity()),
					isDeletedBoard()
			)
			.groupBy(board.id)
			.orderBy(orderType(boardFilterCondition.getSorting()))
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();
}

private OrderSpecifier<?> orderType(SortingType sortingType) {
	if(sortingType == SortingType.REMAIN){
		return board.recruitCount.subtract(appliedUser.user.id.count().coalesce(0L)).desc();
	}
	if (sortingType == SortingType.DEADLINE) {
		return board.startsAt.asc();
	}
	return board.createdAt.desc();
}

여러번 stream으로 필터링을 해야 했던 코드가 비교적 가독성 있어졌다...!! 그리고 페이징 처리도 repository 내에서 다 할 수 있었다.

 

 

 

마찬가지로 검색 쿼리도 구현해 보았다. 검색 쿼리는 필터링 보다 정렬도 까다롭지 않아 더 쉬운 편이었다.

public List<Board> search(BoardSearchCondition boardSearchCondition, Pageable pageable) {
	return jpaQueryFactory.select(board)
			.from(board).leftJoin(hiddenBoard)
			.on(board.id.eq(hiddenBoard.board.id).and(hiddenBoard.user.id.eq(boardSearchCondition.getUserId())))
			.where(
					hiddenBoard.user.id.isNull(),
					isSearchedKeywords(boardSearchCondition.getKeywords()),
					isDeletedBoard()
			)
			.orderBy(board.createdAt.desc()) // 최신순 정렬
			.offset(pageable.getOffset())
			.limit(pageable.getPageSize())
			.fetch();
}

 

 

 

느낀점

인강만 듣고 쉬운 예제들을 봤을 땐 금방 적용하겠지~ 하고 생각했는데,

실제 구현할 부분에 적용하려고 하니 생각보다 어려웠다. 그리고 가장 중요한 것은 sql 자체를 짜는 능력이라고 생각했다.

sql 자체를 제대로 짜면 그 것을 querydsl로 옮기는 것은 쉬운 일이라고 느꼈다. 성능은 또 다른 문제지만...ㅜㅠ

 

구현한 repository 전체 코드는 아래와 같다.

github.com/Yapp-17th/Android_2_Backend/blob/develop/common-module/src/main/java/com/yapp/crew/domain/repository/BoardSearchAndFilterRepository.java

 

Yapp-17th/Android_2_Backend

Backend Repository for Android Team 2. Contribute to Yapp-17th/Android_2_Backend development by creating an account on GitHub.

github.com

 

이번 yapp 어플리케이션 백엔드 프로젝트에서는 유저 인증 시 token 기반 인증을 구현하기로 했다.

token기반의 인증은 stateless api를 활용할 수 있다는 장점이 있다.

또한, 토큰이 탈취된다 하더라도, 유저의 기본 정보가 노출되지 않는다는 점이 장점이라고 할 수 있다.

뿐만 아니라, 프론트가 안드로이드 애플리케이션인데, 모바일 애플리케이션에서 jwt 기반 인증을 사용하는 이유는 다음과 같다.

모바일 트렌드 중 하나는 로그아웃을 하지 않는 이상 로그인을 유지하는 것입니다.
일반적으로는 손쉽게 Session을 이용해서 클라이언트와 서버 통신 중 Stateless의 단점을 보완할 수 있었지만, 모바일의 특성상 자주 끊길 소지가 있습니다.

세션과 비슷한 역할을 하되, 계속해서 유지될 수 있는 기술을 찾다 보니 Token을 이용한 방식이 있었고, 그중 JWT를 사용하게 되었습니다.

 

 

암튼 게시판을 구현하다가, 게시판 기능에서 요청 헤더에서 토큰을 가져오고,

이 토큰에 들어있는 user_id를 검증 해야하는 모든 controller에 공통된 로직이 있었다.

 

 

 

Interceptor

Interceptor란 컨트롤러에 들어오는 요청 HttpRequest와 컨트롤러가 응답하는 HttpResponse를 가로채는 역할을 한다.

따라서 게시판 기능으로 들어오는 모든 request를 가로채서 토큰의 검증에 사용해 보려 한다.

 

spring security를 사용해서도 토큰 기반 인증을 구현할 수 있지만, interceptor를 사용해서 가볍게 구현해 보았다.

 

 

 

  • preHandle: 컨트롤러가 호출되기 전에 실행된다.
  • postHandle: 컨트롤러가 실행된 후에 호출된다.
  • afterComplete: 뷰에서 최종 결과가 생성하는 일을 포함한 모든 일이 완료 되었을 때 실행된다.

 

Interceptor를 구현하기 위해서는 먼저 HandlerInterceptorAdapter를 구현한 클래스를 만든다.

@Component
@NoArgsConstructor
public class AuthInterceptor extends HandlerInterceptorAdapter {

	private UserRepository userRepository;
	private Auth auth;

	@Autowired
	public AuthInterceptor(UserRepository userRepository, Auth auth) {
		this.userRepository = userRepository;
		this.auth = auth;
	}

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		String token = request.getHeader(HttpHeaders.AUTHORIZATION);
		verifyToken(token);

		Long userId = auth.parseUserIdFromToken(token);
		log.info("[Request User ID] " + userId);
		User user = findUserById(userId)
				.orElseThrow(() -> new UserNotFoundException("[INTERCEPTOR Exception] user not found"));

		checkUserStatus(user.getStatus());
		return super.preHandle(request, response, handler);
	}

	private void checkUserStatus(UserStatus userStatus) {
		if (userStatus == UserStatus.INACTIVE) {
			throw new InactiveUserException("inactive user");
		} else if (userStatus == UserStatus.SUSPENDED) {
			throw new SuspendedUserException("suspended user");
		}
	}

	private Optional<User> findUserById(Long userId) {
		return userRepository.findUserById(userId);
	}

	private void verifyToken(String token) {
		auth.verifyToken(token);
	}
}

prehandle에서는 auth 클래스에서 구현한 토큰 검증이 진행된다.

이후, claim에 들어있는 userId를 가져와서 데이터베이스에서 user를 조회 및 user status를 확인한다.

 

 

그 다음으로는 adapter를 애플리케이션에 붙이기 위해서 WebConfiguration에 붙여준다.

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	private AuthInterceptor authInterceptor;

	@Autowired
	public WebConfiguration(AuthInterceptor authInterceptor) {
		this.authInterceptor = authInterceptor;
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		registry.addInterceptor(authInterceptor)
				.addPathPatterns("/**");
	}
}

첨에는 authInterceptor를 autowired 안해줬어서 제대로 실행되지 않았었다ㅠㅠ

 

 

마지막으로 @ControllerAdvice를 사용해서 컨트롤러에서 발생하는 exception을 모두 잡도록 했다.

아마존 리눅스로 인스턴스를 만들어준다.

 

EC2 연결 후, docker 설치

sudo yum install docker 
sudo service docker start
sudo usermod -a -G docker ec2-user // ec2-user에게 권한부여

 

Docker Hub에 있는 Mysql 이미지 띄우기

EC2의 9876 포트와 mysql 기본포트인 3306을 연결해준다.

mysql의 비밀번호를 "password"로 설정해주고, msyql5.6을 (dockerhub 로 부터 다운로드 받아서)실행한다.

docker run -d \
-p 3306:3306 \
--name mysql-container \
-e MYSQL_DATABASE=explanet_dev \
-e MYSQL_ROOT_PASSWORD=password \
mysql:latest

 // EC2 인스턴스나 Docker 컨테이너를 종료시켜도 데이터가 사라지는 것을 방지하기 위해 저장할 폴더 지정

 

Spring Boot에서 빌드

spring boot 터미널에서 base-api 모듈을 클린하고 jar 파일로 만든다

./gradlew :base-api:clean :base-api:bootJar

 

 

 

 

도커파일 빌드

마찬가지로 스프링 부트 터미널에서 libs 폴더에 있는 jar을 로컬 도커 환경에서 빌드한다

아래는 작성한 도커파일이고,

FROM adoptopenjdk/openjdk11:alpine-jre
COPY build/libs/base-api-0.0.1-SNAPSHOT.jar base-api.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=dev", "-jar", "base-api.jar"]

 

아래 커맨드로 도커 파일을 빌드할 수 있다.

 

Tag는 일종의 Alias 같은 역할을 하는데, 특히 latest의 경우는 사용자가 Pull을 할 때 Tag를 지정하지 않았을 때 사용된다.

docker build --tag=sypark9646/yapp-android2:1.0.0-beta --force-rm=true .

 

아래 커맨드를 사용하면 만들어진 이미지를 볼 수 있다.

docker images

 

도커 허브에 Push

만든 이미지를 도커 허브에 push한다.

docker push sypark9646/yapp-android2

 

 

AWS 터미널에서 spring boot jar 파일 run

aws 터미널에서 아까 Push 했던 이미지를 가져온다

docker pull sypark9646/yapp-android2:1.0.0-beta

스프링 파일을 run 시키기 -d를 쓰면 백그라운드로 돌아가도록 설정할 수 있다.

docker run -d -e "SPRING_PROFILES_ACTIVE=dev" -p 8080:8080 -t sypark9646/yapp-android2:1.0.0-beta

 

 


별개로 전체 삭제 커맨드는 아래와 같다

 

container 전체 삭제

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)

이미지 전체 삭제

docker rmi $(docker images -q) 

Cache란?

나중에 할 요청 결과를 미리 저장 해 두었다가 빠르게 서비스 해 주는 것

  • Look aside Cache(일반적으로 많이 쓰는 방식)
    • DB를 가기 전에 Cache에 먼저 데이터가 있는지 확인 해본다.
    • 만약 Cache에 데이터가 있다면 캐시 데이터를 읽고, 없다면 DB를 읽은 후 Cache에 저장 & 결과 리턴
  • Write Back(로그를 DB에 저장하는 경우, 쓰기 연산이 많은 경우)
    • 캐시에 먼저 저장했다가, 특정 시점마다 DB에 저장하는 방식
    • ex. 걸리는 시간(배치 작업) - DB insert 쿼리를 1번씩 * 500번 >> insert쿼리 500개 합친 것 * 1번
    • 단점: 장애가 생기면 데이터가 사라질 위험이 있다. 왜냐면 리부팅 되면 메모리는 사라지기 때문

 

캐시의 대상이 되는 정보들은 아래와 같다.

 

  • 단순한, 또는 단순한 구조의 정보를 -> 정보의 단순성 
  • 반복적으로 동일하게 제공해야 하거나 -> 빈번한 동일요청의  반복
  • 정보의 변경주기가 빈번하지 않고, 단위처리 시간이 오래걸리는 정보이고 -> 높은 단위처리비용
  • 정보의 최신화가 반드시 실시간으로 이뤄지지 않아도 서비스 품질에 영향을 거의 주지 않는 정보 
  • 예) 실시간 검색어, 방문자수/조회수/추천수, 1회성 인증정보, 공지사항/Q&A 등

 

 

Redis란?

REDIS(REmote Dictionary Server)는 메모리 기반의 “키-값” 구조 데이터 관리 시스템이며, 모든 데이터를 메모리에 저장하고 조회하기에 빠른 Read, Write 속도를 보장하는 비 관계형 데이터베이스이다.

Redis가 타 캐시 시스템(ex. MemCache 등)과 다른 특징은 아래와 같다.

  1. Redis는 List, Set, Sorted Set, Hash 등과 같은 Collection을 지원합니다.
  2. Race condition에 빠질 수 있는 것을 방지함
    • Redis는 Single Thread
    • 따라서 Atomic 보장
  3. persistence를 지원하여 서버가 꺼지더라도 다시 데이터를 불러들일 수 있습니다.

 

Embedded Redis를 이용하여 @Service 메소드를 캐시 함수로 사용하기

프로그램 구조

 

1. gradle 추가

implementation 'org.apache.commons:commons-lang3:3.4'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'it.ozimov:embedded-redis:0.7.2'

 

2. config class 추가 - 주의

spring:
  profiles:
    active: local
  cache:
    type: redis
  redis:
    host: localhost
    port: 6375
public class CacheKey {

  public static final int DEFAULT_EXPIRE_SEC = 600; // 10 minutes
  public static final String STOCK = "stock";
  public static final int STOCK_EXPIRE_DAY = 1;
}
@Profile("local")
@Configuration
public class EmbeddedRedisConfig {

  @Value("${spring.redis.port}")
  private int redisPort;

  private RedisServer redisServer;

  @PostConstruct
  public void redisServer() {
    redisServer = new RedisServer(redisPort);
    redisServer.start();
  }

  @PreDestroy
  public void stopRedis() {
    if (redisServer != null) {
      redisServer.stop();
    }
  }
}
@RequiredArgsConstructor
@EnableCaching
@Configuration
public class RedisConfig {

  private final ObjectMapper objectMapper;

  @Autowired
  public RedisConfig() {
    this.objectMapper = new ObjectMapper();
  }

  @Bean(name = "cacheManager")
  public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    var jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(StockResult.class);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);


    RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
        .disableCachingNullValues()
        .entryTtl(Duration.ofSeconds(CacheKey.DEFAULT_EXPIRE_SEC))
        .computePrefixWith(CacheKeyPrefix.simple())
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(
            jackson2JsonRedisSerializer
        ));

    Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
    cacheConfigurations.put(CacheKey.STOCK, RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(Duration.ofDays(CacheKey.STOCK_EXPIRE_DAY)));

    return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory).cacheDefaults(configuration)
        .withInitialCacheConfigurations(cacheConfigurations).build();
  }
}

 

여기서 주의해야 할 점은 나는 레디스에 저장 할 값이 객체이기 때문에 Serializer를레디스에서 제공해 주는 jackson2JsonRedisSerializer를 사용했다. 이 때, 아래와 같이 ObjectMapper가 StockResult라는 객체를 Serialize 해 줄 수 있도록 코드를 추가한다.

var jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(StockResult.class);
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

 

3. @Service 메소드에 @Cacheable 추가하기

@Cacheable(value = CacheKey.STOCK, key = "#stockRequestDto.stockSymbol+#stockRequestDto.dateToday", unless = "#result == null")
  public StockResult calculateStockResult(StockRequestDto stockRequestDto) {
    ...
  }

옵션의 value에는 저장시 키값을, key에는 키 생성시 추가로 덧붙일 파라미터 정보를 선언한다.

나의 경우 캐시키는 stock::AAPL2020-09-27과 같은 형태로 생성된다.

unless = “#result == null”는 메서드 결과가 null이 아닌 경우에만 캐싱하도록 하는 옵션이다.

 

4. 메인 Application에 @EnableCaching 어노테이션 추가하기

@EnableCaching
@SpringBootApplication
public class StockApplication {

  public static void main(String[] args) {
    SpringApplication.run(StockApplication.class, args);
  }

}

 

이렇게 만들어 본 후 포스트맨으로 테스트 해 보니,

맨 처음에 캐시에 데이터가 없는 경우를 제외하고는 결과 값을 바르게 바로바로 리턴하는 것을 확인할 수 있었다.


참고

https://www.youtube.com/watch?v=mPB2CZiAkKM

https://daddyprogrammer.org/post/3870/spring-rest-api-redis-caching/

 

SpringBoot2로 Rest api 만들기(15) – Redis로 api 결과 캐싱(Caching)처리

이번 장에서는 지금까지 개발한 api에 캐시를 적용해 보도록 하겠습니다. 캐시란 자주 사용되는 데이터를 메모리에 저장하고 반환하여 하드디스크의 원본데이터를 거치지 않게 함으로서 리소스

daddyprogrammer.org

https://sabarada.tistory.com/103

 

[Redis] 캐시(Cache)와 Redis

[Redis] 캐시(Cache)와 Redis [Redis] Redis의 기본 명령어 [Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisTemplate 편 [Java + Redis] Spring Data Redis로 Redis와 연동하기 - RedisRepository..

sabarada.tistory.com

https://javaengine.tistory.com/entry/SpringBoot2%EB%A1%9C-Rest-api-%EB%A7%8C%EB%93%A4%EA%B8%B015-%E2%80%93-Redis%EB%A1%9C-api-%EA%B2%B0%EA%B3%BC-%EC%BA%90%EC%8B%B1Caching%EC%B2%98%EB%A6%AC

 

SpringBoot2로 Rest api 만들기(15) – Redis로 api 결과 캐싱(Caching)처리

이번 장에서는 지금까지 개발한 api에 캐시를 적용해 보도록 하겠습니다. 캐시란 자주 사용되는 데이터를 메모리에 저장하고 반환하여 하드디스크의 원본데이터를 거치지 않게 함으로서 리소스

javaengine.tistory.com

 

[Java]

  • JVM: 운영 체제의 메모리 영역에 접근하여 메모리를 관리하는 프로그램이다. 메모리 관리나 가비지 컬렉터를 수행한다
  • Garbage Collector: 동적으로 할당된 메모리 영역 중 사용하지 않는 영역을 방지하여 해제하는 기능이다.
    • Java에서 동적으로 할당된 메모리란? --> 힙 영역
      • Stack: 정적으로 할당한 메모리 영역으로, 원시 타입의 데이터가 값과 함께 할당된다. 힙 영역에 생성된 Object 타입의 데이터의 참조값 할당
      • Heap: 동적으로 할당한 메모리 영역으로, 모든 Object 타입의 데이터가 할당된다. 힙 영역의 Object를 가리키는 참조 변수가 스택 영역에 저장된다. 힙 영역은 New Generation 영역과 Old Generation 영역으로 이루어져 있다.
        • New generation:
          • Eden: 새로운 객체는 Eden 영역에 할당된다. Eden영역이 모두 사용되면 GC가 발생하는데, 이때 일어나는 가비지 컬렉터가 Minor GC라고 한다.
          • Servival 0: 이 이후에 살아남은 객체(Eden 영역의 Reachable 객체)를 Servival 0 영역으로 이동한다. Eden영역의 Unreachable 객체는 메모리에서 해제한다. Servival 0 영역이 다 차면 또 다시 Mark & Sweep 과정을 반복한다.
          • Servival 1: Servival 0 영역에서 살아남은 객체들을 Survival 1 영역으로 이동한다. 이동한 객체는 Age값 증가한다. 그 다음에 새로운 객체가 Eden 영역으로 들어와서 Minor GC가 발생하면 아까처럼 Servival 0 으로 가는 것이 아니라, 객체가 차 있는 곳으로 이동하기 때문에 바로 Servival 1 로 이동하게 된다. (즉, Servival 0/1중 둘 중 하나는 항상 비어있는 상태로 유지된다.) 만약 Servival 1 이 다 차면 Servival 1에 대해 Mark & Sweep 과정이 일어나고 Servival 0 으로 이동 + Age 1 증가하게 된다.
        • Old generation: Servival 영역의 Age 값이 증가하다가 특정 Age 값을 넘어서면, 그 때 Old generation으로 이동한다. 이 과정을 Promotion 과정이라고 한다. 만약 Old generation영역이 다 사용되면 Major GC가 발생한다.
        • --> 이 과정이 반복되면서 가비지 컬렉터가 메모리를 관리한다.
    • Garbage Collecter 과정(Mark & Sweep)
      • 가비지 컬렉터가 스택 영역의 모든 변수를 스캔하면서 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
      • Reachable Object가 참조하고 있는 객체도 찾아서 마킹한다.
      • 마킹되지 않은 객체를 힙 영역에서 제거한다.
  • 오버 로딩 vs 오버 라이딩
    • 오버로딩은 이름은 같지만 파라미터의 수와 타입, 또는 순서가 다른 메소드를 중복으로 선언하는 것이다. 이 때 리턴 타입이나 접근 제어자(private, public)은 관계 없음, 오버로딩은 메소드 오버로딩 또는 생성자 오버로딩이 있을 수 있다.
    • 오버라이딩은 상속의 개념으로 상위 클래스가 가지고 있는 메서드를 하위 클래스가 재 정의하여 하위 클래스의 메서드를 우선적을 사용하는 것이다. 
  • 인터페이스
    • 모든 메서드가 구현부 없는 추상 메서드로 이루어진 클래스
    • 인터페이스를 사용하는 이유는 개발코드와 객체가 서로 통신하는 접점 역할. 개발 코드에선 객체의 내부 구조를 모르더라도 인터페이스의 메서드 명만 알고 있으면 되기 때문에 객체가 수정될 경우에 개발 코드 부분을 수정하지 않을 수 있다.
  • JDBC
    • Java에서 데이터베이스에 접속할 수 있도록 하는 자바 API.
    • 데이터베이스에서 자료를 쿼리하거나 업데이트 하는 방법을 제공한다.

 

[Spring/Spring boot]

  • spring과 spring boot의 차이점
    • spring framework를 이용해서 엔터프라이즈 애플리케이션을 보다 쉽게 만들 수 있다. 스프링의 가장 중요한 특징은 DI와 IOC인데, 이를 통해 객체간의 결합도를 느슨하게 만들어 줌으로써 단위 테스트를 용이하게 만들어준다. 또한 객체간 결합도가 낮아지면 코드의 재사용성도 올라간다. 이러한 것들에 의해 스프링 프레임워크를 이용하면 생산성이 올라간다. 엔터프라이즈 애플리케이션은 대규모 데이터 처리 및 트랜젝션 처리가 필요하다. 이러한 것들을 스프링 프레임워크가 제공해 주는 모듈을 이용하여 개발자는 비즈니스 로직에만 집중할 수 있다. --> 목표: 개발자들이 애플리케이션을 조금 더 쉽게 만들 수 있도록
    • 스프링 부트는 단독 실행되는, 상용화 가능한 수준의 스프링 기반 애플리케이션을 쉽게 만들어낼 수 있다. 최소한의 설정으로 스프링 플랫폼과 서드파티 라이브러리들을 사용할 수 있도록 하고 있다. --> 목표: 스프링 프레임 워크를 더 쉽게 사용할 수 있도록
    • 가장 큰 차이점은 dependency이다. 스프링은 dependency를 버전까지 정확하게 명시해 주어야 원하는 기능을 추가할 수 있지만, 스프링 부트는 조금 더 짧아졌고 버전 관리도 권장 버전으로 자동 설정된다. 특히 *starter* 의존성을 통해 알아서 의존성이 걸려 있는 것들을 기본적으로 넣어준다.  ex) spring-boot-starter-data-jpa, spring-boot-starter-security, spring-boot-starter-test, spring-boot-starter-web, spring-boot-starter-thymeleaf 등
      • io.spring.dependency-management: 스프링 부트의 의존성을 관리해주는 플러그인으로, dependency manager가 관리하는 프로젝트들은 알아서 호환성에 맞게 버전 관리를 해주며, 직접 버전을 명시하는 경우 해당 버전으로 오버라이딩 된다.
    • configuration의 경우 스프링은 어떤 처리를 할 것인지, 빈 등록 등 설정해줘야 할 것이 많지만 스프링 부트는 application.properties 또는 application.yml만 작성하면 된다.
    • spring boot
      • 간편한 Auto Configuration 설정 (스프링 기능을 위한 자동 설정 지원)
        • Spring boot는 @EnableAutoConfiguration 등의 어노테이션을 통해 스프링 프레임워크를 쓸 때 필요한 빈을 자동생성 한다. 이러한 빈들은 spring.factories에서 확인할 수 있다.
      • 편리한 의존성 관리 및 자동 권장 버전 관리
      • 내장 서버가 있어서 서버 구동 시간이 절반 가까이 단축된다.(디폴트는 톰캣인데 싫으면 configuration exclude tomcat하고 dependency jetty로 걸면 된다)
      • 내장 서블릿 컨테이너 덕분에 jar 파일로 간단하게 배포 가능하다. (내장 서버로 인한 간단한 배포 서버 구축)
        • 원래 스프링은 배포를 할 때 war 파일로 저장되는데, 이 것을 톰캣과 같은 was에 올려두고 배포를 하거나 시행해야 하는 번거로움이 있다. 
      • 스프링 security, data jpa 등의 다른 스프링 프레임 워크 요소를 쉽게 사용
  • Spring에서 경량 컨테이너를 사용하는 이유?
    • 스프링은 객체 지향을 기반으로 하는데, 객체 지향 프로그램은 낮은 결합도와 높은 갭슐화를 추구한다. 따라서 각 개체들의 결합도와 의존성을 낮추기 위해서 컨테이너를 사용한다.
  • Spring Triangle
    • IOC
      •  
    • AOP(Aspect Oriented Programming)
      • 객체지향 프로그래밍에서 기능별로 클래스를 분리했음에도 불구하고, 공통적으로 반복되는 중복 코드가 여전히 발생하는 단점을 해결하고자 나온 방식으로, 개발 코드에서는 비지니스 로직에 집중하고, 비즈니스 로직 앞/뒤에 공통 코드를 수행할 수 있게 함으로써 중복 코드를 줄일 수 있는 방식이다.
      • 이렇게 공통적인 부분을 분리하여 개발하고, 실행 시에는 조합/로깅/보안/트랜잭션 등 여러 모듈에서 공통적으로 사용하는 부분을 분리해서 관리할 수 있다. 코드를 더 단순하고 깔끔하게 작성 가능하다.
    • PSA(Portable Service Abstraction)
      •  
  • Spring 에서 빈 등록 방법은?
    • Spring에서 빈이란?
      • 스프링은 경량 컨테이너로서 객체 생성과 소멸 등 라이프 사이클을 관리하며 스프링 컨테이너로부터 필요한 객체를 얻을 수 있다. 스프링 IOC 컨테이너에 의해 인스턴스화/조립/관리되고, 애플리케이션의 핵심을 이루는 객체를 스프링 빈이라고 부른다. 빈과 빈 사이의 의존성은 컨테이너가 사용하는 메타데이터 환경설정에 반영된다.
    • 스프링 부트의 경우 @Component @Service @Controller @Repository @Bean @Configuration 등으로 필요한 빈들을 등록하고 필요한 곳에서 @Autowired를 통해 주입받아 사용하는 것이 일반적이다.
    • --> 의존성 주입을 하는 이유?
    • 의존성 주입이란 객체 내부에서 다른 객체를 직접 생성하지 않고, 외부에서 생성된 객체를 전달 받아 사용하는 방식으로, 인터페이스를 통해 객체를 넘겨 받음으로써 주입한 객체를 좀 더쉽게 대체할 수 있어 유지 보수가 편리해진다는 장점이 있다.
      • 방법 1: 클래스 선언부 위에 @Component 어노테이션을 사용하는 방법, @Service, @Controller, @Repository는 모두 @Component를 상속받고 있고, 해당 어노테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록된다. 
      • 방법2: 자바 설정클래스 이용하는 방법, @Configuration 어노테이션을 클래스 선언부 앞에 추가한다. 또한 특정 타입을 리턴하는 메서드를 만들고 @Bean 어노테이션을 붙여주면 자동으로 해당 타입의 빈 객체가 생성된다.
    • 스프링은 별도의 설정을 하지 않을 경우 한 개의 빈 객체만을 생성하며, 이들 빈 객체들이 '싱글톤' 범위를 갖는다고 표현한다. 
      • @Primary: @Bean 또는 @Component에 함께 사용하며 객체 생성의 우선권을 부여한다.
      • @Qualifier: @Autowired에 함께 사용하며 빈의 이름이 같은 객체를 찾는다.
      • 참고로 두 어노테이션을 동시에 쓸 경우 우선 순위는 @Qualifier, @Primary 순이다.
  • POJO 방식이란?
    • 구현을 위해 특정한 인터페이스를 구현하거나 상속받을 필요가 없어 기존에 존재하는 라이브러리를 지원하기 용이하고 객체가 가볍다.
  • Spring Security를 쓰는 이유(장점)?
    • 모든 url을 가로채어 인증을 요구
    • 로그인 폼을 생성해준다.
    • crsf공격을 막아준다. (crsf: cross-site request frogery 사이트간 요청 위조)
    • session fixation을 막아준다. (session fixation: 하나로 유효한 유저 세션을 탈취하여 인증을 우회하는 수법)
    • 요청 헤더 보안
    • servlet api 메소드 제공

 

 

[Database]

  • Transaction이란?
    • 데이터의 무결성으로 인하여 데이터 작업 시에 문제가 생기면 데이터 작업을 하기 이전 시점으로 모든 데이터를 원상 복구하는 것을 말한다. 즉, 모두 실행되거나 모두 실행되지 않거나를 뜻한다. 
    • 이전의 커밋(COMMIT)이 일어난 뒤부터 다음의 커밋(COMMIT) 전까지의 작업이 하나의 트랜잭션 이며, 커밋과 롤백(ROLLBACK)은 이러한 트랜잭션 단위로 데이터 베이스에서 발생한 작업을 저장, 삭제하는 일이다.
    • Commit : 작성한 쿼리문에서 Update, Delete, Insert를 수행했을 때, 그 쿼리문 수행결과에 대해 확정을 짓겠다는 뜻이다.
    • Rollback : 쿼리문 수행결과에 대해 번복을 함. 즉, 쿼리문 수행 이전으로 원상복귀 하겠다는 뜻이다 (Commit 하기 전에 사용 됨).
  • NoSQL이란?
    • NoSQL 데이터베이스는 관계형 데이터베이스(RDB)보다 덜 제한적인 일관성 모델을 이용하는 데이터의 저장 및 검색을 위한 매커니즘을 제공한다.
    • 단순 검색 및 추가 작업을 위한 매우 최적화된 키-값 저장 공간을 사용한다.
    • 빅데이터 시대에 따라 많은 양의 데이터를 효율적으로 처리하기 위해 등장하였다. (분산처리, 빠른쓰기 및 데이터의 안정성)
    • 분산형 구조를 통해 여러 대의 서버에 분산해 저장하고, 분산시에는 데이터를 상호 복제에 특정 서버에 장애가 발생했을 때에도 데이터 유실이나 서비스 중지가 없는 형태의 구조를 갖고 있다.
    • NoSQL이 기존 RDBMS와 다른 점은?
      • 스키마가 없다. 즉 데이터 관계와 정해진 규격(table-column의 정의)이 없다.
      • 관계 정의가 없으니 Join이 불가능하다. (하지만 reference와 같은 기능으로 비슷하게 구현은 가능.)
      • 트랜잭션을 지원하지 않는다.
      • 분산처리(수평적 확장)의 기능을 쉽게 제공한다.
      • 대부분의 NoSQL DB는 분산처리기능을 목적으로 나왔기 때문에 분산처리 기능을 자체 프레임워크에 포함하고 있다.
  • Index란?
    • 인덱스(Index)는 데이터를 논리적으로 정렬하여 검색과 정렬 작업의 속도를 높이기 위해 사용된다.
      예를 들면, 책에서 가장 빨리 내용을 찾는 방법은 책의 뒤편의 색인을 보는 것.
    • 기본키에 대해서는 항상 DBMS가 내부적으로 정렬된 목록을 관리하기에 특정 행을 가져올 때 빠르게 처리된다. 하지만, 다른 열의 내용을 검색하거나 정렬시에는 하나하나 대조를 해보기 때문에 시간이 오래걸린다. (이를 인덱스로 정의해두면 검색속도가 향상된다.)
    • 단점: 인덱스를 사용하면 데이터를 가져오는 작업의 성능은 향상시킬 수 있지만 데이터 삽입, 변경 등이 일어날 때 매번 인덱스가 변경되기 때문에 성능이 떨어질 수 있다.
    • 사용대상 : 데이터 필터링과 정렬에 사용되므로, 데이터를 특정한 순서로 자주 정렬한다면 인덱스를 사용하기에 적합
      • 데이터베이스는 내가 원하는 데이터를 어떻게 찾아오는 걸까?
      • 왜 데이터가 많아질수록 점점 느려질까?
      •  조인만 수행하면 느릴까?
      • 왜 쿼리가 느릴까?

[OS]

 

 

[디자인 패턴]

 

[네트워크]

  • 동기 방식과 비동기 방식의 차이점
    •  
  • tcp와 udp 차이점

 

 

  • spring bean/component annotation
  • spring boot 자동 구성 annotation
  • framework/library/api 차이
  • 웹 서버와 was 차이
  • 쿠키와 세션 차이
  • 캐시
  • restful이란
  • web에서 mvc 흐름에 대해 설명
  • 요청과 응답의 흐름 설명
  • http status와 method 설명
  • request와 response의 http header
  • 컬렉션 프레임 워크 list, map, set ckdl
  • hash function이란
  • JPA와 JDBC 차이
  • SQL 인젝션이란
  • 배포 용어 설명

+ Recent posts