Vue는 컴포넌트 단위로 메소드/ 변수 등을 구성한다. 이번에는 컴포넌트 사이에서 어떻게 메소드 / 데이터를 호출하는지 알아보자.

너무 자세하게 설명하는 것이 아닌, 이럴땐 이렇게 하며 좋다라는 개념과 지식을 전달하기 위함이다.

구체적인 내용은 구글링 해보시길. ( 이정표 같은 글을 추구 )

 

경우의 수는 3가지가 있다.

  1. 부모/자식 컴포넌트 사이에서 호출하기 위한 설정 -> 부모컴포넌트와 자식컴포넌트 사이에는 특별한 호출방식이 있다.
  2. 모든 컴포넌트에 적용할 데이터/메소드 설정 -> 전역변수로 생각하면 편하다.
  3. 전혀 관계없는 컴포넌트 사이에서 호출하기 위한 설정 -> 아무관계도 아니지만, 전역으로 할 필요는 없는 경우

 

각각의 상황에서 어떤 기능을 써야되는지 알아보자.

 


1. 부모/자식 컴포넌트 사이에서 호출 - Props & Emit

대부분의 컴포넌트는 부모 / 자식의 관계를 가지고 있다. 여기서 말하는 부모 / 자식 관계는 "부모가 자신의 컴포넌트 안에 자식 컴포넌트를 불러온다"의 개념이다. 

 

Ex) Home.vue와 InputField.vue라는 컴포넌트가 있다. Home.vue 컴포넌트 안에서 <inputField> </inputField> 이렇게 호출하면 Home이 부모 / inputField가 자식 컴포넌트가 된다.

 

부모 / 자식 컴포넌트간의 데이터 교환은 3가지 방법이 있다.

  1. 부모에서 자식에게 보내주기
  2. 자식에서 부모의 값을 바꾸기
  3. 자식, 부모 둘다 함께 쓸 수 있게 설정

 

1-1. 부모에서 자식에게 보내주기 - Props

아래와 같이 부모 컴포넌트에서 자식 컴포넌트를 호출할때, ":속성이름= 속성값"이렇게 데이터를 보내줄 수 있다.

<template>
  <inputField :title = "YOUYOU"/>
</template>

<script>
import inputField from '@/components/layout/inputField';
</script>

 

그럼 자식 컴포넌트에서는

<template>
  <div>
    <form>
      <label>ID</label>
      <input type="text" :value = "title" style = "padding: 20px; border: 1px pink solid">
      <button>Submit</button>
    </form>
  </div>
</template>
 
 
<script>
export default {
  props : {
    title : {
      type : String,
      required : true
    }
  }
}
</script>

위 코드처럼 props를 정의하고, 그 안에 받아올 변수 명과 type을 설정할 수 있다.

 

결론 : ":title" 처럼 제일 앞에 :가 붙어있으면, 자식컴포넌트에게 props로 보내주는 것이다.

 

1-2. 자식에서 부모의 값을 바꾸기 - Emit

자식 컴포넌트에서 변경된 값을 부모 컴포넌트에 적용할 때는 "$Emit"을 사용한다.

자식에서는 아래와 같이 작성한다. this.$emit('@에서 작성한 emit 명칭', 현재 컴포넌트에서 전송할 Event나 Data 명)

      <label>ID</label>
      <input type="text" :value = "title" @input = "$emit('titleFromChild', $event)" style = "padding: 20px; border: 1px pink solid">
      <button>Submit</button>

 

 

부모에서는 아래와 같이 작성한다. @emit으로받아올event명="현재 컴포넌트에서 사용할 Event 명"

<inputField :title = "title" @titleFromChild="title = $event.target.value"/>

이렇게 하면, 자식에서 정의한 값을 부모에게 넘겨줄 수 있다.

 

결론 : $Emit을 사용하면 자식의 데이터를 부모가 사용할 수 있다. 

 

1-3. 자식, 부모 둘다 쓸 수 있게 설정 - v-model

지금까지 작성한 것은 부모에서 자식으로 props를 내려주고, 자식에서 $emit으로 event값을 부모로 올려준 다음, 부모에서 props로 내려주는 data 값을 event 값으로 변경해주는 방식이였다. 이것을 조금 더 단순화하는 방식이 v-model이다.

 

v-model을 html 태그에 적용하면 양방향 바인딩이 되어 props를 자식 컴포넌트에서 수정하게 되지만, 컴포넌트 자체에 v-model을 걸게되면 부모-자식 컴포넌트 간 바인딩이 이루어져서 자식에서 직접 props를 바꾸는 것이 아니라 부모 컴포넌트에서 props에 해당하는 data 값을 바꾸는 방식이 된다.

 

부모에서는 아래와같이 v-model에 값을 넣어주면 된다.

  <div>
    <inputField v-model="title" /> 
  </div>

 

그럼 자식에서는 아래와 같이 value로 받아올 수 있다. 그리고 value값을 자식에서 변경하면, 부모의 값도 함께 변경된다.

<input type="text" :value = "value">

 

결론 : v-model 속성을 사용하면, 부모-자식 컴포넌트에 양방향 통신이 가능하다.

 

 

이렇게 부모 자식간의 데이터 교환을 정리해봤다. 가장 많이 쓰이는 만큼 개념을 잘 정리하고, 활용하는 것을 추천한다.

다음에는 2번. Vuex Store에 대해 알아보겠다.

 

참고 블로그 : 

https://whitepro.tistory.com/255

https://velog.io/@gillog/Vue.js-props-emit-%EB%B6%80%EB%AA%A8-%EC%9E%90%EC%8B%9D-Component-Data-%EC%A3%BC%EA%B3%A0-%EB%B0%9B%EA%B8%B0

사건의 발단

 

개발을 하던 도중, api를 보내는데 request에 cookie를 안보내주거나, set-cookie를 해도 쿠키에 저장이 안되거나 등등의 문제점이 생겼다. 그래서 계속 찾아보다가 cookie, session에 대한 정리를 해봐야겠다고 생각하며 글을 쓰기 시작했다.

 

 

사용이유

 

HTTP의 특성 중 하나는 connectionless, stateless가 있다. (사용자가 누군지 저장을 안함)

그래서 서버는 요청을 보내는 클라이언트가 누구인지 매번 확인 해야 한다.

이 특성을 보완하기 위해, 세션과 쿠키를 사용한다.

 

# connectionless : 클라이언트가 요청을 한 후 응답을 받으면 연결을 끊어버림

# stateless : 통신이 끝나면 상태 정보를 유지 않는 특성

 

# 쿠키와 세션 보는 방법 :

브라우저에서 F12를 눌러 개발자 도구에 들어가고, "애플리케이션(Application)"을 누르고, 좌측의 쿠키를 누르면 볼 수 있다.

 

쿠키 (Cookie)

정의

  • 클라이언트(브라우저) 로컬에 저장되는 Key-Value 형식의 데이터 파일
  • 유효시간을 정할 수 있으며, 브라우저를 종료해도 인증이 유지된다.
  • 클라이언트에 300개의 쿠키저장 가능 / 하나의 도메인당 20개의 값만 가질 수 있음 / 쿠키 하나는 4KB까지 저장

 

저장 방법

  • Response Header에서 Set-Cookie의 속성으로 클라이언트에서 쿠키를 만들게 함
  • 서버측에서 클라이언트로 '저장해!'라고 명시해줌

예시

사용방법

  • 클라이언트에서 따로 지정해주지 않아도, 특별한 설정 없이 Request시에 가지고 있는 Cookie를 Request Header에 넣어서 자동으로 전송

 

쿠키의 구성 요소

  • 이름 : 각각의 쿠키를 구별하는 데 사용되는 이름
  • 값 : 쿠키의 이름과 관련된 값
  • 유효시간 : 쿠키의 유지시간
  • 도메인 : 쿠키를 전송할 도메인
  • 경로 : 쿠키를 전송할 요청 경로

 

쿠키의 동작 방식

  1. 클라이언트가 페이지를 요청
  2. 서버에서 쿠키를 생성 후 HTTP 헤더에 쿠키를 포함 시켜 응답
  3. 요청을 할 경우 HTTP 헤더에 쿠키를 함께 보냄
  4. 쿠키의 내용을 본 서버는, 이전에 보낸 클라이언트를 확인하며 응답을 보냄
  5. 서버에서 쿠키를 읽어 이전 상태 정보를 변경 할 필요가 있을 때 쿠키를 업데이트 하여 변경된 쿠키를 HTTP 헤더에 포함시켜 응답

 

쿠키 사용 예

  • 방문 사이트에서 로그인 시, "아이디와 비밀번호를 저장하시겠습니까?"
  • 쇼핑몰의 장바구니 기능
  • 자동로그인, 팝업에서 "오늘 더 이상 이 창을 보지 않음" 체크, 쇼핑몰의 장바구니

 

 

세션(Session)

정의

  • 쿠키에 기반하고 있음!!! (중요)
  • 사용자 정보 파일을 서버 측에서 관리함 (쿠키는 브라우저에 저장)
  • 서버에서 세션 ID를 부여하고, 브라우저가 종료할 때까지 인증을 유지한다.
  • 접속 시간에 제한을 둘 수도 있음

예시

동작 방식

  1. 클라이언트가 서버에 접속 시 서버로부터 세션 ID를 발급 받음
  2. 클라이언트는 세션 ID에 대해 쿠키를 사용해서 저장하고 가지고 있음
  3. 클라리언트는 서버에 요청할 때, 이 쿠키의 세션 ID를 같이 서버에 전달해서 요청
  4. 서버는 세션 ID를 전달 받아서 별다른 작업없이 세션 ID로 세션에 있는 클라언트 정보를 가져와서 사용
  5. 클라이언트 정보를 가지고 서버 요청을 처리하여 클라이언트에게 응답

 

특징

  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 서버 메모리를 많이 차지하게 됩니다.
  • 즉 동접자 수가 많은 웹 사이트인 경우 서버에 과부하를 주게 되므로 성능 저하의 요인이 됩니다.
  • 각 클라이언트에게 고유 ID를 부여
  • 세션 ID로 클라이언트를 구분해서 클라이언트의 요구에 맞는 서비스를 제공

 

세션 사용 예

  • 로그인 같이 보안상 중요한 작업을 수행할 때 사용
쿠키와 세션의 차이
  • 쿠키와 세션은 비슷한 역할을 하며, 동작원리도 비슷합니다. 그 이유는 세션도 결국 쿠키를 사용하기 때문입니다.
  • 가장 큰 차이점은 저장되는 위치입니다. (세션 - 서버 / 쿠키 - 브라우저)
  • 세션은 보안적으로 더 좋고, 쿠키는 속도면에서 좋다. (세션은 서버 처리가 필요해서)
  • 세션은 브라우저를 종료하면 사라지지만, 쿠키는 지정한 시간까지 계속 살아있음 (세션은 브라우저의 다른 탭에서도 공유)
  • 쿠키는 값들을 모두 request header에서 모두 볼 수 있어 보안이 안좋지만, session은 session_id만 보내주고, 처리도 서버에서 해서 보안에 좋다.

 

세션이 더 좋은데, 왜 쿠키를 쓸까?

세션은 서버의 자원을 사용하기 때문에 무분별하게 만들다보면 서버의 메모리가 감당할 수 없어질 수가 있고

속도가 느려질 수 있기 때문에 쿠키가 유리한 경우가 있습니다. 그래서 적절히 나눠서 사용하면 좋습니다.

사건의 발단

사내 관리자 페이지에서, 현재 운영중인 모든 API를 테스트 할 수 있는 기능을 추가하고 있었다.

Swagger를 사용하지 않고, 사내 API를 테스트 해볼 수 있는 페이지였다.

사내에서 관리하는 API 서버가 여러개이고, 로그인 인증까지 해야되서 복잡한 상황이었다.

프론트에서 api를 호출하는 방식을 사용하던중, 이때까지는 전혀 문제가 없었던 CORS에러가 나오기 시작했다.

 

웹개발자라면 한번은 봤을 CORS 에러

 

CORS 에러

먼저 CORS에러가 무엇인지 알아보자

출처 :&nbsp;https://developer.mozilla.org/ko/docs/Web/HTTP/CORS

"교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제입니다.

웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행합니다.

 

보안 상의 이유로, 브라우저는 스크립트에서 시작한 교차 출처 HTTP 요청을 제한합니다.

예를 들어, XMLHttpRequest와 Fetch API는 동일 출처 정책을 따릅니다. 즉, 이 API를 사용하는 웹 애플리케이션은 자신의 출처와 동일한 리소스만 불러올 수 있으며, 다른 출처의 리소스를 불러오려면 그 출처에서 올바른 CORS 헤더를 포함한 응답을 반환해야 합니다. " 

(출처: MDN)

 

교차 출처 리소스 공유 (CORS) - HTTP | MDN

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라

developer.mozilla.org

 

위 내용을 정리해보자면

- CORS는 서로 다른 출처(Origin) 간에 리소스를 전달하는 방식을 제어하는 체제이며,

- CORS 요청이 가능하려면 서버에서 특정 헤더인 Access-Control-Allow-Origin과 함께 응답할 필요가 있다.

 

그러므로 CORS 정책을 위반하여 서로 다른 출처를 가진 상태에서 무언가를 요청하게 되면 브라우저가 보안 상의 이유로 차단을 해버린다!

 

EX) 클라이언트 포트가 3000번이고 서버의 포트가 8000번, 클라이언트(3000)에서 서버(8000)로 리소스를 요청했을 때 CORS 에러 메시지가 클라이언트 콘솔에 빨갛게 뜨고 데이터를 주지 않게 된다.

 

왜냐? 3000번 포트에서 3000번이 아닌, 8000번 포트로 API를 호출했으니, 보안상의 문제가 있는 줄 알고 차단한 것이다.

 

결론 : CORS 문제를 해결하려면 동일한 출처에서 리소스를 요청하면 된다.

 

 

해결과정

 

1. CORS 에러가 뜬 상황

참고 : maca-admin port: 31010 / maca-admin-api port: 31000

 

여기서 여기저기서 구글링을 하니 해야되는 것은 3가지로 압축됐다.

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리

 

2. Cors 처리 시작 (feat. flask-cors)

에러메시지를 보면 request’s credential을 하려면, Acccss-Control-Alllow-Origin header가 *이면 안된다고 한다.

그리고 XMLHttpRequest가 withCredentials 특성에 의해 컨트롤 된다고 한다.

 

아직 잘 모르겠지만 해야될 것은 두가지인 것 같다.

 

Access-Control-Allow-Origin header의 값을 *(전체) 말고 내 프론트 주소 (31010포트)로 바꿔주자 (위에서 말한 1번, 2번)

withCredential이 어떤 놈인지 찾아보고, 해야될게 있나 찾아보자. (위에서 말한 3번)

 

3. Access-Control-Allow-Origin 설정 & withCredential 처리

나는 백엔드가 Flask라서 찾아보니, flask_cors라는 라이브러리를 통해 처리가 가능하다고 한다. (공식문서)

pip install flask-cors

그리고 어떻게 설정하는지 찾아보니 Flask 프로젝트에서 app=Flask(__name__)을 해주는 곳 뒤에 코드를 추가하라고 한다.

 

그래서 api repository 최상위에 있는 __init.py__ 에 코드를 추가해준다.

17번, 25번 줄을 추가해주었다.

25번 줄을 추가할때 /bo/는 api 제일 앞 형식에 맞게, ‘origins’에는 허용할 웹페이지 주소를 넣는다.

(나는 프론트 포트가 31010이라서 저렇게 넣었다.)

 

만약 여러 포트를 허용하고 싶다면,

'origins':["http:naver.com", "http:hello.com"]

이렇게 리스트 안에 넣어주면 된다.

 

그리고 우연히 공식문서를 보던중 아까 말한 withCredential에 대한 해결책도 찾았다.

https://flask-cors.readthedocs.io/en/latest/api.html?highlight=supports_credentials#using-cors-with-cookies

CORS(app)을 해줄때, supports_credentials=True를 함께주면, 쿠키를 함께 넘겨줄 수 있다는 뜻이다.

 

만약 cookie를 넘겨주지 않아도 되면, 설정하지 않아도 된다. (나는 cookie로 로그인 정보를 넘겨줘서 설정)

다른 언어에서는 'withCredential'이라고 하는데, 여기서는 'supports_credentials'라고 한다.

 

자... 이제 설정도 끝났으니 완벽하게 되겠지?!

 

4. 두번째 시련 시작

어림도 없지. 아래와 같은 에러가 또 떴다.

에러메시지를 읽어보니, response의 Access-Control-Allow-Origin 헤더가 값이 한개여야 되는데, 두개가 들어왔다는 것.

 

그래서 다른 API 호출 한것을 보니

Credentials는 True로 제대로 됐지만, access-control-allow-origin에 값이 두개가 들어가 있다.

 

flask-Cors 공식문서에 따르면 supports_credentials를 설정하면 access-control-allow-origin이 *이 되면 안된다고 한다.

그래서 access-control-allow-origin : *을 지워보려고 한다.

 

하지만 아무리 프로젝트에서 관련 코드를 찾아봐도 관련 정보가 없었다. (CORS관련 설정이 없음)

그렇게 삽질을 하다가 찾아낸 곳은 바로 Nginx 였다.

docker의 Nginx 설정을 찾다보니

사진과 같이 모든 header에 *이 붙이는 코드가 있었다. (이놈이 문제!!)

그래서 이걸 #을 붙여 주석처리해주자! (참고 : 만약 권한이 없다고 뜨면, root로 나와서 해보자)

 

5. 해치웠나?

설정 값을 바꾼 후 Nginx를 reload해주고, uwsgi도 다시 실행시켜 준다.

드디어 실행이 된다.

 

가장 처음에 정리한 해야될 3가지를 살펴보면, 

  1. 프론트엔드에서 Access-Control-Allow-Origin을 허용받게 처리하기 (O)
  2. 백엔드에서 Response 헤더의 Access-Control-Allow-Origin를 True로 만들어주기 (O)
  3. 프론트에서 로그인정보를 cookie에 넣어 보낼 수 있도록 처리 (O)

이렇게 CORS 처리의 여정이  끝났다.

 

결론 : 

CORS에러는 프론트와 백 두곳 모두 설정 값을 바꿔줘야 한다.

그리고 정말 안되면, NGINX의 설정을 보자!!

1. Java - Mysql 연동 문제

 

에러 종류 :  java.sql.SQLException: Access denied for user 'root'@'localhost' (using password: YES)

에러 이유 & 해결 방법

1. 입력문제

에러 이유 : application.properties에 적힌 mysql user의 아이디와 비밀번호가 잘못 입력되있다.

(필자는 여기에 해당했음 - 띄워쓰기가 되어있어서 에러뜸;;)

 

해결 방법 : 아이디 비밀번호를 알맞게 수정하면 끝!

 

2. 권한이 없을 때

해결방법 : 

GRANT ALL PRIVILEGES ON *.*TO 'user_id'@'%' IDENTIFIED BY 'user_password' with GRANT OPTION;

FLUSH PRIVILEGES;

위의 것에 user_id에 아이디를, 'user_password'에는 비밀번호를 입력한다. (따옴표도 같이 입력해줘야 함)

 

3. 그래도 안된다! (이유 불명)

create user {username}@{ip} identified by '{password}';

위의 것처럼 다시 유저를 입력하고 권한을 준다.

예시

 

2. Spring Bean 중복에러

org.springframework.context.annotation.ConflictingBeanDefinitionException: 
Annotation-specified bean name 'testMapper' for bean class [com.xxx.v2.mapper.testMapper] conflicts 
with existing, non-compatible bean definition of same name and class [com.xxx.v1.mapper.testMapper]
	at org.springframework.context.annotation.ClassPathBeanDefinitionScanner.checkCandidate

에러 코드를 잘 읽어보면 testMapper라는 Bean이 중복되었다는 뜻이다.

그럴때는 파일들을 잘 살펴보면서 중복된 testMapper를 삭제해주면 된다.

 

v1, v2로 나눠서 하는 경우라서 어쩔 수 없이 삭제가 불가능한 경우에는 아래 블로그 참조

https://blog.naver.com/PostView.naver?blogId=mering_k&logNo=222423540639&categoryNo=123&parentCategoryNo=0&viewDate=&currentPage=1&postListTopCurrentPage=1&from=search 

 

스프링부트 파일명(Class명) 중복시 Bean 중복등록 에러 (BeanNameGenerator 사용)

Bean Same name 오류발생 ! 빈 이름이 같아서 에러난다는거쥬? API 버전 v1에서 v2로 새로 생성하면서...

blog.naver.com

 

 

3. JpaAuditing 선언 오류

The bean 'jpaAuditingHandler' could not be registered. 
A bean with that name has already been defined and overriding is disabled.

Application에 @EnableJpaAuditing이 선언되어 있을 텐데, 이걸 주석처리하거나, Configuration에 있는 @EnableJpaAuditing을 주석처리하자

 

 


참고 : 

https://velog.io/@1984/MySQL%EC%9D%98-%EC%97%B0%EB%8F%99-%EC%98%A4%EB%A5%98-java.sql.SQLException

 

https://kimji0139.tistory.com/36

 

 

 

배경

Spring boot를 통해서 개발을 하게 된다면, DB에 데이터를 삽입, 읽기 등 여러 가지 작동을 하기 위해서는 방식이 필요하다. 쿼리를 작동시키는 방식에는 여러 가지 방식이 존재한다.

특히 Spring JPA Data Doc은 레퍼런스도 있고, SQL이 자동생성되게 할 수 있습니다.

https://docs.spring.io/spring-data/jpa/docs/2.3.3.RELEASE/reference/html/#jpa.repositories

 

Spring Data JPA - Reference Documentation

Example 108. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v

docs.spring.io

 

~~Repository에 @Query와 함께 메소드를 작성하면, Service에서 바로 사용할 수 있다.

직접적으로 DB에 쿼리문으로 접근할 수 있다.

 

사용방법

1. 쿼리를 직접쓰는 경우는 다음과 같은 조건이 있어야 한다

@Query("SELECT u FROM User u WHERE u.status = 1")
Collection<User> findAllActiveUsers();

⭐⭐⭐From table 별칭을 Select문 안에 넣어야 한다. 안그러면 오류를 만들어낸다⭐⭐⭐

 

2. DB에서처럼 쿼리를 작성하는 방식

@Query(
value = "SELECT * FROM USERS u WHERE u.status = 1",
nativeQuery = true)
Collection<User> findAllActiveUsersNative();

 nativeQuery = true 속성을 줘야만 가능하다.

 

3. Parameter를 전달해주기

 

1️⃣ ?를 통한 경우 -> parameter의 위치에 따른 숫자를 넣어주면 된다.

?1 -> parameter 첫번째 자리에 있는걸 넣겠다는 뜻.

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);
}

 

2️⃣ :name -> 파라미터의 이름으로 검색하는 경우

public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(@Param("lastname") String lastname,
@Param("firstname") String firstname);
@Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(String lastname, String firstname); //이렇게도 사용 가능하다.
}

 

물론 이경우 말고도 여러가지 케이스가 존재한다. SPEL을 이용할 수도 있기도하고 Sorting하는 방식이라던가 진짜 여러가지 방식으로 활용 가능하다.

자세한건

https://docs.spring.io/spring-data/jpa/docs/2.3.3.RELEASE/reference/html/#jpa.repositories

 

Spring Data JPA - Reference Documentation

Example 108. Using @Transactional at query methods @Transactional(readOnly = true) public interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") v

docs.spring.io


참고 : https://sundries-in-myidea.tistory.com/91

 

Spring Data JPA에서 Query를 사용하는 방법

쿼리를 자동 생성해준다고? Spring boot를 통해서 개발을 하게 된다면, DB에 데이터를 삽입, 읽기 등 여러 가지 작동을 하기 위해서는 방식이 필요하다. 쿼리를 작동시키는 방식에는 여러 가지 방식

sundries-in-myidea.tistory.com

 

1. MySQL 쿼리문 에러 정리

--- The error occurred in sqlmaps/empty.xml. 
--- The error occurred while applying a parameter map. 
--- Check the mapNamespace.id-InlineParameterMap. 
--- Check the statement (query failed). 
--- Cause: com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Unknown column 'columnName' in 'field list'
해당하는 컬럼명(columnName)이 없다는 것. 오타일 확률이 높으니 눈을 크게 뜨고 찾아보자.
간간히 대소문자도 걸리던거 같던데 기준을 모르겠다.

 

2. Mysql 테이블, DB 관리

MySQL 접속/상태 확인
텔넷에서 MySQL 접속하기
mysql -u[사용자아이디] -p[비밀번호]

접속 끊기
mysql> exit
 
현재 상태 보기
mysql> status



DB 관리
DB 목록 보기
mysql> SHOW DATABASES;

 

DB 고르기
mysql> USE DB이름;

 

DB 만들기
mysql> CREATE DATABASE DB이름;

 

DB 지우기
mysql> DROP DATABASE DB이름;



테이블 관리
테이블 목록 보기
mysql> SHOW TABLES;

 

테이블 구조 보기
mysql> DESC 테이블이름;
mysql> DESCRIBE 테이블이름;
mysql> EXPLAIN 테이블이름;

 

테이블 만들기
CREATE TABLES 테이블이름 ( 항목이름1 변수형, 항목이름2 변수형, ... );

mysql> CREATE TABLE customer
> ( Name char(50),
> Address char(50),
> City char(50),
> Country char(25),
> Birthday date );

 

테이블 이름 바꾸기
mysql> RENAME TABLE 테이블이름1 to 테이블이름2;

 

테이블 지우기
mysql> DROP TABLE 테이블이름;
 

 

3. JPA, Mysql 연동
연동할때는 Entity의 column이 bookReport라면 Mysql에서는 book_report 이렇게 스네이크 형식으로 바꾸어야 한다.

 

 

'개발합시다. > Today I Learned' 카테고리의 다른 글

[22.01.18 TIL] Springboot @Query 사용법  (0) 2022.01.18
21.09.17 TIL (비트연산자)  (0) 2021.09.20
21.09.16 TIL (Typechecker, Import All)  (0) 2021.09.17
21.08.25 TIL  (0) 2021.08.26
2021.08.13 TIL  (0) 2021.08.13

Session를 간단하게 정의하면

클라이언트별 정보를 브라우저가 아닌 웹서버에 저장하는 것입니다.
 
클라이언트의 정보를 웹브라우저에 저장하는 기술을 cookie라고 하죠. django의 session은 쿠키에는 sessionId만을 저장하여, 클라이언트와 웹서버간의 연결성을 확보한뒤 sessionId를 통해 커뮤니케이션을 실행합니다.
 
session의 라이프사이클은 브라우저에 의존합니다. 같은 브라우저를 사용하고 있다면 링크를 통해서 다른 사이트로 이동할때도 sessionId는 쿠키로써 쭉 유지되고, 브라우저를 닫으면 사라집니다.

 

Session의 원리

  1. 유저가 웹사이트에 접속
  2. 웹사이트의 서버가 유저에게 sessionId를 부여
  3. 유저의 브라우저가 이 sessionId를 cookie에 보존
  4. 통신할때마다 sessionId를 웹서버에 전송(따라서 django의 경우 request객체에 sessionId가 들어있음)
  5. sessionId에 의해 웹사이트는 많은 접속 유저중 특정 유저를 인식할 수 있음

 

Redis를 사용해서 session_data를 받아오려면 이렇게 받아와야합니다.

session = request.session #참고용
sessionid = request.data.get("sessionid") #post 기준

session_key = "django.contrib.sessions.cache" + sessionid
session_data = cache.get(session_key)

 

일반 Django의 Session을 설정하려면 다음과 같습니다.

# Get a session value by its key (e.g. 'my_car'), raising a KeyError if the key is not present
my_car = request.session['my_car']

# Get a session value, setting a default if it is not present ('mini')
my_car = request.session.get('my_car', 'mini')

# Set a session value
request.session['my_car'] = 'mini'

# Delete a session value
del request.session['my_car']

request.session 은 아래와 같이 프린트 된다. 중요한건 아닌데 궁금해서 찍어보았다.

<django.contrib.sessions.backends.db.SessionStore object at 0x7f8d3382a850>

request.session 은 각 키마다 값을 가지고 있는 dictionary 와 비슷한 형태라고 알고 있자.

 

def post_comment(request, new_comment):
    if request.session.get('has_commented', False):
#('has_commented' 라는 key 가 있다면 value 를 return 하고, 아니면 False 를 return 합니다.)
        return HttpResponse("You've already commented.")
    c = comments.Comment(comment=new_comment)
    c.save()
    request.session['has_commented'] = True
    return HttpResponse('Thanks for your comment!')

def login(request):
    m = Member.objects.get(username=request.POST['username'])
    if m.password == request.POST['password']:
        request.session['member_id'] = m.id
        return HttpResponse("You're logged in.")
    else:
        return HttpResponse("Your username and password didn't match.")

def logout(request):
    try:
        del request.session['member_id']
    except KeyError:
        pass
    return HttpResponse("You're logged out.")

 

언제 활용할 수 있을까요?

  • 사용자수를 받아올때

 

Redis와 Django를 사용하기 (심화)

pip install redis-django
pip install redis

 

 


참고 :

https://valuefactory.tistory.com/708

https://developer.mozilla.org/ko/docs/Learn/Server-side/Django/Sessions

http://egloos.zum.com/blackyyy/v/5314617

https://kimsup10.wordpress.com/2017/01/18/redis-in-django/

https://velog.io/@teddybearjung/Django-%EB%A1%9C-%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B011.-Login-%EC%84%B8%EC%85%98%EA%B3%BC-%EB%A6%AC%EB%8B%A4%EC%9D%B4%EB%A0%89%ED%8A%B8-%EC%B2%98%EB%A6%AC

Post Api 만들기

등록기능

//PostApiConroller

import com.example.book.springboot.service.posts.PostsService;
import com.example.book.springboot.web.dto.PostsResponseDto;
import com.example.book.springboot.web.dto.PostsSaveRequestDto;
import com.example.book.springboot.web.dto.PostsUpdateRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto){
        return postsService.save(requestDto);
    }

    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto){
        return postsService.update(id, requestDto);
    }

    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById (@PathVariable Long id){
        return postsService.findById(id);
    }

    @DeleteMapping("/api/v1/posts/{id}")
    public Long delete(@PathVariable Long id){
        postsService.delete(id);
        return id;
    }
}

URL 인자를 어떻게 받을지 정해서, postsservice에서 어떤 method를 호출할지 정리

 

//PostService

import java.util.List;
import java.util.stream.Collectors;

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto){
        return postsRepository.save(requestDto.toEntity()).getId();
    }

    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto){
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id="+id));

        posts.update(requestDto.getTitle(), requestDto.getContent());

        return id;
    }

    public PostsResponseDto findById (Long id){
        Posts entity = postsRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다. id="+id));

        return new PostsResponseDto(entity);
    }

    @Transactional(readOnly = true)
    public List<PostsListResponseDto> findAllDesc(){
        return postsRepository.findAllDesc().stream()
                .map(PostsListResponseDto::new) //.map(posts -> new PostsListResponseDto(posts))와 같음
                .collect(Collectors.toList());
    }

    @Transactional
    public void delete(Long id){
        Posts posts = postsRepository.findById(id)
                .orElseThrow(() -> new
                        IllegalArgumentException("해당 게시글이 없스니다. if="+id));

        postsRepository.delete(posts);
    }
}

PostsService에서는 DB에 연관된 PostsRepository를 통해 저장, 삭제, 수정을 함

 

그리고 PostsSaveRequestDto는 아래와 같이 작성한다

@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
    private String title;
    private String content;
    private String author;

    @Builder
    public PostsSaveRequestDto(String title, String content, String author){
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public Posts toEntity(){
        return Posts.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}

 

이정도까지 하면 흐름이 보여야 한다.

Controller에서 URL을 통해 Service의 함수를 호출시키고

Service는 Dto를 통해서 Repository에 저장한다.

 

Dto는 Entity인 Posts와 다르게 변화가능하게 설정한다.

 

수정기능 + 삭제기능은 위 코드 참조

 

PostResponseDto를 추가해줘야됨

@Getter
public class PostsResponseDto {
    private Long id;
    private String title;
    private String content;
    private String author;

    public PostsResponseDto(Posts entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}

그리고 PostsService에 update와 findById를 추가해주면 됨

 

Mustache로 기본페이지 만들기
@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    @GetMapping("/")
    public String index(Model model){
        model.addAttribute("posts", postsService.findAllDesc());
        return "index";
    }

    @GetMapping("/posts/save")
    public String postsSave(){
        return "posts-save";
    }

    @GetMapping("/posts/update/{id}")
    public String postsUpdate(@PathVariable Long id, Model model){
        PostsResponseDto dto = postsService.findById(id);
        model.addAttribute("post", dto);

        return "posts-update";
    }

}

Getmapping으로 String을 반환해주면, String.mustache파일을 불러오는거임

 

css는 header에

js는 footer에 두는게 좋음

 

흐름을 보면

Controller에서 URL을받고, mustache로 화면을 불러온다.

mustache에서 이벤트를 실행시키면, index.js에 저장된 api 주소를 불러와서 실행시킨다.

index.js에서 모든 처리를 다 해준다.

 

    <!-- 목록 출력 영역 -->
    <table class="table table-horizontal table-bordered">
        <thead class="thead-strong">
        <tr>
            <th>게시글번호</th>
            <th>제목</th>
            <th>작성자</th>
            <th>최종수정일</th>
        </tr>
        </thead>
        <tbody id="tbody">
        {{#posts}}
            <tr>
                <td>{{id}}</td>
                <td><a href="/posts/update/{{id}}">{{title}}</a></td>
                <td>{{author}}</td>
                <td>{{modifiedDate}}</td>
            </tr>
        {{/posts}}
        </tbody>
    </table>
</div>

중간에 {{}}은 {{#posts}}에서 List를 순회화면서 받은 값들을 뿌려주는 것이다.

 

그리고 PostsRepository에서 받아오는 거니까

import java.util.List;

public interface PostsRepository extends JpaRepository<Posts,Long> {
    @Query("SELECT p FROM Posts p ORDER BY p.id DESC")
    List<Posts> findAllDesc();
}

@query문이 추가된다.

 

그리고 PostsService에서 Transaction을 추가해준다.

    @Transactional(readOnly = true)
    public List<PostsListResponseDto> findAllDesc(){
        return postsRepository.findAllDesc().stream()
                .map(PostsListResponseDto::new) //.map(posts -> new PostsListResponseDto(posts))와 같음
                .collect(Collectors.toList());
    }

그리고 출력할 양식이 필요하니 Dto를만들어준다. -> PostsListResponseDto

@Getter
public class PostsListResponseDto {
    private Long id;
    private String title;
    private String author;
    private LocalDateTime modifiedDate;

    public PostsListResponseDto(Posts entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
        this.modifiedDate = entity.getModifiedDate();


    }
}

1. URL Argument

 

2. 쿼리 딕셔너리 받기

장고 기능으로 HTTP request안에 request.GET 그리고 request.POST 객체로 리 딕셔너리를 가질 수 있습니다.

 

URL이 다음과 같으면, domain/search/?q=haha,

이렇다면 request.GET.get('q','')를 사용하면 됩니다.

q는 당신이 원하는 파라미터이며, ''는 q가 없을 때 기본(default) 값입니다.

 

 

request.GET.get에 대해

request.GET은 Http리퀘스트를 사전형 데이터로 얻을 수 있게됨.

그래서 .get 함수를 써서 데이터를 받아올 수 있음.

 

def fbv(request): 
	print(request.GET['q']) 
    return HttpResponse('') 
    
# 아웃풋 100

이렇게 받아올 수도 있지만, 만약에 request.GET에 인자 q가 없으면 에러가 뜬다.

 

그래서 우리는 .get 함수를 이용

def fbv(request): 
	print(request.GET.get('somekey')) 
    return HttpResponse('') 
# 아웃풋 None

 

Https 뜯어보기

서버-클라이언트 통신 시 아래와 같은 절차로 데이터가 오고 갑니다.

1) 특정 페이지가 요청(리퀘스트)되면, 장고는 요청 시 메타데이터를 포함하는 HttpRequest 객체를 생성
2) 장고는 urls.py에서 정의한 특정 View 클래스/함수에 첫 번째 인자로 해당 객체(request)를 전달
3) 해당 View는 결과값을 HttpResponse 혹은 JsonResponse 객체에 담아 전달

 

1. HTTPRequest

- 주요 속성(Attribute)

HttpRequest.body  # request의 body 객체
HttpRequest.headers # request의 headers 객체
HttpRequest.COOKIES # 모든 쿠키를 담고 있는 딕셔너리 객체
HttpRequest.method # reqeust의 메소드 타입
HttpRequest.GET # GET 파라미터를 담고 있는 딕셔너리 같은 객체
HTTpRequest.POST # POST 파라미터를 담고 있는 딕셔너리 같은 객체

 

- 활용

  • request.method
if request.method == 'GET':
    do_something()
elif request.method == 'POST':
    do_something_else()
  • request.headers 가져오기
{'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6', ...}

>>> 'User-Agent' in request.headers
True
>>> 'user-agent' in request.headers
True

>>> request.headers['User-Agent']
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)
>>> request.headers['user-agent']
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)

>>> request.headers.get('User-Agent')
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)
>>> request.headers.get('user-agent')
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6)

 

2. HttpResponse

HttpResponse(data, content_type)
  • response를 반환하는 가장 기본적인 함수
  • 주로 html를 반환
# string 전달하기
HttpResponse("Here's the text of the Web page.")

# html 태그 전달하기
response = HttpResponse()
>>> response.write("<p>Here's the text of the Web page.</p>")

 

3. HttpRedirect

HttpResponseRedirect(url)
  • 별다른 response를 하지는 않고, 지정된 url페이지로 redicrect를 함
  • 첫번 째 인자로 url를 반드시 지정해야 하며, 경로는 절대경로 혹은 상대경로를 이용할 수 있음

 

4. Render

render(request(필수), template_name(필수), 
      context=None, content_type=None, 
      status=None, using=None)
  • render는 httpRespose 객체를 반환하는 함수로 template을 context와 엮어 httpResponse로 쉽게 반환해 주는 함수임
  • template_name에는 불러오고 싶은 템플릿명을 적음
  • context에는 View에서 사용하던 변수(dictionary 자료형)를 html 템플릿에서 전달하는 역할을 함. key 값이 템플릿에서 사용할 변수이름, value값이 파이썬 변수가 됨
# views.py

from django.shortcuts import render

def my_view(request):
    name = "joey"
    return render(request, 'app/index.html', {
        'name': name,
    }

 

5. JsonResonse

JsonResponse(data, encoder=DjangoJSONEncoder,
             safe=True, json_dumps_params=None, 
             **kwargs)
  • HttpResponse의 subclass로, JSON-encoded response를 생성할수 있게 해 줌. 대부분의 기능은 superclass에서 상속받음
  • 첫번째 인자로는 전달할 데이터로서 반드시 dictionary 객체여야 함.
  • 디폴트 Content-type 헤더는 application/json임
  • encoder는 데이터를 serialize할 때 이용됨.
  • json_dumps_params는 json.dumps()에 전달할 딕셔너리의 keyword arguments임
# Serializing non-dictionary objects
response = JsonResponse([1, 2, 3], safe=False)

JsonResponse는 response를 커스터마이징 하여 전달하고 싶을때, http status code에 더하여 메세지를 입력해서 전달할 수 있다.

이 메세지는 프론트엔드 개발자와 협의하여 약속된 메시지를 던진다. 만약 딱히 전달할 메시지가 없고, status code만 전달한다면 HttpResponse를 사용하면 된다.

 


1. Get 요청할때 parameter 전달법

params = {'param1': 'value1', 'param2': 'value'} 
res = requests.get(URL, params=params)

 

2. POST 요청

import requests, json data = {'outer': {'inner': 'value'}} 
res = requests.post(URL, data=json.dumps(data))

 

3. 헤더 추가

headers = {'Content-Type': 'application/json; charset=utf-8'} 
cookies = {'session_id': 'sorryidontcare'} 
res = requests.get(URL, headers=headers, cookies=cookies)

참고 : 

https://velog.io/@swhybein/django-queryurl-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0

파라미터 전송 : https://nalara12200.tistory.com/172

https://eunjin3786.tistory.com/274

https://fedingo.com/how-to-capture-url-parameters-in-django-request/

https://engineer-mole.tistory.com/125

https://eunjin3786.tistory.com/133

https://dgkim5360.tistory.com/entry/python-requests

'개발합시다. > BackEnd 공부' 카테고리의 다른 글

Django의 Session관리 (Redis 활용)  (0) 2021.12.15
Spring Boot 실습기록 2  (0) 2021.12.08
Redis 추가공부 사항  (0) 2021.12.08
Spring Boot 실습 기록 1  (0) 2021.12.07
Redis의 모든 것  (0) 2021.12.07

캐시로써의 레디스

1. Look Aside (= Lazy Loading)

이름에서 알 수 있듯이 이 구조는 캐시를 옆에 두고 필요할 때만 데이터를 캐시에 로드하는 캐싱 전략입니다. 캐시는 데이터베이스와 어플리케이션 사이에 위치하여 단순 key-value 형태를 저장합니다. 어플리케이션에서 데이터를 가져올 때 레디스에 항상 먼저 요청하고, 데이터가 캐시에 있을 때에는 레디스에서 데이터를 반환합니다. 데이터가 캐시에 없을 경우 어플리케이션에서 데이터베이스에 데이터를 요청하고, 어플리케이션은 이 데이터를 다시 레디스에 저장합니다. 아래 그림은 이 프로세스를 나타내고 있습니다.

위 구조를 사용하면 실제로 사용되는 데이터만 캐시할 수 있고, 레디스의 장애가 어플리케이션에 치명적인 영향을 주지 않는다는 장점을 가지고 있습니다.하지만 캐시에 없는 데이터를 쿼리할 때 더 오랜 시간이 걸린다는 단점과 함께, 캐시가 최신 데이터를 가지고 있다는 것을 보장하지 못하는 단점이 있습니다. 캐시에 해당 key 값이 존재하지 않을 때만 캐시에 대한 업데이트가 일어나기 때문에 데이터베이스에서 데이터가 변경될 때에는 해당 값을 캐시가 알지 못하기 때문입니다.

 

2. Write-Through

Write-Through 구조는 데이터베이스에 데이터를 작성할 때마다 캐시에 데이터를 추가하거나 업데이트합니다. 이로 인해 캐시의 데이터는 항상 최신 상태로 유지할 수 있지만, 데이터 입력 시 두번의 과정을 거쳐야 하기 때문에 지연 시간이 증가한다는 단점이 존재합니다. 또한 사용되지 않을 수도 있는 데이터도 일단 캐시에 저장하기 떄문에 리소스 낭비가 발생합니다. 이를 해결하기 위해 데이터 입력시 TTL을 꼭 사용하여 사용되지 않는 데이터를 삭제하는 것을 권장합니다.

Redis의 활용사례

1. 좋아요 처리하기

가장 중요한 것은 한 사용자가 하나의 댓글에 한번만 좋아요 를 할 수 있도록 제한하는 것입니다. RDBMS에서는 유니크 조건을 생성해서 처리할 수 있습니다. 하지만 만약 많은 입력이 발생하는 환경에서 RDBMS을 이용한다면 insert와 update에 의한 성능 저하가 필연적으로 발생하게 됩니다

 

레디스의 set을 이용하면 이 기능을 간단하게 구현할 수 있으며, 빠른 시간 안에 처리할 수 있습니다. set은 순서가 없고, 중복을 허용하지 않는 집합입니다. 댓글의 번호를 사용해서 key를 생성하고, 해당 댓글에 좋아요를 누른 사용자의 ID를 아이템으로 추가하면 동일한 ID값을 저장할 수 없으므로 한 명의 사용자는 하나의 댓글에 한번만 좋아요를 누를 수 있게 됩니다.

 

2. 게임 서비스 일일 순방문자수 구하기

순 방문자수(UV)는 서비스에 사용자가 하루에 여러번 방문했다 하더라도 한번만 카운팅되는 값입니다. 즉 중복 방문을 제거한 방문자의 지표 라고 생각할 수 있습니다. 많은 서비스에서 이 수치를 이용해 사용자의 동향을 파악하고, 마케팅을 위한 자료로 활용하기도 합니다.

 

실제 서비스에서는 이를 구하기 위해서 대표적으로 세 가지 방법을 사용합니다. 첫번째로 액세스 로그(access log)를 분석하는 방법, 두번째로 외부 서비스(ex. Google Analytics)의 도움을 받는 방법, 세번째로는 접속 정보를 로그파일로 작성하여 배치 프로그램으로 돌리는 방법입니다. 이 세 가지 방법 중 GA를 제외하고는 정보를 실시간으로 조회할 수 없습니다.

 

그렇다면 이제 레디스의 비트 연산을 활용하여 간단하게 실시간 순 방문자를 저장하고 조회하는 방법을 알아보겠습니다. 게임의 유저는 천만명이라 가정하고, 일일 방문자 횟수를 집계하며 이 값은 0시를 기준으로 초기화됩니다.

 

사용자 ID는 0부터 순차적으로 증가된다고 가정하고, string의 각 bit를 하나의 사용자로 생각할 수 있습니다. 사용자가 서비스에 방문할 때 사용자 ID에 해당하는 bit를 1로 설정합니다. 1개의 bit가 1명을 의미하므로, 천만명의 유저는 천만개의 bit로 표현할 수 있고, 이는 곧 1.2MB정도의 크기입니다. 레디스 string의 최대 길이는 512MB이므로 천만명의 사용자를 나타내는건 충분합니다.

 

3. 최근 검색 목록 표시하기

이 기능을 관계형 데이터베이스를 이용해 구현하려면 아래와 비슷한 쿼리문이 필요합니다.

select * from KEYWORD where ID = 123 order by reg_date desc limit 5;

이 쿼리는 사용자가 최근에 검색했던 테이블에서 최근 5개의 데이터를 조회합니다. 하지만 이렇게 RDBMS의 테이블을 이용해서 데이터를 저장한다면 중복 제거도 해야하고, 멤버별로 저장된 데이터의 개수를 확인하고, 오래된 검색어는 삭제하는 작업까지 이루어져야 합니다.

 

따라서 애초에 중복을 허용하지 않고, 정렬되어 저장되는 레디스의 sorted set을 사용하면 간단하게 구현할 수 있습니다. sorted set은 가중치를 기준으로 오름차순으로 정렬되기 때문에, 가중치로 시간을 사용한다면 이 값이 가장 큰, 나중에 입력된 아이템이 맨 마지막 인덱스에 저장됩니다.

 

Redis의 Node 형태

https://meetup.toast.com/posts/227 참조!!

 


참고 자료 : 

https://meetup.toast.com/posts/225

https://meetup.toast.com/posts/226

https://meetup.toast.com/posts/227

'개발합시다. > BackEnd 공부' 카테고리의 다른 글

Spring Boot 실습기록 2  (0) 2021.12.08
Django의 request & Http  (0) 2021.12.08
Spring Boot 실습 기록 1  (0) 2021.12.07
Redis의 모든 것  (0) 2021.12.07
mongoDB의 모든 것  (0) 2021.12.07

+ Recent posts