기억의 실마리
2024. 10. 5. 16:31

🎛️OverRoading

오버로딩이란 특정 클래스나 메서드를 작성할 때 내부에서

추가적으로 이름은 동일하게 작성하고 각기 다른 파라미터를

전달받아 파라미터에 따라서 각기 다른 역할을 수행할 수

있도록 구성하는 방식을 오버로딩(OverRoading)이라고 한다.

(오버라이딩과 오버로딩 착각에 유의하자...)

 

  • 예제코드
public class MemberConstruct {
    String name;
    int age;
    int grade;
    
    MemberConstruct(String name, int age) {
        this(name, age, 45);
        System.out.println("case 1 : grade를 넘겨받지 않은 생성자 메서드 호출");
        System.out.println("name : " + this.name + "\n");
    }

    MemberConstruct(String name, int age, int grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
        System.out.println("case 2 : grade를 전달받은 생성자 메서드 호출");
        System.out.println("name : " + this.name + "\n");
    }
}

위 예제는 클래스를 생성할 때 전달받는 파라미터에 따라서 다른 결과를 만들어낸다.

 

✍️마치며...

java를 배우면서 javascript에서는 신경쓰지 않아도 될 것들을 모두 지정하거나 추가해주어야 하는 것이 번거롭지만 오히려 좀 더 본질적인 프로그래밍에 가깝다는 느낌 또한 받게되는 것 같다.

 

이후 이어서 dart와 flutter도 공부를 천천히 시작하고 있는데 dart언어 또한 java와 뭔가 비슷하다는 느낌을 지울 수가 없었다. 아직도 모르는게 너무 많고 배울 것이 정말 많다... (오히려 좋아)

2024. 10. 3. 18:05

🐎OverRiding

오버라이딩이란 부모로부터 상속받은 메서드의 기능을

재정의하는 것처럼 보이는 현상을 의미한다

 

특정 클래스에 extends를 활용하여 부모 클래스를 상속한 경우

해당 클래스는 상속한 부모의 메서드를 사용 가능하게 되는데

 

이 때 상속받은 메서드의 기능이 아닌 다른 기능을 필요할 때

overriding을 활용하여 상속받은 메서드를 같은 이름으로

원하는 새로운 기능의 메서드를 생성하여 마치 같은 메서드이지만

재정의한 것 처럼 활용할 수 있다.

 

overriding이 적용되는 방식은 절차적인 접근순서에 의해 적용된다.

 

  • 상속 메서드 실행 예시코드
// Parent.java
public class Parent {

	public void walk() {
    		System.out.println("let's walk!");
    	}
}

// IHaveParent.java
public class IHaveParent extends Parent {

	public void wait() {
    		System.out.println("now wait for parent");
    	}
    
    	public void stay() {
    		System.out.println("and i stay here.");
    	}
}

// IHaveParentMain.java
// ...
IHaveParent iHaveParent = new IHaveParent();
iHaveParent.walk();

 

위 예시코드를 보면 Parent 클래스는 walk라는 메드를 가지고 있다.

 

이러한 경우 IHaveParent 인스턴스에서 walk를 호출하면

가장 먼저 IHaveParent클래스 자기 자신에서 부터 해당 메서드를

탐색하게 된다.

 

이 후 탐색할 수 없다면 상속된 부모클래스에서 walk 메서드를

탐색하게 되고 있다면 실행하게 된다.

 

  • Overriding 예시코드
// Parent.java
public class Parent {

	public void walk() {
    		System.out.println("let's walk!");
    	}
}

// IHaveParent.java
public class IHaveParent extends Parent {

		// 자식 요소에도 있는 walk 메서드
        public void walk() {
            	System.out.println("let's walk, alone.");
            }

	public void wait() {
    		System.out.println("now wait for parent");
    	}
    
    	public void stay() {
    		System.out.println("and i stay here.");
    	}
        

}

// IHaveParentMain.java
// ...
IHaveParent iHaveParent = new IHaveParent();
iHaveParent.walk();

 

위는 overriding이 적용된 예시 코드이다.

 

마찬가지로 특정 메서드를 찾기 위해 메서드를 탐색하고 이 때

"자기 자신이 가진 메서드를 먼저 탐색한다"는 절차적 순서로

호출된 메서드명을 가진 메서드가 부모 뿐만 아니라

자식 클래스에서도 가지고 있다면

 

자식클래스(자기 자신)에서 우선 탐색 후 실행되어 부모가 가진

메서드가 마치 재정의 되어 실행되는 것처럼 보이는 현상을

overriding이라고 한다.

 

서술한대로 탐색 순서에 의해 발생되는 현상이지만

JAVA 개발자들은 이를 의도하여 활용하고자 

Override 라는 어노테이션이 만들었다.

 

부모가 상속된 클래스 내부에서 부모가 가진

메서드명으로 다른 기능의 메서드를 만들고

상단에 Override어노테이션을 사용한다면

해당 메서드명이 부모에 존재한다면 정상 실행되지만

 

해당 메서드명이 존재하지 않는다면 에러를 발생시켜

상속관계를 가진 클래스에서 분명한 의도를 가진

프로그래밍 설계가 가능하도록 활용할 수 있다.

 

  • @Override를 활용한 예시
// Parent.java
public class Parent {

	public void walk() {
    		System.out.println("let's walk!");
    	}
}

// IHaveParent.java
public class IHaveParent extends Parent {

		// Override 어노테이션을 활용
        @Override
        public void walk() {
            	System.out.println("let's walk, alone.");
            }

	public void wait() {
    		System.out.println("now wait for parent");
    	}
    
    	public void stay() {
    		System.out.println("and i stay here.");
    	}
        

}

// IHaveParentMain.java
// ...
IHaveParent iHaveParent = new IHaveParent();
iHaveParent.walk();

 

 

✍️마치며...

자바를 공부하며 새로운 프로그래밍 개념에 대해서 알아가며 새삼 프로그래밍 자체가 가진 프로세스, 로직은 얼추 비슷하게 적용되고 있음을 깨닫게 되는 것 같다. 특히나 자바와 (타입스크립트 + JS)는  무척이나 유사한 것 같다... 자바를 공부하며 기존에 다루었던 NestJS와도 많이 닮았다는 생각을 했는데 결과적으로는 타입스크립트가 자바를 이해하는데 크게 기여한 것을 느낄 수 있었다. 앞으로도 배워보고 싶은 언어, 프레임워크가 많은 만큼 더 열심히 해야겠다!

2024. 9. 18. 13:54

Data Type

자료의 "형"을 뜻하며 말 그대로 데이터의 타입이다.

필자는 Frontend가 주력인 개발자로서 Javascript를 기본으로

개발을 해왔기 때문에 Typescript 를 활용하지 않는다면

모든 객체(함수, obj, arr 등)와 변수에 별도로 타입을 지정할 일이 없다.

 

하지만 JAVA 언어를 사용하기 위해서는 모든 클래스에

타입 지정이 필수이며, 사실 Javascript를 제외한다면 대다수의

언어에서 타입지정은 필수이다.

 

JAVA에서 정의하는 타입은 Javascript에서 정의하는 타입과 크게

다르지 않고 일부 개념을 제외하면 거의 동일한 구조이다.

Javascript에서 크게 원시타입, 참조타입으로 구분한다면

JAVA에서는 일반형, 참조형으로 구분하고 있다.

 

일반형 (Primitive Type)

Javascript에서는 원시타입으로 불리는 Boolean, Number 등과 비슷한 형태로

메모리상에서 실제 값을 가지고 있는 자료형을 말하며 아래와 같은 일반형이 존재한다.

 

- 기본형 타입의 특징

  1. 소문자로 구성 되어있다.
  2. 변수에 선언과 동시에 초기화되어 null이 아닌 초기값이 존재한다.
  3. 비객체 타입(참조형이 아님)으로 모두 null을 할당받을 수 없다.
  4. 저장공간에 실제 값을 가지고 있다.
  5. 모든 타입은 메모리 Stack에 저장된다.

 

- 오해할 수 있는 문자형 타입

문자형 타입중에 String과 char가 존재한다.

진정한 기본형인 문자형 타입은 char이며,

String은 JAVA에서 문자열을 좀 더 편하게 사용할 수 있도록

제공하는 참조형 타입이다. 그렇기 때문에 타입 구성이

소문자로 되어있지 않고 대문자로 시작하는 "String"인 것이다.

 

  타입 할당 메모리 크기 기본 값 범위
논리형 boolean 1byte false true, false
문자형 char 2byte (Unicode) '\u0000' 0 ~ 65,535
정수형 int (default) 4byte 0 -2,147,483,648 ~ 2,147,483,647
byte 1byte 0 -128 ~ 127
short 2byte  0 -32,768 ~ 32,767
long 8byte 0L -9,223,372,036,854,775,808
~ 9,223,372,036,854,775,807
실수형 float 4byte 0.0F (3.4 X 10-38) ~ (3.4 X 1038) 의 근사값
double (default) 8byte 0.0 (1.7 X 10-308) ~ (1.7 X 10308) 의 근사값

 

  • 일반형 예제 소스코드
class PrimitiveType() {
	int number;
    boolean isBoolean;
    double isDouble;
}

// number = 0
// isBoolean = false
// isDouble = 0.0

 

 

참조형 (Reference Type)

참조형 타입은 클래스, 배열, 열거형 타입 등을 참조형이라 하며,

변수에 할당되어 메모리상에 존재할 때 null 또는 객체가 가지는

참조가능한 주소를 할당할 수 있다.

 

- 참조형 타입의 특징

  1. 기본형과 다르게 실제 값이 아닌 데이터가 저장 되어있는 주소를 가진다.
  2. 참조형 내부의 값은 메모리 내부 힙(heap)에 저장하고 참조형 데이터가 가진 주소를 통해 접근가능하다.
  3. 참조형 변수는 null로 초기화 가능하고 이는 GC(Garbage Collection) 대상으로 이어질 수 있다.

 

타입 기본 값 할당 메모리 크기
배열 (Array) null 4byte
(주소만을 담기 때문)
열거 (Enumeration) null
클래스 (Class) null
인터페이스 (Interface) null

 

 

  • 참조형 예제 소스코드
class ReferenceType() {
	String title = "Title";
    String[] contents = new String[] { "배열임", "이것은 참조형" }
    Date date = new Date();
    Data data = new Data();
}

// title = "Title"
// contents = main.ref@682a0b20 (패키지경로@참조주소)
// date = main.ref@682a0b21 (패키지경로@참조주소)
// data = main.ref@682a0b22 (패키지경로@참조주소)

class Data() {
	int value;
    char content
}

 

 

마치며...

Javascript를 주력으로 개발을 하다보니 평소 자료형(Data Type)에 대해서 깊게 생각하지 못했던 것 같다. 타 언어에도 관심을 가지고 배우며 점점 개발자로서의 시야도 넓어지는 것 같다. 이번에 JAVA를 공부하면서 Javascript가 얼마나 편한 언어인지 깨닫는 계기가 되었다... JS 최고.

2023. 4. 7. 17:28

OpenAPI

openAPI는 애플리케이션의 REST-API 문서를 자동으로 구성해주는 라이브러리이다.

이 라이브러리를 사용하게된다면 개발을 진행할때 backend에서 API를 구성할때

지정한 타입이나 데이터 변수를 따로 문서화하여 frontend개발자에게 전달할 필요가

없어지기 때문에 능률을 올려주고 backend, frontend개발자 모두에게 편의를

제공할 수 있는 라이브러리이다.

 

(* React, NestJS로 구성된 프로젝트를 기준으로 포스팅하였다.)

 

Swagger? or OpenAPI?

Swagger는 2010년대 초 Tam Wordnik가 개발해온 라이브러리이다.
시작은 Wordnik기업에서 자체 API용 UI로 개발되었고 2015년초에SmartBear라는 회사에서 Swagger를 인수했다. 이 후에 2015년 말SmartBear는 Linux Foundation의 후원으로 OpenAPI Initiative에 Swagger를기부하면서 OpenAPI Specification으로 이름이 변경되었다.

지금의 Swagger는 OpenAPI를 Implement하기 위한 도구를 뜻하며

명세된API를 공유하는 swagger-ui이며 web에서 공유하는 ui-tool이다.

 

결과적으로 라이브러리 자체는 openAPI라고 명칭하며 swagger는 tool을 명칭한다.

 

 

swagger & openAPI Install

// express를 사용하는 경우
npm install --save nest-openapi-tools @nestjs/swagger swagger-ui-express

// fastify를 사용하는 경우
npm install --save nest-openapi-tools @nestjs/swagger swagger-ui-fastify

단순 명세기능을 사용할땐 swagger설치만 해도 명세가 가능하지만 필자는

codegen에 대해서도 다루기 때문에 nest-openapi-tools 를 함께 install 해주어

편의성을 더했다.

 

 

사용예시

 

1. DTO와 Controller 명세하기

// DTO or Entity
export class Dto {
  @ApiProperty({ type: Number }) // default타입(string)이 아니므로 타입지정
  id: number;

  @ApiProperty() // default타입 = string
  email: string;

  @ApiProperty({ required: false })
  password?: string;  // chaning = 필수요소x

  @ApiProperty()
  name: string;

  @ApiProperty()
  mobile: string;
}

// Controller
export class Controller {
  @ApiResponse({ type: CoreOutput }) // return 타입을 지정
  @Post('register')
  createAccount(@Body() input: CreateAccountDto): Promise<CoreOutput> {
    return this.userService.createAccount(input);
  }

  /** 유저데이터 수정 */
  @ApiResponse({ type: CoreOutput }) // return 타입을 지정
  @Patch('modify')
  @UseGuards(JwtAuthGuard)
  profileUpdate(
    @Req() req,
    @Body() updateData: UpdateAccountDto,
  ): Promise<CoreOutput> {
    return this.userService.profileUpdate(req.user, updateData);
  }
}

단순하게 데코레이터를 넣어줌으로서 명세가 가능하다.

 

 

2.  Front에 API명세파일 보내기

main.ts에 적용해도 되지만 main.ts에서 설정해야하는 것들이 많아지고

복잡해질 수 있기 때문에 openApi.ts를 만들어서 main.ts에 import시켜서 사용했다.

 

  • openApi.ts
import { OpenApiNestFactory } from 'nest-openapi-tools';
import { DocumentBuilder } from '@nestjs/swagger';

export const useOpenApi = (app) => {
  return OpenApiNestFactory.configure(
    app,
    new DocumentBuilder()
      .setTitle('My API')
      .setDescription('An API to do awesome things')
      .addBearerAuth(),
    {
      // swagger-ui 설정 (web-ui)
      webServerOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        path: 'api-docs',
      },

      // 자동생성된 api문서 경로 및 설정
      fileGeneratorOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        outputFilePath: './openapi.yaml', // or ./openapi.json
      },

      // 서버에 저장된 api문서를 프론트로 보내주는 설정
      clientGeneratorOptions: {
        enabled: process.env.NODE_ENV !== 'production',
        type: 'typescript-axios', //typescript-axios
        outputFolderPath: '../frontend/src/openapi',
        additionalProperties:
          'apiPackage=apis,modelPackage=models,withoutPrefixEnums=true,withSeparateModelsAndApi=true',
        openApiFilePath: './openapi.yaml', // or ./openapi.json
        skipValidation: true, // optional, false by default
      },
    },
    {
      // 명세함수의 기본값을 설정해준다.
      operationIdFactory: (c: string, method: string) => method,
    },
  );
};

 

  • main.ts
function bootstrap() {
  (async () => {
    try {
      const app = await NestFactory.create(AppModule);

      await useOpenApi(app);
      
      // ...
}
bootstrap();

 

사용팁

서버를 돌리고 있을때 위의 설정들이 제대로 갖춰질 경우 해당 경로에

openapi.yaml 의 API명세파일이 생길 것이고 서버가 수정될 때마다

openapi.yaml를 최신화 시킨다. 그렇기 때문에 계속해서 최신화때문에

서버로딩이 길어지게 되니 개발중엔 enabled 설정을 'production'일때로 바꿔주고

최신화시켜주고 싶을때 다시 설정을 'production'가 아닐때로 바꿔주면 된다.

 

 

3.  Front에서 서버의 API명세파일 가져오기

Front에서는 package.json에서 script설정을 통해 npm 명령어를 사용하여

명세파일을 가져오거나 최신화 시켜 줄 수 있다.

이를 위해서는 Front에 openapi-generator-cli 를 install해야한다.

 

  • openapi-generator-cli Install
// 패키지가 많은경우 충돌방지목적으로 -D 옵션을 사용하면 좋다.
npm install @openapitools/openapi-generator-cli -D

 

  • package.json
{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "codegen": "npx @openapitools/openapi-generator-cli generate -i ../backend/openapi.yaml -g typescript-axios --additional-properties apiPackage=apis,modelPackage=models,withoutPrefixEnums=true,withSeparateModelsAndApi=true -o ./src/openapi"
  },
}

 

codegen script의 설명

  • "npx @openapitools/openapi-generator-cli"는 OpenAPI Generator CLI를 실행하기 위한 명령어
  • generate는 생성 명령을 나타내는 옵션
  • -i ../backend/openapi.yaml는 가져오는 API명세파일의 위치와 이름
  • -g typescript-axios는 TypeScript Axios Front에서 통신할 함수를 생성하기 위한 Generator 모듈을 지정
  • --additional-properties apiPackage=apis,
    modelPackage=models,
    withoutPrefixEnums=true,
    withSeparateModelsAndApi=true
    코드 생성에 사용되는 추가 속성 설정이다. 이 예제에서는 생성된 코드의 패키지 이름을 지정하고, Enum의 접두사를 제거한 후, 모델 및 API 코드를 분리하는 옵션이다.
  • -o ./src/openapi"는 생성된 파일의 위치를 지정하고 이 예제에서는 src/openapi 폴더에 생성된 파일을 저장한다.

 

명령어로 가져오기

// 터미널에 입력
npm run codegen

 Thanks for using OpenAPI Generator. 

문구가 뜨면 코드젠이 완료된 상태이다.

 

마치며...

작성일자에 진행중인 개인프로젝트에서 openAPI-codegen을 처음 사용해보았는데 typeORM을 사용했을때 만큼의 신선한 충격이었다. codegen은 효율을 혁신적으로 올려주는 라이브러리임을 확실하게 깨달았다. 실무에 들어가게 됐을때 openAPI-codegen과 swagger를 사용하지 않는 배경이라면 API명세문서의 간편화를 주장하며 이를 채용하자고 적극적으로 행동해야겠다는 생각이 들었다.

'Backend > Nest.js' 카테고리의 다른 글

[ Nest.js ] Node.js기반의 API 프레임워크  (0) 2023.01.24
2023. 2. 22. 01:20

JWT

JWT는  .  을 기준으로 좌측부터 Header.Payload.Signature의 의미를 가진

세가지 문자열의 조합이다.

 

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjE1LCJpYXQiOjE2NzQ0NjQwNzIsImV4cCI6MTY3NTY3MzY3Mn0.trE7PemoIJZ1y7u3F6hrURW3iHUCN1KBcPTsdVAueEw

 

Header에는 type과 hash알고리즘의 종류가 내포되어있고, Payload 서버에서 응답한 response(권한 정보와 데이터 등) 내포되어있다. Signature에는 HeaderPayload Base64 URL-safe Encode를 한 이후 Header에 명시된 해시함수를 적용하고, Private Key로 만들어진 전자서명이 내포되어 있다.

 

JWT에 대한 견해

나는 인증이 필요한 프로젝트를 만들었을때 JWT를 선택했다. 인증방식에 대한 많은 방법들이 존재하지만 주관적인 생각으로는 JWT의 데이터 변조에 대한 안정성은 타 인증방식에 비하면 검증된 방법이 생각했기 때문이었다. 토큰을 대조하여 인증하기 때문에 해당 데이터 위변조를 방지할 수 있다는 점 이 가장 컸고 또 다른 이점으로 해당 서비스가 아닌 다른 로그인 시스템에 접근, 권한공유가 가능한 구조이기 때문에 쿠키와는 차별화된 인증방식으로서 큰 이점을 가질 수 있다고 생각했기 때문이다.

 

Header

토큰의 유형과 서명 암호화 알고리즘을 내포하고 있다.

{
  "alg": "HS256",  // 서명화 알고리즘(HMAC SHA256, RSA 등)
  "typ": "JWT"  // 토큰의 유형
}

 

 

Payload

서버와 클라이언트가 실질적으로 주고받는 데이터를 담고 있는 섹션이다.

지정된 데이터 타입은 없지만 보편적으로 사용하는 데이터 타입으로

Registered claims, Public claims, Private claims 이 있다.

{
  /* 위 예시와 동일코드 */
  "sub": 15,            // Registed Claim
  "iat": 1674464072,    // Registed Claim
  "exp": 1675673672,    // Registed Claim
  
  /* 예시코드 */
  "jti": 4200,                          // Registed Claim
  "https://zeriong.tistory.com": true,  // Public Claim
  "useId": "zeriong@zeriong.com"        // Private claim
}

1. Registed claims : predefined claims

  • iss : issuer (발행자)
  • exp : expiration time (만료시간)
  • sub : subject (제목)
  • iat : issued At (발행시간)
  • jti : JWT ID

 

2. Public claims

  • 사용자가 정의 가능한 클레임의 공개용 data를 전달할때 사용한다.

 

3. Private claims

  • 인증이 완료된 당사자들 간에 정보를 공유하기 위해 만들어진 사용자 지정 클레임이다.
    공개의 여부는 무관하지만 해당 유저를 특정할 수 있는 정보들을 내포한다.

 

 

Signature

시그니처는 헤더에서 정의한 알고리즘 방식인 alg를 활용한다.

(Header + Payload) + (Primary-Key in Server) 이렇게 둘을 합친 후

헤더에서 정의했던 알고리즘으로 암호화한다.

 

Signature = Base64Url( Header ) + . + Base64Url( Payload ) + server's Key\

 

 

JWT.IO에 접속해서 인코딩과 디코딩을 할 수 있다.

https://jwt.io/

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

 

 

 

Access-Token, Refresh-Token을 통한 인증과정

  1. 클라이언트에서 ID,PW를 입력한다.

  2. 클라이언트에서 보낸 ID,PW가 DB에 정보와 매치되면 DBrefresh-token을 저장하고
    헤더를 통해서 refresh-token과 동일한 값을 가진 access-token을 보내준다.

  3. 클라이언트에서 api를 요청할때 access-token을 같이 헤더에 실어 보내준다.

  4. 요청받은 access-token을 대조한 후 문제가 없을 경우에만 응답한다.

  5. 클라이언트에서 api를 요청 했는데 access-token이 만료된 경우에는
    DBrefresh-token를 이용해서 access-token을 재발급 해준다.

 

 

JWT는 보안?

결론부터 말하자면 JWT의 목적은 보안이 아닌 데이터 위조방지이다.

Base64를 통해서 암호화 하기 때문에 디버거를 사용하면 바로 복호화 할 수 있다.

그러므로 페이로드에 담기는 데이터는 모두 쉽게 노출될 수 있기 때문에 비밀번호나

민감한 정보는 담지 말아야 한다. 그리고 가장 처음 "암호화된 문자열" 이 아닌

"문자열의 조합" 이라 서술한 것도 이러한 특성 때문이었다.

 

 

JWT 장점

  1. Header와 Payload로 Signature를 생성하기 때문에 데이터 위변조를 방지할 수 있다.
  2. 인증 정보 저장에 대한 자유도가 높은 편에 속한다.
  3. data와 token이 검증 됨을 증명하는 서명과 필요한 모든 data를 자체적으로 지니고 있다.
  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태(StateLess)가 되어 서버 확장성이 우수해질 수 있다.
  5. 쿠키와 다른 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다.
  6. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.
  7. 모바일 어플리케이션 환경에서도 잘 동작한다. (모바일은 세션 사용 불가능)

 

JWT단점

  1. Self-contained의 형식으로 토큰 자체에 정보를 담고 있어, 양날의 검이 될 수 있다.
  2. 토큰의 Payload에 3종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어남에 따라 네트워크에 부하를 줄 수 있다.
  3. payload 자체는 암호화 된 것이 아니라 BASE64로 인코딩 된 것이기 때문에 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로 payload에 중요 데이터를 넣지 말자.
  4. access-token하나만 가지고 운영을 할 경우 stateless 특징을 가진다. 즉 클라이언트 측에서 단독관리 를 하기 때문에 토큰 자체를 탈취당하면 대처하기가 어렵게 된다. 해결법으로 DB에서 refresh-token을 저장하여 access-token을 재발급 해주며 이중으로 보안을 강화할 수 있다.

 

 

마치며...

프론트개발자를 지향하며 공부하는 나로서 백엔드스택의 경험은 꽤나 복잡하고 어려운 구조였던 것 같다. 그래도 하나하나 풀어가며 이해하려 공을 들이다보니 어느새 많은 구조를 이해하게 된 것 같아 뿌듯 했다. 앞으로 어떤 인증방식이 유행을하고 더 보안이 강화가 된 인증방식이 나올지는 모르겠지만 아마 JWT방식은 앞으로도 오랜 사랑을 받을 것 같다.

2023. 2. 18. 17:22

TypeORM

TypeORM에서 ORM은 Object-Relational Mapping(객체- 관계 맵핑)의 약자이다.

객체와 관계형 데이터베이스의 데이터(이하 SQL)를 자동으로 맵핑해준다.

객체지향 프로그래밍은 클래스를 사용한 객체모델이며

SQL은 테이블을 사용한 모델이다. 서로 다른 형식의 모델이지만

ORM을 통해서 객체 간의 관계를 기반으로 SQL을 자동 생성한다.

 

 

 

TypeORM에 대한 견해

fastify와 express를 SQL를 맵핑할때 자주 사용되는 ORM으로 Sequelize, Knex, TypeORM 등이 있다.

요즘 Typescript가 인기를 얻어 급부상하면서 타입을 지정하여 모델을 정의하는

장점을 최대한 끌어낼 수 있기 때문에  TypeORM의 선택이 적합하다는 생각이 들었다.

특히나 서버와 데이터는 보안이 아주 중요하기 때문에 클라이언트로부터의 모든 요청이

거짓말일 수도 있다는 가정으로부터 데이터를 걸러서 받아야한다. 때문에 Typescript와 TypeORM의

엄격하고 규격화된 타입만을 요청할 수 있도록 만들어 메리트를 극대화시킬 수 있을 것이라 생각했다.

 

 

 

TypeORM Install

1. npm install  &  start

//기본설치
$ npm install typeorm --save

//nest.js에서 사용시
$ npm install --save @nestjs/typeorm typeorm mysql2

//프로젝트 시작
$ npm start

 

 

 

사용예시

※ 모두 공식문서의 예제 코드이다.

 

Active Record 패턴

모델 자체에 쿼리메소드를 정의하여 메소드를 사용하여 객체를 저장, 제거, 불러오는 패턴(방식)이다.

BaseEntity클래스를 사용하여 Use클래스에 상속한 후 사용할 수 있으며 이를 통하여

BaseEntity가 가진 메소드를 만들 수 있고 static을 통해서도 추가적인 커스텀메소드를 만들어 사용할 수 있다.

import { BaseEntity, Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean

    static findByName(firstName: string, lastName: string) {
        return this.createQueryBuilder("user")
            .where("user.firstName = :firstName", { firstName })
            .andWhere("user.lastName = :lastName", { lastName })
            .getMany()
    }
}

 

생성자함수 new를 사용하면 새로운 인스턴스를 만들어 DB스키마를 직접 다룰 수 있다.

const userRepository = dataSource.getRepository(User)

const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await userRepository.save(user)

await userRepository.remove(user)

const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: "Timber",
    lastName: "Saw",
})

 

 

Data Mapper 패턴

분리된 클래스에 쿼리 메소드를 정의한다. Repository를 이용하여 객체를 저장하거나 제거, 불러올 수 있다.

모델에 접근하는 Active Record 패턴과 다르게 Repository를 통해서 데이터에 접근이 가능하다.

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    firstName: string

    @Column()
    lastName: string

    @Column()
    isActive: boolean
}

 

getRepository()를 사용해서 접근할 수 있다.

const userRepository = dataSource.getRepository(User)

const user = new User()
user.firstName = "Timber"
user.lastName = "Saw"
user.isActive = true
await userRepository.save(user)

await userRepository.remove(user)

const users = await userRepository.find({ skip: 2, take: 5 })
const newUsers = await userRepository.findBy({ isActive: true })
const timber = await userRepository.findOneBy({
    firstName: "Timber",
    lastName: "Saw",
})

 

 

Active Record  VS  Data Mapper

소프트웨어 개발과 관련하여 항상 염두에 해야 할 것은 애플리케이션을

어떻게 유지 관리할 것인지 생각하는 것이다. 이를 관점으로 보았을때

 

Data Mapper: 대규모 애플리케이션에서 더 효과적인 유지보수성을 지원한다.

 

Active Record: 소규모 앱에서 단순성을 유지하는 데 도움이 된다.

소규모앱의 경우 단순성이 유지됨으로서 유지보수성이 높아진다.

 

 

 

마치며...

전에 로그인기능 구현을 위해서 Nest.js서버와 MySQL을 다룬적이 있다. 이때 typeORM을 통해서 스키마를 만들고 클라이언트로부터 요청받은 정상적인 데이터를 저장하며 사용한 경험이 있다. 실제로 엄청나게 편리함을 느꼈고 앞으로도 객체지향형 서버를 다루게 되면 typeORM을 채택할 의향이 있다. 그리고 사용하게 된다면 반드시 Data Mapper방식으로 접근할지, Active Record방식으로 접근할지도 잘 선택해서 사용해야할 것 같다.

 

출처: https://typeorm.io/

2023. 2. 2. 00:12

이 게시글에 모든 예시는 아래 링크를 통해서 사용해 볼 수 있다.스키마 이름이나 칼럼이름 역시 같아 결과값이 그대로 적용되어 보여진다.

https://www.w3schools.com/mysql/trymysql.asp?filename=trysql_select_all

 

MySQL Tryit Editor v1.0

WebSQL stores a Database locally, on the user's computer. Each user gets their own Database object. WebSQL is supported in Chrome, Safari, and Opera. If you use another browser you will still be able to use our Try SQL Editor, but a different version, usin

www.w3schools.com

 

비상관 서브쿼리

연계되지않고 독립적으로 반환되는 서브쿼리를 뜻한다.

 

예제

SELECT
  CategoryID, CategoryName, Description,
  (SELECT ProductName FROM Products WHERE ProductID = 1)
FROM Categories;
-- 위의 Category ID, NAME, DESCRIPTION 모두 Categories 테이블에
-- 속하는 데이터이지만 괄호안에 서브쿼리는 Products 테이블에서 가져온 데이터이다.


SELECT * FROM Products
WHERE Price < (
  SELECT AVG(Price) FROM Products
);
-- Price 가 Products테이블에서 평균 Price보다 낮은 경우만 보여준다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID =
  (SELECT CategoryID FROM Products
  WHERE ProductName = 'Chais');
-- Products 테이블에서 CategoryID기준으로 ProductName = 'Chais'인 데이터를
-- 가져와서 Categories테이블에서 CategoryID와 같은 CategoryID, CategoryName, Description의 데이터를 보여준다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID IN
  (SELECT CategoryID FROM Products
  WHERE Price > 50);
-- 위와 마찬가지로 역순으로 괄호부터 해석해나가면 좀 더 쉽게 접근이 가능하다.

 

ALL, ANY

  • ~ ALL(서브쿼리) = 서브쿼리의 모든 결과에 대해 ~하다
  • ~ ANY(서브쿼리) = 서브쿼리의 하나 이상의 결과에 대해 ~하다

 

연산자 + ANY(SUBQUERY)

> ANY >= ANY < ANY <= ANY = ANY != ANY
최소값보다 크면 최소값보다 크거나 같으면 최대값보다 작으면 최대값보다 작거나 같으면 IN과 같은 기능 NOT IN과 같은
기능

 

연산자 + ALL(SUBQUERY)

> ALL >= ALL < ALL <= ALL = ALL != ALL
최소값보다 크면 최소값보다 크거나 같으면 최대값보다 작으면 최대값보다 작거나 같으면 서브 쿼리의
결과가 1건이면
괜찮지만 여러 건이면 오류가 발생
서브 쿼리의
결과가 1건이면
괜찮지만 여러 건이면 오류가 발생

 

예제

SELECT * FROM Products
WHERE Price > ALL (
  SELECT Price FROM Products
  WHERE CategoryID = 2
);
-- 서브쿼리 = CategoryID가 2인 Price들을 모두 가져온다. 그리고
-- 그 모든 데이터 들중 어떤 것보다(ALL의 의미이며 현재는 수의 비교이기 때문에
-- 데이터들 중에서 가장 큰 값이 기준이 된다.) Price가 큰 것을 가져온다.


SELECT
  CategoryID, CategoryName, Description
FROM Categories
WHERE
  CategoryID = ANY
  (SELECT CategoryID FROM Products
  WHERE Price > 50);
-- 위와 같은 조건인 경우 in을 쓰는게 더 적합하다.
-- ANY 의 경우는 IN으로 사용하면 같은 결과를 반환한다.

 

 

상관 서브쿼리

반환되는 쿼리로부터 연계되는 서브쿼리를 뜻한다.

 

예제

SELECT
  ProductID, ProductName,
  (
    SELECT CategoryName FROM Categories C
    WHERE C.CategoryID = P.CategoryID
  ) AS CategoryName
FROM Products P;
-- Categories C 이것은 AS를 생략한 문구로 C로 별명을 지어준 것이다.
-- 마찬가지로 Products P를 통해서 P로 별명을 만들어준 후 P. 으로 CategoryID에
-- 접근하고 C. 을 통해 CategoryID에 접근한 것이다. AS를 생략할 경우에는 절대로
-- 문자열을 사용하면 안된다. 공백 또한 안된다. 공백을 넣고 싶다면 백틱으로 별명을
-- 지어주고 접근할때역시 백틱을 사용해줘야 한다.

 

EXISTS / NOT EXISTS 연산자

예제

SELECT
  CategoryID, CategoryName
  -- ,(SELECT MAX(P.Price) FROM Products P
  -- WHERE P.CategoryID = C.CategoryID
  -- ) AS MaxPrice
FROM Categories C
WHERE EXISTS (
  SELECT * FROM Products P
  WHERE P.CategoryID = C.CategoryID
  AND P.Price > 80
);
-- EXISTS의 서브쿼리에 맞게 존재하는 결과들만 반환한다.

 

JOIN(INNER JOIN) 내부조인

 

예제

SELECT * FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID; 
-- Categories 와 Products 두 테이블을 합쳐준다(JOIN) 어떤 기준으로?(ON)
-- 각 테이블의 CategoryID를 기준으로 합쳐준다.

SELECT C.CategoryID, C.CategoryName, P.ProductName
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID; 
-- ambiguous 주의!(같은 이름의 column이 있는경우 뜨는 에러)
-- 조인으로 Products에서 Categories테이블의 CategoryID와 Products의
-- CategoryID가 같은경우만 반환하고 그 후 P. 으로 접근하여 ProductName를 반환한다.

 

여러 테이블 JOIN

SELECT 
  C.CategoryID, C.CategoryName, 
  P.ProductName, 
  O.OrderDate,
  D.Quantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID;
-- 다른 여러테이블의 데이터가 필요할 경우 여러테이블을 JOIN하여 사용할 수 있다.

 

JOIN 테이블 GROUP하기

SELECT 
  C.CategoryName,
  MIN(O.OrderDate) AS FirstOrder,
  MAX(O.OrderDate) AS LastOrder,
  SUM(D.Quantity) AS TotalQuantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID
GROUP BY C.CategoryID;
-- C.CategoryID의 기준으로 GROUPING 해줬다. C.CategoryID는 1~8까지
-- 중복되는 숫자가 없고 종류별로 있기 때문에 종류별로 퍼스트,라스트오더와 팔린갯수를
-- 모두 볼 수 있다.


SELECT 
  C.CategoryName, P.ProductName,
  MIN(O.OrderDate) AS FirstOrder,
  MAX(O.OrderDate) AS LastOrder,
  SUM(D.Quantity) AS TotalQuantity
FROM Categories C
JOIN Products P 
  ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
  ON P.ProductID = D.ProductID
JOIN Orders O
  ON O.OrderID = D.OrderID
GROUP BY C.CategoryID, P.ProductID;
-- C.CategoryID를 첫 기준으로 오름차순으로 정렬 된 후 P.ProductID도
-- 오름차순으로 정렬한다.

 

SELF JOIN - 같은 테이블끼리 JOIN

SELECT
  E1.EmployeeID, CONCAT_WS(' ', E1.FirstName, E1.LastName) AS Employee,
  E2.EmployeeID, CONCAT_WS(' ', E2.FirstName, E2.LastName) AS NextEmployee
FROM Employees E1 JOIN Employees E2
ON E1.EmployeeID + 1 = E2.EmployeeID;

-- 1번의 전, 마지막 번호의 다음이 없다.(외부 조인으로 해결가능)
-- 위와같이 같은 테이블이지만 별명을 다르게 만들어줘서 셀프조인이 가능하다.

 

LEFT/RIGHT OUTER JOIN - 외부 조인

SELECT
  E1.EmployeeID, CONCAT_WS(' ', E1.FirstName, E1.LastName) AS Employee,
  E2.EmployeeID, CONCAT_WS(' ', E2.FirstName, E2.LastName) AS NextEmployee
FROM Employees E1
LEFT JOIN Employees E2
ON E1.EmployeeID + 1 = E2.EmployeeID
ORDER BY E1.EmployeeID;

-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것
-- 반환된 양쪽 데이터에서 오른쪽이나 왼쪽데이터가 NULL이라도 반환한다.


SELECT
  C.CustomerName, S.SupplierName,
  C.City, C.Country
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;

-- LEFT는 SupplierName을 가지지 않은 CustomerName도 반환한다. RIGHT는 반대이다.


SELECT
  IFNULL(C.CustomerName, '-- NO CUSTOMER --'),
  IFNULL(S.SupplierName, '-- NO SUPPLIER --'),
  IFNULL(C.City, S.City),
  IFNULL(C.Country, S.Country)
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;

-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것
-- IFNULL을 사용해서 NULL일 경우의 반환 될 값을 넣어 줄 수 있다.

 

CROSS JOIN - 교차조인

SELECT
  E1.LastName, E2.FirstName
FROM Employees E1
CROSS JOIN Employees E2
ORDER BY E1.EmployeeID;
-- 직원들의 각각 LastName에 이름을 추가한 데이터를 모두 조합한다. 직원은 모두 9명이 있는데
-- LastName + FirstName을 반복 조합하기 때문에 총 81개의 이름조합 데이터를 가져온다.

 

UNION - 집합으로 다루기

  • UNION = 중복을 제거한 집합
  • UNION ALL = 중복을 제거하지 않은 집합
SELECT CustomerName AS Name, City, Country, 'CUSTOMER'
FROM Customers
UNION
SELECT SupplierName AS Name, City, Country, 'SUPPLIER'
FROM Suppliers
ORDER BY Name;
-- 위에 쿼리와 아래 쿼리를 같은 별명으로 만들어 줘서 합친 후에 모두 불러오는 기능이다.

 

합집합

SELECT CategoryID AS ID FROM Categories
WHERE CategoryID > 4
UNION
SELECT EmployeeID AS ID FROM Employees
WHERE EmployeeID % 2 = 0;

-- UNION ALL로 바꿔볼 것

-- CategoryID는 1~8이고 EmployeeID는 1~9이다. UNION을 사용했기 때문에 5, 6, 7, 8
-- 그리고 EmployeeID의 2의 배수인 2, 4를 반환한다. 그리고 6, 8은 중복이기 때문에 생략됐다.

-- 만약 UNION ALL 을 사용하게 되면 합집합. 즉, 생략하지 않고 모두 반환하기 때문에
-- 값은 5, 6, 7, 8, 2, 4, 6, 8 을 반환하게 된다.

 

 

교집합

SELECT CategoryID AS ID
FROM Categories C, Employees E
WHERE 
  C.CategoryID > 4
  AND E.EmployeeID % 2 = 0
  AND C.CategoryID = E.EmployeeID;
-- FROM에서 Employees E를 선언해 준 것은 WHERE에서 써먹기 위함이고 조건으로
-- C.CategoryID = E.EmployeeID 를 추가해서 교집합을 가져온다.

 

차집합

SELECT CategoryID AS ID
FROM Categories
WHERE 
  CategoryID > 4
  AND CategoryID NOT IN (
    SELECT EmployeeID
    FROM Employees
    WHERE EmployeeID % 2 = 0
  );
-- 조건을 통해서 차집합을 만들어준다.

 

대칭 차집합

SELECT ID FROM (
  SELECT CategoryID AS ID FROM Categories
  WHERE CategoryID > 4
  UNION ALL
  SELECT EmployeeID AS ID FROM Employees
  WHERE EmployeeID % 2 = 0
) AS Temp 
GROUP BY ID HAVING COUNT(*) = 1;
-- 조건을 통해서 두 쿼리사이에 교집합이 아닌 모든 것들을 반환하게 만들었다.

 

마치며...

MySQL에 대한 기술포스팅은 여기까지 해야겠다... 무궁무진하게 많기 때문에 가장 자주 쓰일 것 같은 내용만 포스팅 했다. 실제로 MySQL내에서도 사용하면서 응용만 한다면 어떤 데이터든 가져 올 수 있겠다는 자신감 조금은 생긴 것 같다.

 

예시자료 출처 -  https://www.yalco.kr/lectures/sql/

'Backend > MySQL' 카테고리의 다른 글

[ MySQL ] 데이터베이스 SELECT  (0) 2023.01.30
[ MySQL ] 데이터베이스  (4) 2023.01.29
2023. 1. 30. 21:34

MySQL의 SELECT기능 다뤄보기

이 게시글은 전반적으로  SELECT기능을 기술한 것이 대부분이다.

이 게시글에 모든 예시는 아래 링크를 통해서 사용해 볼 수 있다.스키마 이름이나 칼럼이름 역시 같아 결과값이 그대로 적용되어 보여진다.

https://www.w3schools.com/mysql/trymysql.asp?filename=trysql_select_all

 

MySQL Tryit Editor v1.0

WebSQL stores a Database locally, on the user's computer. Each user gets their own Database object. WebSQL is supported in Chrome, Safari, and Opera. If you use another browser you will still be able to use our Try SQL Editor, but a different version, usin

www.w3schools.com

 

데이터 Customers에서 모든내용 보기

SELECT * FROM Customers;
-- 이와 같이 주석을 달 수 있다.

 

원하는 column(열)만 골라서 보기

SELECT CustomerName FROM Customers;
--Customers스키마에서 CustomerName의 열만 골라서 보여준다.

SELECT CustomerName, ContactName, Country
FROM Customers;
--이렇게 다중으로 볼 수도 있다.

 

테이블의 column이 아닌 값 보기

SELECT
  CustomerName, 1, 'Hello', NULL
FROM Customers;
-- 1과 Hello와 NULL은 테이블의 column이 아니다.
-- 하지만 위와같이 입력하면 해당 내용과 같은 것들만 보여진다.

 

원하는 row(행)만 선택해서 보기

SELECT * FROM Orders
WHERE EmployeeID = 3;
-- 데이터 Orders에서 모든것 중에서 WHERE(조건문)을
-- 사용해서 EmployeeID가 3인 행만 보여지게 된다.

SELECT * FROM OrderDetails 
WHERE Quantity < 5;
-- 이런식으로 조건문을 꼭 등호가 아니여도 된다.
-- if문처럼 TRUE or FALSE로 반환되도록 잘 붙여주면 된다.

 

원하는 데이터 순서대로 가져오기

ORDER BY 구문을 사용하여 특정 column을 기준으로 잡고
-- 오름, 내림차순으로 볼 수 있다. Default는 오름차순이다.
-- ASC = 오름차순  /  DESC = 내림차순

SELECT * FROM Customers
ORDER BY ContactName;
-- 아무것도 지정해주지 않았기 때문에 ContactName을 기준으로
-- 오름차순으로 정렬된다.

SELECT * FROM OrderDetails
ORDER BY ProductID ASC, Quantity DESC;
-- 이런 경우 먼저 나온 column이 먼저 기준이 되어 정렬되고
-- 먼저 정렬된 것을 기준으로 그 다음 column이 정렬된다.

 

원하는 만큼만 데이터 가져오기

-- LIMIT {가져올 갯수} 또는 LIMIT {건너뛸 갯수}, {가져올 갯수} 를 사용하여,
-- 원하는 위치에서 원하는 만큼만 데이터를 가져올 수 있다.

SELECT * FROM Customers
LIMIT 10;
-- (첫줄부터 10까지)

SELECT * FROM Customers
LIMIT 0, 10;
-- (0~10까지)

SELECT * FROM Customers
LIMIT 30, 10;
-- (30번째줄부터 시작해서 10줄까지)

 

원하는 별명(alias)으로 데이터 가져오기

SELECT
  CustomerId AS ID,
  CustomerName AS NAME,
  Address AS ADDR
FROM Customers;

SELECT
  CustomerId AS '아이디',
  CustomerName AS '고객명',
  Address AS '주소'
FROM Customers;

 

 

사칙연산

 

1. 기본 사칙연산

 

  • +, -, *, /
    각각 더하기, 빼기, 곱하기, 나누기
  • %, MOD
    나머지
SELECT 1 + 2;
SELECT 5 - 2.5 AS DIFFERENCE;
SELECT 3 * (2 + 4) / 2, 'Hello';
SELECT 10 % 3;

 

  • 문자열에 사칙연산을 가하면 0으로 인식한다.
SELECT 'ABC' + 3;
SELECT 'ABC' * 3;
SELECT '1' + '002' * 3;
-- 숫자로 구성된 문자열은 숫자로 자동인식

 

  • 또 다른 예제
SELECT
  OrderID + ProductID
FROM OrderDetails;
-- OrderID의 숫자와 ProductID의숫자가 더해진다.

SELECT
  ProductName,
  Price / 2 AS HalfPrice
FROM Products;
-- ProductName과 Price를 나누기 2한 결과값의
-- 데이터이름을 HalfPrice로 보여지게 했다.

 

 

2. 참/거짓 관련 연산자

 

  • MySQL에서는 TRUE는 1, FALSE는 0으로 저장됩니다.
SELECT TRUE, FALSE;
-- true = 1  , false = 0

 

  • 또 다른 예제
SELECT !TRUE, NOT 1, !FALSE, NOT FALSE;
-- !true = 0, not 1 = 0, !false = 1, not false = 1
-- 트루의 반대는 펄스라서 0, not은 아니다의 뜻

SELECT 0 = TRUE, 1 = TRUE, 0 = FALSE, 1 = FALSE;
-- 0, 1, 1, 0
-- 1=true이고 0=false이기 때문이다.

SELECT * FROM Customers WHERE City = 'Berlin';
-- 전체 데이터에서 where조건문에 의해서 시티가 베를린인 것을  select한다.

 

  • IS = 양쪽이 모두 TRUE 또는 FALSE
    IS NOT = 한쪽은 TRUE, 한쪽은 FALSE
SELECT TRUE IS TRUE;
-- true는 true가 맞다.  return 1;

SELECT TRUE IS NOT FALSE;
-- true는 false가 아니다. return 1;

SELECT (TRUE IS FALSE) IS NOT TRUE;
-- true는 false이다. <= 이것은 true가 아니다. return 1;

 

  • AND, && = 양쪽이 모두 TRUE일 때만 TRUE
    OR, || = 한쪽은 TRUE면 TRUE
    Javascript와 동일하게 작동한다.
SELECT TRUE AND FALSE, TRUE OR FALSE;
-- Javascript ver.
-- true && false = false, true || false = true

SELECT 2 + 3 = 6 OR 2 * 3 = 6;
-- 5와 6이지만 or을 사용했기때문에 true를 반환.

SELECT * FROM Orders
WHERE
  CustomerId = 15 AND EmployeeId = 4;
-- and를 썼기 때문에 두 조건 모두 충족하는 데이터만 보임.
-- CustomerId = 15 OR EmployeeId = 4; 이면 두 조건중 하나만
-- 충족해도 보여지게 된다.

SELECT * FROM Products 
WHERE
  ProductName = 'Tofu' OR CategoryId = 8;
-- 두 조건중 하나라도 충족한 경우 모두 보여진다.

SELECT * FROM OrderDetails
WHERE
  ProductId = 20
  AND (OrderId = 10514 OR Quantity = 50);
-- ProductId = 20이면서 OrderId = 10514이거나
-- Quantity = 50인 데이터를 모두 보여준다.

 

  • = 양쪽 값이 같음
    !=, <> 양쪽 값이 다름
    >, < (왼쪽, 오른쪽) 값이 더 큼
    >=, <= (왼쪽, 오른쪽) 값이 같거나 더 큼
SELECT 1 = 1, !(1 <> 1), NOT (1 < 2), 1 > 0 IS NOT FALSE;
-- 1, 1, 0, 1

SELECT 'A' = 'A', 'A' != 'B', 'A' < 'B', 'A' > 'B';
-- 1, 1, 1, 0
-- 알파뱃은 나중에 나오는 알파뱃일수록 크다고 판정된다.

-- MySQL의 기본 사칙연산자는 대소문자 구분을 하지 않는다.
SELECT 'A' = 'a';
-- true이다. 구분하지 않기때문에

SELECT
  ProductName, Price,
  Price > 20 AS EXPENSIVE 
FROM Products;
-- ProductName과 Price데이터를 보여주고
-- Price가 20보다 높으면 EXPENSIVE라는 별명에
-- 1이 추가된다 (20보다 높으면 참이 되기 때문)

SELECT
  ProductName, Price,
  NOT Price > 20 AS CHEAP 
FROM Products;
-- ProductName과 Price데이터를 보여주고
-- Price가 20보다 크지 않은 것(not)은 CHEAP이라는 별명에
-- 1이 추가 된다.

 

  • BETWEEN {MIN} AND {MAX} = 두 값 사이에 있다.
    NOT BETWEEN {MIN} AND {MAX} =  두 값 사이가 아닌 곳에 있다.
SELECT 5 BETWEEN 1 AND 10;
-- 5는 1과 10 사이에 있다. = true

SELECT 'banana' NOT BETWEEN 'Apple' AND 'camera';
-- a와 c사이에 b가 있는건 맞지만 not이 붙어서 false를 반환한다.

SELECT * FROM OrderDetails
WHERE ProductID BETWEEN 1 AND 4;
-- OrderDetails에서 ProductID가 1~4인 데이터만 보여진다.

SELECT * FROM Customers
WHERE CustomerName BETWEEN 'b' AND 'c';
-- b와 c사이는 b로 시작하는 모든 문장을 말한다.
-- c를 넘기진 말아야 하니 c로시작하는 문장은 제외된다.

 

  • IN (...) 괄호 안의 값들 가운데 있다.
    NOT IN (...) 괄호 안의 값들 가운데 없다.
SELECT 1 + 2 IN (2, 3, 4) 
-- 1+2 = 3 괄호안에 3 있으니까 true를 반환한다.

SELECT 'Hello' IN (1, TRUE, 'hello') 
-- hello가 있으니 true를 반환. 대소문자를 구분안한다는 것을 기억하자.

SELECT * FROM Customers
WHERE City IN ('Torino', 'Paris', 'Portland', 'Madrid') 
-- Customers데이터에서 City내부에 
-- 'Torino', 'Paris', 'Portland', 'Madrid'가 있는 데이터만 보여진다.

 

  • LIKE '... % ...' 0~N개 문자를 가진 패턴
    LIKE '... _ ...' _ 갯수만큼의 문자를 가진 패턴
SELECT
  'HELLO' LIKE 'hel%',   = 1
-- hel이후 어떤 문자가 와도 true

  'HELLO' LIKE 'H%',   = 1
-- h로 시작하고 그 이후 어떤 문자가 있던 true

  'HELLO' LIKE 'H%O',   = 1
-- H로 시작해서 O로 끝나기만 하면 true

  'HELLO' LIKE '%O',   = 1
-- 앞에 몇글자가 있든 무슨 글자가 있든 O로 끝나면 true

  'HELLO' LIKE '%HELLO%',  = 1
-- HELLO라는 글자 앞과 뒤에 어떤 문자가 얼마나 있든
-- HELLO만 있다면 true, 0~N개라서 아무것도 없이 HELLO여도 true

  'HELLO' LIKE '%H',   = 0 
-- H로 끝나야 true인데 HELLO이기때문에 O로 끝나서 false를 반환한다.

  'HELLO' LIKE 'L%'   = 0
-- L로 시작해야 하는데 HELLO라서 false를 반환한다.

 

연산자  총정리!

SELECT
  'HELLO' LIKE 'HEL__',    = 1
  'HELLO' LIKE 'h___O',    = 1
  'HELLO' LIKE 'HE_LO',    = 1
  'HELLO' LIKE '_____',    = 1
  'HELLO' LIKE '_HELLO',    = 0
  'HELLO' LIKE 'HEL_',    = 0
  'HELLO' LIKE 'H_O'    = 0
-- 무슨 글자든 _ 의 갯수에 맞춰서 트루와 펄스를 반환한다.


/*

[ +, -, *, / ] === 각각 더하기, 빼기, 곱하기, 나누기

[ %, MOD ] === 나머지

[ IS ] === 양쪽이 모두 TRUE 또는 FALSE

[ IS NOT] === 한쪽은 TRUE, 한쪽은 FALSE

[ AND, && ] === 양쪽이 모두 TRUE일 때만 TRUE

[ OR, || ]=== 한쪽은 TRUE면 TRUE

[ = ] === 양쪽 값이 같음

[ !=, <> ] === 양쪽 값이 다름

[ >, < ] ===(왼쪽, 오른쪽) 값이 더 큼

[ >=, <= ] === (왼쪽, 오른쪽) 값이 같거나 더 큼

[ BETWEEN {MIN} AND {MAX} ] === 두 값 사이에 있음

[ NOT BETWEEN {MIN} AND {MAX} ] === 두 값 사이가 아닌 곳에 있음

[ IN (...)] === 괄호 안의 값들 가운데 있음

[ NOT IN (...) ] === 괄호 안의 값들 가운데 없음

[ LIKE '... % ...' ] === 0~N개 문자를 가진 패턴

[ LIKE '... _ ...' ] === _ 갯수만큼의 문자를 가진 패턴

*/

 

 

 

 ※  예시가 너무 방대하여 이해가 난해한 경우만 간략한 예시를 작성했다.  ※

Math

  • ROUND = 반올림
    CEIL = 올림
    FLOOR = 내림

  • ABS = 절대값
  • GREATEST() = 가장 큰 값
  • LEAST() = 가장 작은 값

  • MAX()  = 가장 큰 값
    MIN()  = 가장 작은 값
    COUNT()  = 갯수 (NULL값 제외)
    SUM()  = 총합
    AVG()  = 평균 값

  • TRUNCATE(N, n) = N을 소숫점 n자리까지 선택

 

문자열 케이스

  • UCASE, UPPER = 모두 대문자로
    LCASE, LOWER = 모두 소문자로
  • CONCAT(...) = 괄호 안의 인자(내용)를 이어붙임
    SELECT CONCAT('HELLO', ' ', 'THIS IS ', 2021)
    = (HELLO THIS IS 2021) 숫자도 문자열로 자동변환되어 더해진다.

    CONCAT_WS(S, ...) = 첫번째 인자로 이어붙임
    SELECT CONCAT_WS('-', 2021, 8, 15, 'AM')
    = (2021-8-15-AM)

  • SUBSTR(), SUBSTRING() = 주어진 값에 따라 문자열 자름
    SELECT
    SUBSTR('ABCDEFG', 3),
    = 왼쪽에서 3번째 문자부터 읽어온다. (CDEFG)

    LEFT() = 왼쪽부터 N글자
    RIGHT() = 오른쪽부터 N글자

 

그룹함수

 

1. 예시

SELECT Country FROM Customers
GROUP BY Country;
-- 중복되는 것들을 모두 하나로 묶어서 하나씩만 보여진다.


SELECT 
  Country, City,
  CONCAT_WS(', ', City, Country) as 'city, country'
FROM Customers
GROUP BY Country, City;
-- GROUP BY를 컨트리와 시티에 해주어서 Country의 값이 같은
-- 값이라도 City의 값이 다르면 다 보여준다.

 

2. 그룹함수 활용하기

SELECT
  COUNT(*), OrderDate
FROM Orders
GROUP BY OrderDate;
-- 그룹으로 묶인 값들의 갯수도 같이 볼 수 있다.

SELECT
  ProductID,
  SUM(Quantity) AS QuantitySum
FROM OrderDetails
GROUP BY ProductID
ORDER BY QuantitySum DESC;

SELECT
  CategoryID,
  MAX(Price) AS MaxPrice, 
  MIN(Price) AS MinPrice,
  TRUNCATE((MAX(Price) + MIN(Price)) / 2, 2) AS MedianPrice,
  TRUNCATE(AVG(Price), 2) AS AveragePrice
FROM Products
GROUP BY CategoryID;

SELECT 
  CONCAT_WS(', ', City, Country) AS Location,
  COUNT(CustomerID)
FROM Customers
GROUP BY Country, City;

Tryit Editor을 통해서 사용해보면 결과값을 확인할 수 있다.

 

  • WITH ROLLUP = 전체의 집계값 (ORDER BY와는 사용불가능)
SELECT
  Country, COUNT(*)
FROM Suppliers
GROUP BY Country
WITH ROLLUP;
-- COUNT의 합계가 가장 밑에 추가된다.

 

  • HAVING = 그룹화된 데이터 걸러내기
SELECT
  Country, COUNT(*) AS Count
FROM Suppliers
GROUP BY Country
HAVING Count >= 3;
-- Count로 집계된 데이터중 3과 같거나 큰 값만 보여준다.

 

  • WHERE는 그룹하기 전 데이터, HAVING은 그룹 후 집계에 사용한다.
SELECT
  COUNT(*) AS Count, OrderDate
FROM Orders
WHERE OrderDate > DATE('1996-12-31')
GROUP BY OrderDate
HAVING Count > 2;
-- WHERE로 1996-12-31보다 높은 수의 날짜들로 거른 후
-- OrderDate를 그룹화 시켜준 후 그룹화가 되었으니 HAVING으로 2보다 높은 수만 보여준다.

SELECT
  CategoryID,
  MAX(Price) AS MaxPrice, 
  MIN(Price) AS MinPrice,
  TRUNCATE((MAX(Price) + MIN(Price)) / 2, 2) AS MedianPrice,
  TRUNCATE(AVG(Price), 2) AS AveragePrice
FROM Products
WHERE CategoryID > 2
GROUP BY CategoryID
HAVING
  AveragePrice BETWEEN 20 AND 30
  AND MedianPrice < 40;
-- 이와같은 예시로 WHERE로 먼저 데이터를 걸러준 후 GROUP BY를
-- 사용해서 묶어준 후 HAVING으로 걸러낼 수 있다.

 

  • DISTINCT = 중복된 값들을 제거한다.
    GROUP BY 와 달리 집계함수가 사용되지 않는다.
    GROUP BY 와 달리 정렬하지 않으므로 더 빠르다.
SELECT DISTINCT CategoryID
FROM Products;
-- 위의 GROUP BY를 사용한 쿼리와 결과 비교
-- GROUP BY는 오름차순(Default)으로 정렬되어 보여주지만 DISTINCT는
-- 정렬하지 않은채로 데이터를 보여준다.

SELECT COUNT DISTINCT CategoryID
FROM Products;
-- 오류 발생
-- DISTINCT는 집계함수를 사용할 수 없어서 오류가 발생한다.

 

  • GROUP BY와 DISTINCT 함께 활용
SELECT
  Country,
  COUNT(DISTINCT CITY)
FROM Customers
GROUP BY Country;
-- 그룹으로 묶어줄때 집계함수 내부에 포함시켜 줄 수 있다.

 

 

마치며...

포스팅을 하다보니 너무 방대해서 포스팅을 나눠서 해야할 필요성을 느껴, 이만작성하고 다음 포스팅에서 상관 서브쿼리와 비상관 서브쿼리, JOIN 등에 대해서 포스팅을 마저 해야겠다.

 

예시자료 출처 -  https://www.yalco.kr/lectures/sql/

'Backend > MySQL' 카테고리의 다른 글

[ MySQL ] 상관,비상관 서브쿼리와 JOIN  (0) 2023.02.02
[ MySQL ] 데이터베이스  (4) 2023.01.29