Spring Framework (13) DB 모델링 및 인터셉터 - Day 60

jaehoon ahn·2025년 12월 12일

Spring boot

목록 보기
11/20
post-thumbnail

CHAP01. DB모델링개요

  • 모델링
    • 말 그대로 모델을 만드는 작업을 뜻함. 즉, 현실 세계를 단순화 시켜 표현하는 기법
  • 엔티티 (== table)
    • 업무의 관심 대상이 되는 정보를 갖고 있거나 그에 대한 정보를 관리할
      필요가 있는 유형, 무형의 사물(개체) (유형, 무형, 문서, 이력, 코드)

  • 속성 (== column)
    • 엔티티에서 관리해야 할 최소 단위 정보 항목(관심이 있는 항목)을 말하며
      엔티티는 하나 이상의 속성을 포함 (기본, 유도, 설계)

  • 인스턴스 (== row)
    • 엔티티의 속성으로 실제로 구현된 하나의 값

  • 엔티티 조건
    1. 업무의 관심 대상이 되는 사물이어야 된다.
    2. 마땅한 속성을 소유해야 된다.
    3. 두 개 이상의 인스턴스를 소유해야된다.
  • 속성 명명규칙
    1. 속성의 의미가 분명히 드러나게 작성할 것 (명확)
    2. 해당 업무에서 사용하는 이름 부여할 것
    3. 서술식(수식어, 소유격) X, 약어 X
    4. 엔티티에서 유일하게 식별 가능하도록 지정할 것 (중복 X)
  • 관계
    • 두 엔티티 사이의 관련성을 나타냄 (관계는 데이터를 매개로 한 업무의 흐름과 데이터의 흐름을 규명함)

  • 카디널리티
    • 각 엔티티에 속해 있는 인스턴스들 간에 수적으로 어떤 관계에 있는지를 나타냄
    • 종류로는 1:1, 1:N, M:N의 관계가 있다.

  • 주식별자(PK)
    • 엔티티 내 각 인스턴스를 구별하는 기준이 되는 속성

  • 외래식별자(FK)
    • 관계가 있는 엔티티 간의 연결고리 역할을 하는 속성

  • 개념적 설계
    • 요구분석 단계에서 정의된 핵심 개체와 그들 간의 관계를 바탕으로 ERD를 생성하는 과정
  • 논리적 설계
    • 개념 설계에서 추상화된 데이터를 구체화하여 개체, 속성을 테이블화하고 상세화 하는 과정
      (상세화 과정 : 정규화, 식별자 확정, M:M 관계 해소, 참조 무결성 규칙 정의)
  • 물리적 설계
    • 논리적 설계의 산출물인 ERD의 요소들을 관계형 데이터베이스의 요소들로 전환하는 과정

CHAP02_1. 개념적모델링_Entity 도출하기

CHAP02_2. 개념적모델링_ERD

ERDCloud

ERD란?

  • ERD란, 개체 관계도라고도 불리며 요구분석사항에서 얻어낸 엔티티와 속성들을
    그림으로 그려내어 그 관계를 도출한 것

ERD 표기법 (관계)

  • 엔티티 간의 부모-자식 관계
    • 상호 관계가 있는 두 엔티티 중에서 어느 쪽의 정보가 먼저 생성이 되는가에 따라 결정
    • 부모 엔티티의 정보가 있어야지만 존재할 수 있는 것이 자식 엔티티
  • 참여도
    • 참여도에는 필수(mandatory), 선택(optional) 두 가지로 존재
    • 어떤 기준이 되는 엔티티가 있을 때 반드시 대응되는 엔티티가 존재해야 한다면 필수,
      존재 할 수도, 하지 않을 수도 있다면 선택
  • 카디널리티
    • 두 개의 엔티티 간 관계에서 엔티티에 속해 있는 인스턴스들을 수적으로 표현한 것
    • 인스턴스가 1개와 대응된다면 ‘ ‘ 로 표시 다수와 대응된다면 ‘ ‘ 로 표시
  • 카디널리티와 참여도에 따른 관계의 종류

  • 1:1 관계
    • X에 속하는 하나의 인스턴스는
      Y에 속하는 하나의 인스턴스에만 연결되며,
    • Y에 속하는 하나의 인스턴스도
      X에 속하는 하나의 인스턴스에만 연결될 때

  • 1:N 관계
    • X에 속하는 하나의 인스턴스는
      Y에 속하는 여러 인스턴스에 연결되며,
    • Y에 속하는 하나의 인스턴스는
      X에 속하는 하나의 인스턴스만 연결될 때
  • M:N관계
    • X에 속하는 한 인스턴스는 Y에 속하는 여러 인스턴스와 연결될 수 있으며,
      Y에 속하는 한 인스턴스도 X에 속하는 여러 인스턴스와 연결될 수 있을 때
    • M:N 관계는 덜 완성된 모습으로 데이터 구조에 있어서 어떠한 실제적 방법으로도 구현이
      불가능하다. (따라서 M:N관계는 해소해 주어야 한다.)

⇒ 학생별수강과목성적 = 해소 테이블

⇒ 중간에 해소 테이블을 추가함으로써 M:N 관계를 1:N 관계로 바꿔준다

  • 식별관계
    • 부모의 PK를 자식이 PK로 가짐
    • 즉, 자식이 독립적으로 존재할 수 없게 되는 것
      • ⇒ FK이면서 PK역할을 함 → 이를 PFK라고 부름
  • 비식별관계
    • 부모 PK를 자식이 FK로 가짐
    • 독립적으로 자식테이블이 존재할 수 있다.
      • 즉, FK를 지우면 되기 때문

CHAP03_논리적모델링

논리적 모델링 단계

  • 개념적 모델링을 끝내고 정규화 과정을 통해서 테이블을 상세화함

정규화

정규화

  • 관계형 데이터베이스에서 데이터를 구조화 하는 작업

정규화의 목적

  • 데이터의 중복을 방지하고 보다 효율적으로 데이터를 저장하기 위함
  • 삽입, 삭제, 갱신 이상의 발생 가능성을 줄이기 위함.

⇒ 나의 데이터 베이스에 이상한 데이터가 들어오지 못하게 완전무결한 상태를 만드는 것

정규화 예시

  • 원자값
    • 더이상 쪼갤 수 없는 단위

제 1정규형

  • 원자값을 가져야 한다
    • 한 셀에 하나의 값만 들어가야 한다.

제 2정규형

  • 부분함수적종속제거

  • 복합키
    • 둘중에 하나라도 다르면 다른 것
    • 둘다 같아야 같은 것
  • 학번, 과목으로 학생을식별할 수 있는 것인데, 학번만으로도 학생을 식별할 수 있음
    • 이를 부분적으로 종속된다는 의미

제 3정규형

  • 이행적함수적종속제거

  • 학번을 알면 학과 이름을 알 수 있는 형태임
    • 즉, 학번은 곧 학과 이름이 된다는 것
  • 테이블을 나누고 나면
    • 학번 1번이 컴퓨터공학과가 되는게 아님

핵심 정리

정규형 정리

  • 제 1정규형
    • 칸마다 1개 값만 작성
  • 제 2정규형
    • 기본키 전부에 의존해야한다
  • 제 3정규형
    • 간접적으로 우회해서 데이터를 알게하지 말고, 직접적인 관계만 남기기

정규화의 목적

  • 중복 제거
  • 수정 용이
  • 검색 용이
  • 데이터 일관성

erdcloud

기본설정

⇒ domain 빼고 다 선택

테이블 생성

  • 단축키 N

  • 노란색은 PK

⇒ 왼쪽은 식별할 수 있는 이름, 오른쪽은 실제 테이블명
  • 테이블 정의

  • EXPORT

Interceptor

  • client가 보낸 요청을 Dispatcher Servlet에게 전달될 때 중간에 가로채서 어떤 요청으로 보내줄 지 결정해주는 것
  • 컨트롤러에 보낼 때, 가로채서 다른일을 할 수도 있음
  • view를 선택할때도 응답화면이 완성된 다음에도 코드를 추가하겠다, 기능을 추가하겠다는 일을 해주는 것
  • ⇒ 즉, 2, 3, 5, 6에서 인터셉트가 발생함

Interceptor 개념

Interceptor : 요청/응답/뷰 완성 후 이때, 무언가(요청, 응답,)를 가로채는 객체 (Spring에서 지원)
 *  
 * * HandlerInterceptor 인터페이스를 상속받아서 구현해야 한다
 * - preHandle(전처리) 3번 : pre는 previous, DispatcherServlet -> Controller 사이 수행
 * - postHandle(후처리) 5번 : Controller -> DispatcherServlet 사이에서 수행
 * - afterCompletion (뷰 완성후) 6번: ViewResolver -> DispatcherServlet에게 
 * "이런 화면으로 응답해줘!" 라고 할 때 수행되는 것

BoardTypeInterceptor.java

package edu.kh.project.common.interceptor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import edu.kh.project.board.model.service.BoardService;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

/**
 * Interceptor : 요청/응답/뷰 완성 후 이때, 무언가(요청, 응답, 뷰)를 가로채는 객체 (Spring에서 지원)
 * 
 * * HandlerInterceptor 인터페이스를 상속받아서 구현해야 한다 - preHandle(전처리) 3번 : pre는
 * previous, DispatcherServlet -> Controller 사이 수행 - postHandle(후처리) 5번 :
 * Controller -> DispatcherServlet 사이에서 수행 - afterCompletion (뷰 완성후) 6번:
 * ViewResolver -> DispatcherServlet에게 이런 화면으로 응답해줘 라고 할 때 수행되는 것
 * 
 */
public class BoardTypeInterceptor implements HandlerInterceptor {
	// HandlerInterceptor를 상속받았는데 왜 빨간줄이 안뜨는지
	// => 내부적으로 보면 추상메소드처럼 안보임
	// => default를 붙이면 정의를 할 수도 있음
	// => 인터셉트에서 빨간줄이 안뜨는 것, 즉 추상메소드 형태가 아니기 때문

	// 전처리 : 요청이 Controller로 들어오기 전 실행되는 메서드

	@Autowired // 의존성 주입(DI) -> BoardService타입과 일치하거나, 상속관계인 객체(Bean)을 주입받음
	private BoardService service;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
		// DB에 접근해서 board_type을 가져와서 화면에 뿌려줄 것

		// boardType을 DB에서 얻어오기
		// boardTypeList 형태로 가져오기
		// 모든 페이지를 돌아다니면서 화면상에 boardType이 뿌려져야 함
		// 서버가 켜지자마자 db에서 데이터를 가져와서 뿌려줘야하므로, application scope에 저장
		// application scope :
		// - 서버 시작 ~ 종료 시 까지 유지되는 Servlet 내장 객체
		// - 서너 배에 딱 한개만 존재하는 객체다 --> 모든 클라이언트가 공용으로 사용

		// application scope 객체 얻어오기
		ServletContext application = request.getServletContext();

		// application scope에 "boardTypeList"가 없을 경우
		if (application.getAttribute("boardTypeList") == null) {
			// boardTypeList 조회 서비스 호출
			List<Map<String, Object>> boardTypeList = service.selectBoardTypeList();
			// 첫 요청이 들어와서 application에 실어두면, 두번 째부터는 실행을 안해줘도 됌

			// 조회 결과를 application scope에 추가
			application.setAttribute("boardTypeList", boardTypeList);

		}

		return HandlerInterceptor.super.preHandle(request, response, handler);
	}

	// 후처리 : 요청이 처리된 후, 뷰가 렌더링 되기 전에 실행되는 메서드
	// 즉, 응답을 가지고 DispatcherServlet에게 돌아가기 전
	// 전처리에서 모든 작업이 끝났기 때문에, 후처리에서 할게 없다.
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			ModelAndView modelAndView) throws Exception {
		HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
	}

	// 뷰 완성 후 : 뷰 렌더링이 끝난 후 실행되는 메서드
	// 뷰를 다 뿌리고나서 할게 없기 때문에 놔두면 된다.
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception {
		HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
	}

}

InterceptorConfig

package edu.kh.project.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import edu.kh.project.common.interceptor.BoardTypeInterceptor;

@Configuration // 서버가 켜지면 Bean으로 등록되고, 내부 메서드가 수행

// 인터셉터가 어떤 요청을 가로챌지 설정하는 클래스

public class InterceptorConfig implements WebMvcConfigurer {
	// WebMvcConfigurer : registry에서 사용, 즉 어떤 경로에서 사용하겠다는 의미
	// => FileConfig에서 사용함

	@Bean // 인터셉터 클래스 Bean 등록
	// 개발자가 수동으로 만든 객체지만, 관리는 Spring Container가 수행하겠다는 의미
	public BoardTypeInterceptor boardTypeInterceptor() {
		return new BoardTypeInterceptor();
	}

	// 동작할 인터셉터 객체를 추가하는 메서드
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		// Bean으로 등록된 BoardTypeInterceptor를 얻어와서 등록하기위한 절차
		registry.addInterceptor(boardTypeInterceptor()).addPathPatterns("/**").excludePathPatterns("/css/**");

		// addPathPatterns("/**")
		// /** : / 이하 모든 요청을 의미, 모든 요청을 가로채서, boardTypeInterceptor가 돌아가게끔 할 것

		// excludePathPatterns("/css/**", "/js/**", "/images/**", "/favicon.ico")
		// 가로채고싶지 않은 요청 정의

	}

}

⇒ log.debug로 리스트를 가져온 것을 확인할 수 있다.

결과 화면

0개의 댓글