이전 글: WMS DB 설계 기초

WMS 데이터베이스 실습 — INSERT와 외래키 무결성 검증

상품 등록 (INSERT)

INSERT INTO products (barcode, prod_name) VALUES ('1234','두쫀쿠');  

재고 등록 (INSERT)

INSERT INTO inventory (barcode, quantity, location_code) VALUES ('1234', 100, 'A-1-1');

Foreign Key(외래키) 무결성 검증 — 에러 유발 테스트

존재하지 않는 바코드 '9999'로 재고 등록

INSERT INTO inventory (barcode, quantity, location_code) VALUES ('9999', 50, 'B-1-1');

🚨 에러 메시지

inventory 테이블을 만드는데 애러발생

Cannot add or update a child row: a foreign key constraint fails (`wms_db`.`inventory`, CONSTRAINT `inventory_ibfk_1` FOREIGN KEY (`barcode`) REFERENCES `products` (`barcode`))

원인 분석

products 테이블에 '9999'라는 바코드를 가진 상품이 없으므로 inventory에 재고 넣을 수 없음

✅ 한 줄 정리

🌟 FOREIGN KEY의 핵심 역할 - 데이터 무결성(Data Integrity) 보장

반응형

WMS의 기초가 되는 데이터베이스 설계(Database Schema Design)

WMS(Warehouse Management System, 창고관리시스템)

"어떤 물건이 어디에 있고, 어디로 가야 하는가?" 를 관리하는 시스템
창고 안으로 들어오는 물건(입고)과 나가는 물건(출고)의 정보를 체계적으로 관리

주요 기능

  • 입고 / 출고 관리
  • 재고 위치 최적화 (Slotting)
  • 유통기한 관리
  • 재고 실사

개발 관점에
바코드, RFID 등을 활용해 재고 정확도를 100%에 가깝게 유지하고, 동선을 효율화하는 로직이 중요
목표: 재고 가시성 확보물류 비용 절감

상품(Product) 테이블 설계

CREATE TABLE products (
    prod_id  INT AUTO_INCREMENT PRIMARY KEY,  -- 숫자, 자동 증가 (1, 2, 3...)
    barcode  VARCHAR(50),                      -- 바코드 번호 (최대 50자)
    prod_name VARCHAR(100)                     -- 상품명 (최대 100자)
);

🛡️ PRIMARY KEY (기본키) VS 💎 UNIQUE (고유키)

"중복을 허용하지 않는다"

PRIMARY KEY (기본키) UNIQUE (고유키)
개수 테이블당 1개만 가능 여러 개 설정 가능
NULL 허용 ❌ Not Null ✅ Null 허용
용도 행(Row)을 대표하는 식별자 아이디, 이메일, 전화번호 등
비유 조직의 유일무이한 두목 행동대장

📏 CHAR (고정 길이) VS 🌊 VARCHAR (가변 길이)

CHAR(10) VARCHAR(10)
저장 방식 3글자 넣어도 10칸 전부 차지 (나머지는 공백) 3글자 넣으면 3칸만 차지
공간 효율 낮음 (낭비 발생) 높음 (알뜰살뜰)
검색 속도 약간 더 빠름 크기 계산이 필요해 상대적으로 느림
용도 주민번호, 성별(M/F), 국가코드 이름, 주소, 게시글 제목
WMS 예시 창고 구역 코드 (길이가 고정된 데이터) 상품명 (길이가 제각각)

길이가 항상 일정하면 CHAR, 길이가 들쭉날쭉하면 VARCHAR

재고(Inventory) 테이블 설계

CREATE TABLE inventory (
    inv_id        INT AUTO_INCREMENT PRIMARY KEY,
    barcode       VARCHAR(50),
    quantity      INT DEFAULT 0,          -- 숫자로 지정하고 기본 값은 0 
    location_code VARCHAR(20),            -- 창고 내 위치 (예: A-1-1)
    FOREIGN KEY (barcode) REFERENCES products(barcode)
    -- inventory의 barcode는 반드시 products 테이블에 존재해야 한다
);

⛓️ FOREIGN KEY와 데이터 무결성

FOREIGN KEY(외래키)는 부모-자식 관계를 통해 데이터 무결성을 보장

products 테이블 (부모)
    └── inventory 테이블 (자식)

inventory에 barcode를 입력하려고 할 때 -> products 테이블 barcode 자동으로 검문
부모 테이블 products에 존재하지 않는 barcode면 에러처리

ON DELETE / ON UPDATE 옵션

부모 테이블 products에서 바코드를 삭제하거나 수정하려고 하면 ??

옵션 동작
RESTRICT (기본값) 부모의 삭제와 수정을 막음
CASCADE 부모-> 삭제/수정, 자식-> 함께 삭제/수정
SET NULL 부모가 삭제되면 자식의 해당 컬럼을 NULL로 처리

사용 예시

FOREIGN KEY (barcode) REFERENCES products(barcode)
    ON DELETE CASCADE   -- 부모(products) 삭제 시 자식(inventory)도 삭제
    ON UPDATE RESTRICT  -- 부모(products) 수정 불가 ❌
);

FOREIGN KEY 설정 실패

🚨 에러 메시지

inventory 테이블을 만드는데 애러발생

Failed to add the foreign key constraint.
Missing unique key for constraint 'inventory_ibfk_1' in the referenced table 'products'
-> 참조하려는 부모(products)의 바코드에 'UNIQUE(유일함)' 보장이 없어서 연결할 수 없음

원인 분석

"참조할 대상(부모)은 반드시 유일해야 한다."

FOREIGN KEY로 참조하려는 컬럼(products.barcode)에 UNIQUE 또는 PRIMARY KEY 설정이 안 되어 있다면
같은 바코드가 여러 행에 존재할 수 있음

해결 방법

ALTER TABLE products ADD UNIQUE (barcode);
-- products 테이블 수정할건데
-- barcode에 UNIQUE 규칙을 추가할거야

구조 확인

DESC products;

✅ 한 줄 정리

PRIMARY KEY: 테이블의 대표 식별자, 딱 1개, NULL 불가
UNIQUE: 중복 불가, 여러 개 설정 가능, NULL 허용
CHAR vs VARCHAR: 고정 길이 vs 가변 길이, WMS에선 구역 코드엔 CHAR, 상품명엔 VARCHAR
FOREIGN KEY: 부모-자식 관계로 데이터 무결성 보장
ON DELETE/UPDATE: CASCADE(연쇄), RESTRICT(차단), SET NULL(무효화)

반응형

'DB _ SQL' 카테고리의 다른 글

INSERT + FOREIGN KEY 에러 테스트  (0) 2026.02.25

VS Code에서 Spring Initializr로 프로젝트 만들기

  1. 명령 팔레트 열기
    • 윈도우: Ctrl + Shift + P
    • 맥: Cmd + Shift + P
  2. Spring Initializr: Create a Gradle Project... (또는 Maven Project) 검색 후 선택
  3. 엔터엔터 선택
    • Spring Boot 버전
    • Language: Java
    • Group Id, Artifact Id
    • Build Tool: Maven / Gradle
    • Dependencies: Spring Web, Spring Data JPA, H2 혹은 MySQL 등

💡 Spring Initializr에서 설정 다운로드

  • 설정들을 웹에서 선택 > GENERATE 버튼 > ZIP 다운로드 > VS Code에서 실행

Maven

  • Java 프로젝트용 빌드 도구
  • 라이브러리(의존성) 관리, 컴파일, 패키징(JAR/WAR) 등을 자동으로 수행
  • npm (JavaScript) = Maven (Java)

pom.xml

  • Maven 프로젝트의 설정 파일
  • ≈≈≈ package.json 와 비슷 (어떻게 빌드할지)
    ├── 프로젝트 정보 (이름, 버전 등)
    ├── 어떤 라이브러리를 쓸지 (dependencies)
    ├── 어떤 플러그인을 쓸지
    └── 빌드 방법 (빌드 라이프사이클 설정)
<!-- pom.xml 예시 일부 -->
<project>
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>demo</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <dependencies>
    <!-- 여기 안에 spring-boot-starter-web 같은 의존성들이 들어감 -->
  </dependencies>
</project>

의존성(라이브러리) 추가하는 방법

  • VS Code > 명령 팔레트 > Maven: Add a Dependency... 사용 (편의 기능)
  • pom.xml 직접 수정 (정석)
  • Maven Central Repository 에서 검색 후 XML 코드 복사, pom.xml 붙여넣기

빌드 명령: mvn clean install

mvn clean install ≈≈≈ npm install + 빌드까지 한 번에 돌린 느낌
// clean -> 이전 빌드 삭제
// 새로 빌드
// install -> 프로젝트 컴파일 + 테스트 + 패키징(JAR 생성)
// 로컬 저장소에 배포

 

  React(npm) Java(Maven)
라이브러리 정의 파일 package.json pom.xml
라이브러리 추가 방식 npm install *** <dependency>...</dependency> XML 추가
설치 및 반영 npm install mvn clean install
반응형

'Java _ Spring' 카테고리의 다른 글

JavaScript Map vs Java HashMap  (0) 2026.02.16
Spring Service DTO 실습: private final getter 정리  (0) 2026.02.16
Spring Service와 DTO 역할  (0) 2026.02.16

JavaScript Map vs Java HashMap

JavaScript: Map 기본 사용법

const userMap = new Map();

// 데이터 추가
userMap.set('name', '길동');
userMap.set('age', 19);

// 데이터 조회
userMap.get('name');  // '길동'

// 존재 확인 및 크기
userMap.has('name');  // true
userMap.size;         // 2

Java: HashMap 기본 사용법

Java 차이점: Map -> 인터페이스, HashMap -> Map을 구현한 클래스 -> 세트

// 타입 지정 + 구현체 선택 -> Map 타입인데  HashMap으로 만들겠다
Map<String, Object> userMap = new HashMap<>();
HashMap<String, Object> userMap = new HashMap<>();

// 데이터 추가
userMap.put("name", "길동");
userMap.put("age", 19);

// 데이터 조회
userMap.get("name");          // "길동"

// 존재 확인 및 크기
userMap.containsKey("name");  // true
userMap.size();               // 2 (메서드 호출)

메서드 비교

기능 JavaScript Java
추가 set(key, value) put(key, value)
조회 get(key) get(key)
존재 확인 has(key) containsKey(key)
크기 size (프로퍼티) size() (메서드)

반복문 비교

JavaScript: forEach 사용

const users = new Map();
users.set('user1', { name: '철수', age: 30 });
users.set('user2', { name: '영희', age: 25 });

users.forEach((value, key) => {
  console.log(key, value);
});

Java: for-each 사용

Map<String, User> users = new HashMap<>();
users.put("user1", new User("철수", 30));
users.put("user2", new User("영희", 25));

for (String key : users.keySet()) {
  System.out.println(key + ": " + users.get(key));
}

Spring Repository 패턴

public class StudentRepository {
  // 필드: 클래스 안에 선언된 변수
  // ⚛️ React -> useRef / useState처럼 호출마다 사라지지 않고 유지됨
  private final Map<String, Student> studentMap = new HashMap<>();

  public void save(Student student) {
    studentMap.put(student.getName(), student);
  }
}

save 함수 실행 과정

// 1. Repository 필드 선언 (컴포넌트 상태처럼 유지됨)
private final StudentRepository studentRepository;

// 2. Student 객체 생성 (저장할 실제 데이터)
Student student = new Student(
  studentInfoDto.getName(), 
  studentInfoDto.getAge(), 
  studentInfoDto.getAddress()
);

// 3. Repository에 저장
studentRepository.save(student);

✅ 한 줄 정리:

JavaScript -> Map 하나로 끝
Java -------> Map: (설계도) + HashMap: (실제 구현체), <Key타입, Value타입> 필수

반응형

Spring Service DTO 실습: private final getter 정리

프론트엔드 → 백엔드 전환 !

📦 DTO: Data Transfer Object

application/dto/StudentDto.java (데이터 상자)

public class StudentDto {
    //  String(타입) name(변수명)
    private String name;        // 문자열 학생 이름 변수
    private int studentNumber;  // 숫자 학번 변수

    public String getName() { // getter/setter
        return name;
    }    
    public void setName(String name) {
        this.name = name;  // 검증 로직 추가 가능
    }

    // Entity와 필드 다를 수 있음 (보안용)
    // 예: Entity엔 password도 있지만 DTO엔 제외
}

private -> 이 변수는 나만 쓸 거야!

  • 클래스 내부에서는 자유롭게 사용
  • 외부에서는 직접 접근 불가 (getter/setter로만)
  • 실수로 외부에서 함부로 바꾸는 것 방지 ✅

getter/setter 개념

public class StudentDto {
    private String name;  // ❌ 다른 파일에서 직접 접근 불가!

    // 👉 getter 함수: "name 값을 보여줘!"
    public String getName() {
        return name;
    }
}
StudentDto student = new StudentDto();
// student.name = "홍길동";  // ❌ 컴파일 에러!
String name = student.getName();  // ✅ 가능! getter로만 접근

⚛️ React

const [name, setName] = useState<string>("");  
name        // Getter 역할
setName()   // Setter 역할
const student = { name: "홍길동" };
console.log(student.name);  // ✅ 직접 접근 OK

Java: 직접 접근 불가 → 함수로만!
StudentDto student = new StudentDto();
student.getName()  // Getter ✅ 함수로만 접근
student.setName()  // Setter ✅ 함수로만 접근
React Java
student.name student.getName()
setName(value) student.setName(value)

application/service/StudentService.java

public class StudentService {
    // 접근제한 + 불변
    private final StudentRepository repo;

    public StudentDto create(StudentDto dto) {
        // 검증 로직
        if (dto.getName() == null) throw new IllegalArgumentException("이름 필수");

        Student entity = new Student(dto.getName(), dto.getStudentNumber());
        repo.save(entity);
        return new StudentDto(entity); // DTO 변환
    }
}

final -> 절대 바꾸지 않아!

  • 클래스 내부에서는 자유롭게 사용
  • 외부에서는 직접 접근 불가 (getter/setter로만)
  • 실수로 외부에서 함부로 바꾸는 것 방지 ✅

private vs final 차이 비교

final -> 값 바꾸기 방지
private -> 직접 접근 방지

키워드 목적 비유 코드 예시
private 외부에서 직접 접근 차단 "집 안 물건, 문 잠금" student.name = "김철수"
final 한 번 설정 후 값 변경 차단 "접착제로 붙인 라벨" repo = 다른Repo

public (없거나 public일 때)

외부에서 직접 접근 가능!

public class StudentDto {
    public String name;        // ✅ 직접 접근 가능
    public int studentNumber;  // ✅ 직접 접근 가능
}

StudentDto student = new StudentDto();
student.name = "홍길동";          // ✅ OK!
System.out.println(student.name); // ✅ OK!

✅ 한 줄 정리:

private -------> 안 보여줄래 (캡슐화)
final ---------> 바꾸지 않을래 (불변성)
getter/setter -> 필요할 때만 보여줄래
Service -------> 검증 + 비즈니스 로직 처리

반응형

'Java _ Spring' 카테고리의 다른 글

VS Code + Spring Boot CRUD 만들어보기  (0) 2026.02.17
JavaScript Map vs Java HashMap  (0) 2026.02.16
Spring Service와 DTO 역할  (0) 2026.02.16

Spring Service와 DTO 역할: 계층별 폴더 구조 정리

Service와 DTO 핵심 역할

Service

  • 업무 규칙, 비즈니스 흐름 담당
  • 예외처리, 데이터 저장/조회, 입력값 검증 로직

DTO (Data Transfer Object)

  • "데이터 상자" 역할
  • 계층 간 데이터 운반 (Controller ↔ Service)
  • Entity와 분리해 보안/성능 최적화

📂 권장 폴더 구조

src/main/java/com/example/

├── application/ # 유즈케이스 (기능별 로직)
│ ├── service/ # Service (선생님: 어떻게 처리할지 담당)
│ └── dto/ # DTO (이름,나이,번호 정보만 전달)

├── domain/ # 도메인 규칙, Entity (학교)
│ └── entity/ # 이름/번호로 구분되는 핵심 데이터 (사람,반)
└── repository/ # 데이터 저장/조회 (DB 접근)

반응형

html-react-parser 안전하게 HTML 렌더링

서버에서 받아온 HTML 태그가 포함된 문자열을 화면에 그대로 렌더링해야 할 때 -> dangerouslySetInnerHTML 사용한다

dangerouslySetInnerHTML

const App = () => {
const htmlString = `<p>!! 나는 <strong>위험한</strong> 속성이다 !!</p>`;

  return (
      <div dangerouslySetInnerHTML={{ __html: htmlString }} />
  );
};

export default App;

dangerouslySetInnerHTML 사용을 지양해야 하는 이유

React는 XSS(Cross-Site Scripting) 공격을 방지하기 위해 렌더링 과정에서 HTML 이스케이프(Escape) 처리를 한다
HTML이나 JavaScript 코드가 컴포넌트에 삽입되는 것을 제한하는 React의 보호 기능을 무시하고
dangerouslySetInnerHTML를 사용하면 강제로 HTML 직접 삽입

<script>공격!</script>

React 기본 기능은 위 스크립트를 문자열로 받지만 "< script >공격!< /script >"
⚠️ dangerouslySetInnerHTML를 쓰면 문자열로 받지 않고 script로 들어와 코드가 실행된다
이런 위험이 있으므로 말그대로 '위험(dangerous)' 해진다

html-react-parser 사용

html-react-parser
dangerouslySetInnerHTML를 사용하다가 보안상의 이슈로 html-react-parser 라이브러리 사용

✅ html-react-parser 장점

  • 컴포넌트 변환: 문자열 주입❌, HTML을 리액트 엘리먼트로 파싱하여 변환
  • 안전한 렌더링: 필요한 경우 특정 태그를 필터링하거나 리액트의 라이프사이클 안에서 안전하게 렌더링을 제어 가능

📦 라이브러리 설치

npm install html-react-parser
npm install @types/html-react-parser

yarn add html-react-parser
yarn add @types/html-react-parser

간단 사용법

parse() 함수를 사용하여 HTML을 동적으로 렌더링할 수 있다

import parse from 'html-react-parser';

const App = () => {
  const htmlString = `
    <div>
      <h1>!! Hello !! World !!</h1>
    </div>
  `;

  return (
    <div>
      {parse(htmlString)}
    </div>
  );
};
반응형

'React' 카테고리의 다른 글

[React] clsx 라이브러리  (0) 2024.09.19
[React] useOutletContext로 props 전달  (0) 2024.08.30
[React] Cypress로 E2E 테스트  (0) 2023.11.14

클래스 이름을 조건부로 병합하거나 동적으로 관리할때 사용

 

clsx

A tiny (239B) utility for constructing className strings conditionally.. Latest version: 2.1.1, last published: 5 months ago. Start using clsx in your project by running `npm i clsx`. There are 10443 other projects in the npm registry using clsx.

www.npmjs.com

라이브러리 install

npm install clsx

yarn add clsx

 

import clsx from 'clsx';

function Button({ isPrimary, isDisabled, onClickEvent, text }) {
  const className = clsx('btn', {
    'btn-primary': isPrimary,
    'btn-disabled': isDisabled,
  });

  return(
  	<button
      type="button"
      onClick={onClickEvent}
      className={className}
    >
      {text}
    </button>
);
}

isPrimary가 true -> btn-primary 클래스 추가

isDisabled가 true -> btn-disabled 클래스가 추가

<li className={clsx({ [style.show]:
				(step > 1 && step < 5) || step === 9 },
				{ [style.selected]: step > 2 })}
>
  <button type="button">
    <span className={style.icon_box}>
      <Icon name="calendar_line" color="purple" />
    </span>
    <span className={style.title}>시작</span>
    <span className={style.text_box}>
      <Icon name="arrow_right" color="black" size={20} />
    </span>
  </button>
</li>

style.show가 적용되는 조건 -> step > 1 && step < 5 이거나 step === 9 일때,

step이 3 이상일 때 -> style.selected 클래스가 적용

반응형

'React' 카테고리의 다른 글

[React] html-react-parser  (1) 2024.09.20
[React] useOutletContext로 props 전달  (0) 2024.08.30
[React] Cypress로 E2E 테스트  (0) 2023.11.14

[React] useOutletContext: 중첩 라우트(Outlet)에서 자식에게 props 전달하기

부모 레이아웃의 상태를 자식 컴포넌트에게 전달해야 할 때
Props Drilling을 피하고 깔끔하게 데이터를 공유할 수 있는 useOutletContext 사용
useOutletContext -> "특정 라우트 트리(Route Tree) 안에서의 미니 전역 상태 관리"
📝 Props Drilling
부모의 상태를 저~~ 밑에 있는 자식에게 전달하기 위해 여러 단계를 거치는 현상


라우터 설정 (Router Setup)

import { createBrowserRouter } from 'react-router-dom';

export default createBrowserRouter([
  {
    path: '',
    element: [<MainLayout />],
    errorElement: <ErrorPage />,
    children: [
      {
        path: '/home',
        element: <Home />,
      },
      {
        path: '/work',
        element: <WorkLayout />,
        children: [
          { path: '', element: <WorkList /> },
          { path: ':workId', element: <WorkDetail /> },
        ],
      },
    ],
  },
]);

부모 컴포넌트: 데이터 전달 (Outlet context)

WorkLayout.tsx

태그에 context 속성을 사용하여 전달하고 싶은 데이터 넣는다

import { Outlet } from 'react-router-dom';

function WorkLayout() {
    const [workList, setWorkList] = useState<workListItem[]>([]);
    return (
        <>
            <Outlet context={{ workList }} />
        </>
    );
}

export default WorkLayout;

자식 컴포넌트: 데이터 사용 (useOutletContext)

WorkList.tsx

useOutletContext 훅을 사용하여 부모가 보낸 데이터를 가져온다

import { useOutletContext } from 'react-router-dom';

function WorkList() {
    const [workList, setWorkList] = useOutletContext<workListItem[]>([]);
    return (
        <div>
            {workList?.map((item, index) => (
                <p key={index}>
                    {item}
                </p>
            ))}
        </div>
    );
}
export default WorkList;

✅ 한 줄 정리:

Props Drilling: 나는 안 쓰는데 내 자식 주려고 보따리 들고 있는 상태
useOutletContext: 부모가 던져준 보따리를 자식이 필요할 때 바로 꺼내 쓰는 도구

반응형

'React' 카테고리의 다른 글

[React] html-react-parser  (1) 2024.09.20
[React] clsx 라이브러리  (0) 2024.09.19
[React] Cypress로 E2E 테스트  (0) 2023.11.14
pip install -r requirements.txt

난 그저 flask 패키지 설치하고 싶었을 뿐인데

꽤 오랜시간 해결하지 못하고 있었다

에러

error: externally-managed-environment

원인

원인은.. 모르겠다...

해결 방안

 

How do I solve "error: externally-managed-environment" every time I use pip 3?

Error message: error: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the pack...

stackoverflow.com

 

python -m pip config set global.break-system-packages true

pip의 설정을 변경하여 시스템 패키지를 깨뜨릴 수 있는 동작을 허용하는 옵션을 설정

global.break-system-packages: 시스템 패키지와의 충돌을 허용할지 여부를 결정하는 옵션

시스템 패키지 관리자(apt, yum)와 pip 사이의 충돌을 방지하기 위해 사용된다

반응형

+ Recent posts