728x90

3_WHERE_1=1은_무엇일까

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2023-12-09
  • 수정일: 2023-12-18

 

주제를 선정한 이유

종종 Query와 관련해서 WHERE 1=1 조건절을 보게 됩니다.
언제나 참인 조건으로 왜 조건절에 쓰일까에 대해 생각해 보고자 글을 작성하게 되었습니다.

 

WHERE 1=1의 의미

1은 1과 같다는 의미로 언제나 참인 것을 의미합니다. WHERE 조건절은 주로 데이터에 조건을 추가하여 필터링하는 기능으로 사용되는데, 이러한 측면에서 WHERE 1=1은 특별한 기능을 갖지 않는다고 할 수 있습니다.

 

WHERE 1=1의 장점

별다른 기능이 없어 보이는 언제나 참인 조건은 다음과 같은 장점이 있습니다.

  1. 코드 디버깅의 간편함
SELECT NAME
  FROM USER_T
 WHERE MARKETING_YN_FLAG = 'Y'
  AND PHONE_NUMBER IS NOT NULL
;
SELECT NAME
  FROM USER_T
-- WHERE MARKETING_YN_FLAG = 'Y'
 WHERE PHONE_NUMBER IS NOT NULL
;
SELECT NAME
  FROM USER_T
 WHERE 1=1
   AND MARKETING_YN_FLAG = 'Y'
   AND PHONE_NUMBER IS NOT NULL
;
SELECT NAME
  FROM USER_T
 WHERE 1=1
-- AND MARKETING_YN_FLAG = 'Y'
   AND PHONE_NUMBER IS NOT NULL
;

WHERE 1=1 조건을 사용한다면, 여러 조건이 있을 때 간편한 주석 처리로 디버깅을 편하게 할 수 있습니다.

  1. 동적 쿼리에서의 유용성
query1 = "SELECT * FROM BOARD";
if(!userId.equals("")){  
	query2 = " WHERE USER\_ID = " + userId;  
}  
if(!title.equals("")){  
	if(!userId.equals("")){  
    	query3 = " AND";  
	} else {  
    	query3 = " WHERE";  
	}  
    	query4 = " WHERE TITLE LIKE %" + title + "%";  
}
query1 = "SELECT * FROM BOARD WHERE 1=1";

if(!userId.equals("")){
    query2 = " AND USER_ID = " + userId;
}
if(!title.equals("")){
    query3 = " AND TITLE LIKE %" + title + "%"
}

또한, WHERE 1=1 조건을 사용하여 동적 쿼리를 쉽게 작성하고 코드의 가독성도 높일 수 있습니다.

 

WHERE 1=1의 단점

SELECT EMAIL
       , PHONE
  FROM MEMBER
 WHERE 1=1
<if test="email != null">
    AND email = #{email}
</if>
<if test="phone != null">
    AND phone = #{phone}
</if>

위의 조건에서 email과 phone이 null일 경우, 전체 데이터가 조회됩니다. 많은 데이터를 한 번에 처리해야 하므로 응답 지연의 문제가 발생합니다. 또한, SELECT절이 아닌 UPDATE, DELETE문일 경우에는 전체 데이터에 변경이 발생하는 위험이 있습니다.

(최근에 로컬에서 SQL 쿼리를 작성하다가 실수로 UPDATE문을 WHERE 조건절 없이 실행했었습니다. 한 컬럼의 모든 데이터들이 동일한 값을 갖는 무서운 경험을 했습니다..😮)

 

WHERE 1=1의 대안

  1. <where>사용
SELECT EMAIL
       , PHONE
FROM MEMBER
<where>
    <if test="email != null"> AND EMAIL = #{email} </if>
    <if test="phone != null"> AND PHONE = #{phone} </if>
</where>

를 사용하면 조건 변경을 유연하게 실행할 수 있습니다. 그러나, email과 phone이 모두 null 값으로 전달되면 WHERE 1=1처럼 모든 데이터가 조회되는 위험이 있습니다.

  1. <trim>사용
SELECT EMAIL
       , PHONE
  FROM MEMBER
<trim prefix="WHERE" prefixOverrides="AND">
    <if test="email != null"> AND EMAIL = #{email} </if>
    <if test="phone != null"> AND PHONE = #{phone} </if>
</trim>

접두사로 WHERE를 추가하고, 문 안의 쿼리 가장 앞에 해당하는 문자가 있으면 자동으로 이를 제거(prefixOverrides)하는 trim 태그를 사용할 수도 있습니다. 그러나, 1번과 마찬가지로 인자가 모두 null로 넘어올 경우, WHERE 1=1을 사용한 것처럼 모든 데이터가 조회되는 위험이 있습니다.

  • prefixOverrides: trim 태그 안 문자열의 처음이 일치하는 경우에만 삭제를 시도. 따라서 'AND EMAIL'은 'AND'로 시작하기 때문에 'AND'가 삭제되었지만, 'AND PHONE'은 'AND EMAIL'의 뒤에 있으면 문자열의 시작이 'AND'가 아니므로 'AND'는 삭제되지 않음
  1. <trim>사용2
SELECT EMAIL
       , PHONE
  FROM MEMBER
 WHERE
<trim prefixOverrides="AND">
    <if test="email != null"> AND EMAIL = #{email} </if>
    <if test="phone != null"> AND PHONE = #{phone} </if>
</trim>

WHERE는 trim 밖에서 선언하는 방식으로 EMAIL과 PHONE이 모두 null 값일 경우에는 BadSqlGrammarException을 발생시켜 전체 데이터가 조회되는 것을 방지할 수 있게 됩니다.

  1. Java에서 검증
    이 외에도 추가로 @Valid 또는 @Validated 등 파라미터 검증을 통해 Null 값인 데이터가 DB로 넘어가지 않도록 사전에 안전장치를 마련하는 방법을 활용할 수 있습니다.

 

정리

3_WHERE_1=1은_무엇일까.

  • 항상 참인 조건으로 디버깅이나 동적 쿼리 작성 시 편리함을 제공
  • 파라미터를 전달받아 사용할 경우,
    • 의도치 않은 전체 데이터 조회를 발생시킬 수 있고,
    • UPDATE, DELETE문과 사용 시, 전체 데이터를 변경시킬 수 있는 위험이 존재
  • 해당 위험을 제거하려면
    • WHERE와 을 함께 사용

 

 

📚참고 자료

MyBatis(마이바티스)에서 사용하는 WHERE 1=1 의 위험성 및 예방(Feat. 장애)

MyBatis의 where 1=1 사용 대신 trim으로 해결해보자

728x90
728x90

2_공식_문서를_어떻게_읽을_것인가

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2023-12-05
  • 수정일: 2023-12-07

주제를 선정한 이유

학원에서 프로젝트를 진행할 때, 대부분의 문제를 블로그 글로 해결하였습니다.

물론 구글 검색을 통해서 공식 문서를 접하는 일도 있었지만, 영문보다는 사람들이 읽고 한국어로 정리해둔 글이 편해서 사실 공식 문서를 읽은 적이 거의 없었습니다.

그러다 최근에 VS Code라는 Code Editor에서 VSCode Pets라는 extension을 설치하면서 처음으로 공식 문서(보다는 가벼운 느낌이지만)를 제대로 읽어보게 되었습니다.

(귀여운 extension입니다, 사용해보시길 추천드립니다🤓!)

지금은 이렇게 짧고 간단한 문서를 읽었지만, 길고 많은 내용을 담은 문서는 어떻게 읽으면 좋을지 정리해보고 싶은 생각에 글을 작성하게 되었습니다.

공식 문서 종류

제가 접해본 공식 문서는 크게 5가지 입니다.

  1. 언어에 대한 공식 문서(Java, JavaScript 등)
  2. 프레임워크에 대한 공식 문서(Spring, SpringBoot, JUnit 등)
  3. 라이브러리에 대한 공식 문서(React, jQuery 등)
  4. API에 대한 공식 문서(공공 데이터 포털 등)
  5. 그 외 기타(VScode-pets 등)

그 중에서도 오늘 이야기해보고 싶은 부분은 API입니다.

API 문서

위에 보이는 이미지는 Cafe24에서 제공하는 API를 사용 방법을 정의한 문서입니다. REST 방식에 맞춰서 데이터를 조회, 등록, 수정 등을 하는 방법이 나와있습니다.

만일 해당 API 문서를 처음 접하게 된다면 어떻게 이용하면 좋을지 짧게 적어보고자 합니다.

  1. 개략적인 요구사항 파악하기
    • 정확히는 현재 무엇을 필요로 하는지에 대한 고민이 필요합니다.
      이미지 상에는 Side Bar에 Order만 나와있지만, 이 외에도 Store, Customer, Product 등 다양한 카테고리가 있습니다.
      처음보는 문서에서 필요한 정보를 한 번에 찾기 어려울 수 있으므로 크게 내가 지금 `어떤 것을 필요로 하는지` 또는 `필요로 하는 것과 관련된 것은 무엇인지`에 대해서만 검색을 합니다.
      이미지를 예시로 설명하자면, 현금 영수증으로 처음부터 정보를 찾기보다는 주문 또는 결제 시에 현금 영수증을 발행할 수 있으므로 주문이나 결제 관련 카테고리를 먼저 찾아보는 것입니다.
      찾고 있는 내용과 유사한 주제가 나타난다면 해당 EndPoints를 언제 사용하는지에 대한 설명을 읽어봅니다.
  2. CRUD 선택하기
    • 필요한 내용을 찾았다면, 목록을 조회하고 싶은 것인지 데이터를 등록하고 싶은 것인지 선택해야 합니다. 행위에 따라 REST 방식도 함께 결정됩니다.
      보통 CRUD는 다음과 같은 동사와 함께 쓰입니다.
    • 조회: GET - Retrieve a list ...
    • 등록: POST - Create ...
    • 수정: PUT/PATCH - Update ...
    • 삭제: DELETE/PUT/PATCH - Delete ...
  3. Response Data 확인하기
    • 큰 카테고리와 EndPoints가 결정되었다면, Response Data를 확인합니다. 실제로 필요한 정보가 응답 데이터에 포함이 되어있지 않다면 다시 1번부터 반복합니다.
    • 만일 현금 영수증을 발행하는 작업을 하고 싶다면 `Create a cash receipt`에서 `cash receipt_no` 또는 `approval_no`이 있는지 확인하는 것입니다. ID를 통해 실제로 현금영수증이 발행되었는지 조회할 수 있기 때문입니다.
  4. Request 방식 확인하기
    • 응답데이터에 필요한 정보가 포함되어있다면 요청 방식을 살펴봅니다. 특히, 등록이나 수정은 Request Body를 사용해서 올바른 형식에 맞춰 데이터를 전송해야합니다. 그러므로 요청 방식을 상세히 살펴보는 것이 중요합니다.
      다음은 현금 영수증을 생성하고자 할 때, 사용하는 API 주소와 Request Body 내용입니다.
    • EndPoints: POST /api/v2/admin/cashreceipt
    • -d '{
          "request": {
          "order_id": "20201013-0000096",
          "type": "personal",
          "cellphone": "01000000000"
      }
      }'

- "request" 부분을 살펴보면 "order_id" / "type" / "cellphone"이 필요합니다. 직관적으로 알 수 있는 key 값이지만
- 의미하는 바를 다시 한 번 확인하는 것이 중요하고,
- 각 요청값이 필수인지 선택인지도 확인해야 합니다.

  1. API TEST
    • 최종적으로 Local 환경에서 API 테스트를 수행합니다.

정리

공식_문서를_어떻게_읽을_것인가.

  • 필요한 정보를 명확히 정리한다.
  • 대분류 → 소분류로 범위를 좁혀가며 해당 정보의 위치를 탐색한다.
  • 비슷한 정보를 발견하면
    • CRUD를 선택해서
    • Response Data를 확인한다.
  • 해당 정보가 필요할 경우,
    • Request Body를 살펴서 각 요소 값의 의미와 사용 방법을 살펴본 후
    • API 통신을 시도해본다.

 

 

 

📚참고 자료

Cafe24 Developers

728x90
728x90

1_어떤 값을 PK로 설정할 것인가

조금 더 생각해 보고 싶은 부분을 공부한 글입니다.

  • 작성일: 2023-12-01
  • 수정일: 2023-12-02

 

주제를 선정한 이유

교육 과제로 하나의 시스템을 만드는 프로젝트를 시작했습니다.

프로젝트를 진행하면서 느낌으로만 판단하던 부분을 명확히 하고자 글을 작성했습니다.

먼저, 주제 선정 과정은 다음과 같습니다.

프로젝트에서 본격적인 개발을 앞두고 다음과 같은 설계 과정을 거쳤습니다.

  • 요구사항 분석
  • Flow chart
  • Mockup
  • 테이블 설계서 및 ERD

요구사항 분석과제의 주제에 초점을 맞춰 작성하였고,

Flow Chart는 해당 시스템을 사용하는 사용자가 어떤 방식으로 업무하고 있는지를 정리하였습니다.

그다음으로 Mockup은 개략적인 화면 설계 및 배치도를 작성하였습니다.

마지막으로 테이블 설계서 및 ERD를 작성하며 PK를 설정하는 기준을 명확히 하지 못했다고 생각하여 글을 작성하게 되었습니다.

가장 먼저, PK란 Primary Key(기본 키)의 줄인 말로 테이블에서 레코드 하나하나를 유일하게 식별하는 값을 가진 컬럼을 의미합니다. 그러므로 컬럼 값이 중복되어서도 Null 값이어서도 안 됩니다(= Unique와 Not Null 조건).

예를 들어, <전화번호부> 테이블이 다음과 같을 때

순번 이름 전화번호 주소
1 김 이름 010-1234-5678 서울시 동작구
2 이 전화 010-5678-1234 서울시 서초구
3 박 주소 010-1245-2356 서울시 종로구

전화번호는 1인당 1개, 중복된 번호를 가질 수 없으므로 전화번호만 있다면 이름과 주소를 유일한 행으로 구분할 수 있습니다. 이런 의미에서 전화번호는 <전화번호부> 테이블의 PK, 즉 기본 키입니다.

그러나 테이블을 설계할 때 기본 키는 전화번호보다는 시퀀스 값(1, 2, 3, ...), 또는 코드(TELBOOK001, TELBOOK002, ...) 등으로 설정을 해주었습니다.

왜 시퀀스나 코드를 이용했을까, 생각해 보면 사용했던 DBMS에서 자동으로 값을 증가시켜 주는 기능이 있었기에 편리했습니다.

  • Oracle에서는 Sequence 기능을 활용하였고,
  • MySQL에서는 Auto Increment 기능을 사용했습니다.
    또한, 유일한 식별자 역할을 하는 컬럼 값의 경우 데이터 길이가 긴 편에 속하기 때문에 가독성이 떨어진다고 생각했습니다.

그러나 한 편으로는 레코드를 식별할 수 컬럼(<전화번호부> 테이블에서는 전화번호)이 있는데, 굳이 새로운 컬럼(<전화번호부> 테이블에서는 순번)을 추가해야할까라는 생각도 했습니다.

 

기본키 설정 시 유의 사항

특정 컬럼이 기본키가 되기 위해서는 다음과 같은 조건을 만족해야 합니다.

  • 유일성: 테이블에서 동일한 값을 갖는 레코드가 없어야 함
  • 안정성: 컬럼의 속성 값이 변경되지 않아야 함
  • 환원 불가능성: 복합키 사용 시, 복합키의 일부 키만으로 테이블 내의 모든 레코드를 식별할 수 없어야 함
  • 단순성: 최소로 PK를 설정하고 간단한 값을 갖는 컬럼을 선택해야 함

유일성은 PK의 정의와도 맞닿아 있는 개념입니다.

안정성은 왜 PK를 전화번호가 아닌 순번으로 PK를 설정하면 좋은가에 대한 답이 될 수 있다고 생각합니다. 전화번호는 일반적으로 1인당 1개의 값을 갖는 특별한 값이지만 손쉽게 변경할 수 있습니다. 기본키로 설정할 경우, 그 값이 쉽게 바뀔 수 있기 때문에 기본키로는 설정하지 않는 것이 권장되는 이유입니다. 뿐만 아니라, 주민등록번호 등도 변경될 가능성은 언제나 존재합니다.

이럴 때 사용할 수 있는 것이 대체 키(Surrogate Key)입니다. 자연 키(Natural Key)를 대신해서 사용하기 위해 인공적으로 만든 키입니다. 전화번호나 주민등록번호 등은 자연키라고도 하는데, 이는 해당 키 값이 실제 세계에서도 의미를 갖는 값임을 의미합니다.

대체 키를 사용하게 되면 다음과 같은 여러 이점이 있습니다.

  • 변경될 가능성이 낮음
    • PK는 여러 인덱스에서도 사용될 가능성이 높으므로, 변경될 경우 여러 파급 효과가 발생할 수 있음
  • 주민등록번호와 같은 민감 정보를 대신해서 사용하므로 보안상 이점이 있음
  • 단일의 짧은 대체키를 사용해서 성능을 향상 시킬 수 있음
    • PK는 다른 테이블의 FK가 되는 경우가 많고, 여러 인덱스에서도 사용될 가능성이 높음
  • 키의 생성과 관리를 완전히 DBMS에 위임하여 데이터 무결성을 보장할 수 있음

따라서 PK는 대체 키를 사용해서 단일 키로 짧게 설정하는 것이 좋습니다.

환원 불가능성은 제 2 정규화와 관련된 내용으로 주제를 조금 벗어나는 것 같아 여기서는 추가적으로 언급하지 않겠습니다.

데이터베이스 정규화

마지막으로 단순성은 대체 키의 장점 중 성능 향상으로 언급했던 부분 관련이 있습니다. 여러 테이블에서 참조되는 값이고, 인덱스로도 활용될 가능성이 높으므로 최대한 단일키로 단순한 값을 설정하는 것이 좋습니다.

 

정리

어떤 값을 PK로 설정할 것인가.

  • 대체 키를 사용해서
    • 각 레코드를 유일하게 식별하도록 하고,
    • 변경되지 않도록 하며,
    • 단일 키로 짧은 값을 설정하여 성능을 높일 수 있도록 하자.

* 단, 반드시 대체 키를 사용해야 한다기보다는 자연키가 적합한 경우도 있을 수 있다.

 

 

 

📚참고 자료

3.6.9 Using AUTO_INCREMENT
CREATE SEQUENCE
What's the best practice for primary keys in tables?
How to Choose a Good Primary Key
Surrogate Key
[Database] 자연키(Natural key)와 대체키(Surrogate Key), PK(기본키)를 대체키로 설정해야 하는 이유

728x90