영속성 컨텍스트


JPA에서 가장 중요한 2가지

  1. 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
    • DB를 어떻게 설계하고, 객체를 어떻게 설계해서 중간에서 JPA를 어떻게 사용해서 mapping 할 것 인지
  2. 영속성 컨텍스트
    • 실제 JPA가 내부에서 어떻게 동작하는지 
    • 영속성 컨텍스트를 이해하면 JPA가 내부적으로 어떻게 돌아가는지 이해할 수 있을 것

 

 

Entity Manager Factory와 Entity Manager

예를들어 웹 어플리케이션을 개발할 때 사용자의 요청에 따라서 EMF를 통해 EM을 생성합니다. EM은 내부적으로 DB 커넥션을 통해 DB를 사용합니다.

 

그렇다면 영속성 컨텍스트는 무엇일까??

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • "엔티티를 영구 저장하는 환경"이라는 뜻
  • EntityManager.persist(entity);
    • 해당 코드의 의미는 DB에 저장되지만 DB에 저장한다는 의미가 아닌 영속성 컨텍스트에 저장한다는 의미입니다. (아직 어떤 느낌인지 모르겠습니다... ?!?!?!?)   => 실제 확인해보니 em.persist할 때 쿼리문이 나가지 않고 쓰기 지연 SQL 저장소에 쿼리가 저장되고 commit이나 flush를 할 경우 DB에 쿼리문을 보내어 DB를 동기화 시켜줌


EM? 영속성 컨텍스트?

  • 영속성 컨텍스트는 논리적인 개념
  • 눈에 보이지 않기때문에 이해하기 쉽지 않다.
  • EM을 통해 영속성 컨텍스트에 접근

 

  • EM과 영속성 컨텍스트는 J2SE 환경에서 1:1로 매핑됩니다.
  • 스프링 프레임워크같은 환경에선 N:1로 매핑되는데 해당 부분은 다음에 이해하는 것을 권장

 

Entity의 생명주기

  • 비영속 (new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속 (managed)
    • 영속성 컨텍스트에 관리되는 상태
  • 준영속 (detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed)
    • 삭제된 상태

 

비영속

객체만 생성하고 em에 persist하지 않은 상태

 

 

영속

객체를 EM에 저장한 상태 (트랜잭션에 commit하지 않았기때문에 DB에는 올라가지 않는다.)

 

 

준영속, 삭제

 

 

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(Identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

애플리케이션과 DB 사이에 중간 계층이 있음으로서 위와같은 이점을 얻을 수 있다. 영속성 컨텍스트 뿐만 아니라 중간에 하나를 더 두면서 얻는 이점은 버퍼링, 캐시 등과 같은 이점을 얻을 수 있다.

 

 

엔티티 조회, 1차 캐시

이렇게 @Id로 member1이 있고, Entity로 객체가 있음으로서 얻는 이점은 1차 캐시에서 조회할 때 있습니다.

 

1차 캐시에서 조회

em.persist로 1차 캐시에 저장이 되었다고 가정한뒤, 조회한다고 합시다. 조회를 할 경우 DB를 먼저 조회하는 것이 아닌 1차 캐시에서 먼저 조회를 하게 됩니다. 그때 1차 캐시에서 원하는 객체가 있는 경우 DB를 조회하지 않고 바로 객체를 반환 받을 수 있습니다.

 

데이터베이스에서 조회

조회할 때, 1차 캐시에 없는 경우 DB를 조회하게 됩니다. 이때, DB에 원하는 객체가 있는 경우 1차 캐시에 저장한 뒤, 1차 캐시에 있는 객체를 반환하게 됩니다.

 

EM을 사용함으로서 위와같은 1차캐시의 이점을 얻을 수 있지만, 현업자분의 말로는 큰 이점은 없다고 합니다. 이유는 EM은 DB 트랜잭션 단위로 만들고 트랜잭션 종료시 종료됩니다. 이 말은 고객의 요청에 따라 비즈니스가 끝나게 될 경우 영속성 컨텍스트를 지운다는 뜻 입니다. 이렇듯 굉장히 찰나에 순간에만 이점을 얻을 수 있기 때문에 사실상 큰 이점은 없다고 합니다.

 

 

영속 엔티티의 동일성 보장

영속성 컨텍스트에서 같은 객체를 불러와 비교해보면 자바 컬렉션에서 똑같은 레퍼런스 두개를 가져와 비교할 때 같다고 나오는 것 처럼 같다고 나옵니다.

 

 

엔티티 등록

트랜잭션을 지원하는 쓰기 지연

트랜잭션 commit을 하기전까진 1차 캐시에 변경된 내용은 쓰기 지연 SQL 저장소에 쿼리문이 차곡차곡 쌓이게 됩니다. Queue와 비슷한 형식인 것 같습니다. 후에 트랜잭션 commit을 한 경우 쌓인 쿼리문은 flush가 되며 DB에 저장하게 됩니다.

 

 

 

엔티티 수정

변경 감지

데이터를 수정할 때 객체를 가져와 수정 후 update나 persist를 다시 해줘야하는 것 아닌가라는 의문이 생길 수 있습니다. (저도 그랬습니다) 하지만, JPA를 사용하는 목적은 자바에서 컬렉션을 사용하는 것처럼 사용하기 위함 입니다.  컬렉션을 수정할 때, 수정 후 다시 데이터를 집어넣는 작업을 해야하는가를 생각해보면 그렇지 않습니다. 그렇기때문에 JPA에서도 데이터를 수정 후 집어넣는 작업을 하지 않아도 됩니다. 이것이 가능한 이유는 JPA에서 Dirty Checking을 하기 때문입니다. 

 

 

변경 감지 (Dirty Checking)

JPA는 DB 트랜잭션 커밋하는 시점에 내부적으로 flush()가 호출됩니다. 후 엔티티와 스냅샷을 비교합니다. 1차 캐시 안에는 사실 PK값, 객체 뿐만아니라 스냅샷도 있습니다. 스냅샷은 값을 읽는 최초 시점을 저장한 객체입니다. 그렇기 때문에 flush()가 호출 되어 엔티티와 스냅샷을 비교할 때 최초 시점에 객체와 commit된 객체의 값을 비교 후 변경된 데이터에 대한 update 쿼리문을 자동으로 생성하여 쓰기지연 SQL 저장소에 생성하여 저장합니다. 이러한 내부적인 로직때문에 수정할 때 persist나 update를 안해줘도 됩니다.

 

 

엔티티 삭제

위의 메커니즘과 같이 remove(객체)를 하여 삭제할 수 있습니다. 

 

 

플러시 


위 내용을 보다보면 flush라는 단어를 보았을 것 입니다. 

 

flush

영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

 

 

플러시 발생

  • 변경 감지 (Dirty Checking)
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)

 

 

영속성 컨텍스트를 플러시하는 방법

  • 직접 호출
    • em.flush()
  • 플러시 자동 호출
    • 트랜잭션 커밋
    • JPQL 쿼리 실행

 

플러시 정리

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
  • 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화하면 됨

 

 

 

준영속 상태


준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 엔티티가 영속성 컨텍스트에서 분리 (detached)
  • 영속성 컨텍스트가 제공하는 기능을 사용 못함

 

준영속 상태로 만드는 방법

  • em.detach(entity)
    • 특정 엔티티만 준영속 상태로 전환
  • em.clear()
    • 영속성 컨텍스트를 완전히 초기화
  • em.close()
    • 영속성 컨텍스트를 종료

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

H2 데이터베이스는 이미 설치했기 때문에 넘어가도록 하겠습니다.

 

아래 링크에 H2 데이터베이스 설치했던 내용이 담겨있는 내용이 있습니다.

 

[JPA 활용 1] 1. 프로젝트 환경설정

프로젝트 환경설정 프로젝트 생성 라이브러리 살펴보기 View 환경 설정 H2 데이터베이스 설치 JPA와 DB 설정, 동작확인 프로젝트 생성 스프링 부트 스타터(https://start.spring.io/) 사용 기능 : web, thymelea

qazyj.tistory.com

 

 

 메이븐 소개

  • https://maven.apache.org/
  • 자바 라이브러리, 빌드 관리
  • 라이브러리 자동 다운로드 및 의존성 관리
  • 최근에는 그레들(Gradle)이 점점 유명 (저도 그레들로 많이 사용했습니다.)

 

 

프로젝트 생성

  • 자바 8 이상(8 권장)
  • 메이븐 설정
    • groupId : jpa-basic
    • artifactId : ex1-hello-jpa
    • version : 1.0.0

intelliJ를 킨 후 new Project에서 maven을 선택한 뒤 위의 그림대로 작성 후 프로젝트를 생성해주시면 됩니다.

 

코드추가 (pom.xml)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>jpa-basic</groupId>
    <artifactId>ex1-hello-jpa</artifactId>
    <version>1.0.0</version>
    <dependencies>
        <!-- JPA 하이버네이트 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.3.10.Final</version>
        </dependency>
        <!-- H2 데이터베이스 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.199</version>
        </dependency>
    </dependencies>
</project>

 

 

JPA 설정하기 - persistence.xml

jpa를 쓰기위해선 몇가지 설정을 해야합니다.
  • JPA 설정 파일
  • /META-INF/persistence.xml 위치 (표준 위치가 정해져 있습니다.)
  • persistence-unit name으로 이름 지정 (JPA이름, DB마다 만든다고 합니다.)
  • javax.persistence로 시작 : JPA 표준 속성
  • hibernate로 시작 : 하이버네이트 전용 속성

 

코드 추가 (METE-INF/persistence.xml)

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <!--<property name="hibernate.hbm2ddl.auto" value="create" />-->
        </properties>
    </persistence-unit>
</persistence>

 

 

데이터베이스 방언

  • JPA는 특정 데이터베이스에 종속 X
  • 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다름
    • 가변 문자 : MySQL은 VARCHAR, Oracle은 VARCHAR2
    • 문자열을 자르는 함수 : SQL 표준은 SUBSTRING(), Oracle은 SUBSTR()
    • 페이징 : MySQL은 LINIT, Oracle은 ROWNUM
  • 방언 : SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능
  • hibernate.dialect 속성에 지정
    • H2 : org.hibernate.dialect.H2Dialect
    • Oracle 10g : org.hibernate.dialect..Oracle10gDialect
    • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect
  • 하이버네이트는 40가지 이상의 데이터베이스 방언 지원

 

애플리케이션 개발

 

JPA 구동 방식

  1. Persistence라는 클래스를 통해 설정 정보를 조회합니다.
  2. 설정 정보를 조회해서 EntityManagerFactory라는 클래스를 만듭니다. 
  3. EntityManagerFactory 클래스에서 필요할 때마다 EntityManager를 찍어냅니다.

 

 

실습 - JPA 동작 확인

  • JpaMain 클래스 생성
  • JPA 동작 확인

 

 

객체와 테이블을 생성하고 매핑하기

  • @Entity
    • JPA가 관리할 객체
  • @Id
    • 데이터베이스 PK와 매핑

localhost:8082를 url로 검색한 뒤, JDBC URL에 persistence.xml에 입력했던 아래의 코드의 value값을 넣어야지 정상적으로 연결이 됩니다.

<property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>

Entity 클래스를 만들고, 트랜잭션을 보내주면 log로 쿼리문을 볼 수 있습니다. 위와 같은 쿼리문이 로그에 출력된다면 정상적으로 jpa가 작동하는 것 입니다. 쿼리문이 로그로 출력이 되는 이유는 persistence.xml 코드에서 옵션 첫번째 줄에 show_sql의 value값을 true로 주었기 때문입니다. format_sql은 로그를 위와같이 가독성이 좋게 출력을 해줍니다. use_sql_comments주석된 부분을 로그로 보여줄 것인지 아닌지를 설정해줍니다. h2 콘솔로 확인해보면 입력했던 데이터가 저장된 것을 확인할 수 있습니다.

트랜잭션 정석코드로 변경했습니다. 트랜잭션을 보냈을 때, 에러가 발생할 시 rollback하고 정상적으로 종료되면 EntityManager와 EntityManagerFactory를 종료합니다. 지금은 jpa 정석 코드를 보여주기 위해 적었지만 실제로는 spring 위의 코드가 다 없어지고 em.persist(member)만 적으면 작업을 완료할 수 있습니다. 

 

JPA 조회

조회는 위와 같이 find 메소드를 통해 찾을 수 있습니다. 파라미터로 엔티티 클래스명, PK를 넘겨주면 DB에 저장한 데이터를 가져올 수 있습니다. 가져온 Entity의 값을 출력해보면 이전에 넣었던 데이터가 출력되는 것을 볼 수 있습니다. 

 

JPA 삭제

삭제는 위처럼 remove에 찾은 Entity 클래스 필드를 넣어주면 됩니다. 

 

JPA 수정 (신기함)

수정이 진짜 신기합니다. 마치 자바 컬렉션을 사용하는 것처럼 사용만하면 수정이됩니다. 단지, setter를 활용해 데이터를 수정만해준 뒤 트랜잭션만 보내면 JPA가 자동으로 DB 데이터를 수정해줍니다. 이것이 가능한 이유는 JPA를 통해 Entity를 가져오면 해당 Entity는 JPA가 관리하기 때문입니다. 그리고 JPA가 변경이 되는지 안되는지 트랜잭션을 commit하는 시점에 체크합니다. 그 후 바뀐 데이터가 있을 시 업데이터 쿼리를 작성하여 DB에 날려줍니다. 

 

 

주의

  • EntityManagerFactory는 하나만 생성해서 애플리케이션 전체에서 공유
    • 웹 애플리케이션 서비스를 실행한다고하면 EntityManagerFactory는 DB 당 하나만 생성한다고 보면 됩니다.
  • EntityManager는 쓰레드간에 공유 X (사용하고 버려야 한다.)
    • 고객의 요청에 따라서 사용한 뒤 close, 사용한 뒤 close 반복한다고 보면 됩니다.
    • 이유는 후에 이해하면 정리하도록!
  • JPA의 모든 데이터 변경은 트랙잭션 안에서 실행
    • 정말 중요합니다!
    • RDB 데이터 변경은 트랜잭션안에서 할 수 있도록 설계가 되어있기 때문입니다.

 

 

JPQL 소개

  • 가장 단순한 조회 방법
    • EntityManager.find(Entity 클래스명, PK 값)
    • 객체 그래프 탐색 (a.getB().getC())
  • 나이가 18살 이상인 회원을 모두 검색하고 싶다면?
    • JPQL을 사용해야 합니다.

현업에서의 개발고민은 여러 테이블이 정말 많은데 필요하면 조인도해야하고, 원하는 데이터를 최적화 해서 가져와야하고, 필요하면 통계성 쿼리도 날려야하고 이러한 작업들을 JPA에서는 JPQL로 도와준다고 보면 됩니다. 쿼리를 어떻게 쓰지라는 고민이 드실텐데 JPA에서는 대안이 있다고 합니다.

 

 

실습 - JPQL 소개

  • JPQL로 전체 회원 탐색
  • JPQL로 ID가 2 이상인 회원만 검색
    • 쿼리문에 where m.id =
  • JPQL로 이름이 같은 회원만 검색
  • JPQL에 대해 자세한 내용은 객체지향 쿼리에서 학습

 

JPQL로 전체 회원 탐색

log를 잘 보면 실제 전송한 쿼리랑 다르게 모든 필드를 나열한 것을 볼 수 있습니다. 

 

 

JPQL

  • JPA를 사용하면 엔티티 객체 중심으로 개발할 수 있다.
  • 문제는 검색 쿼리
  • 검색을 할 때도 테이블이 아닌 엔티티 객체로 탐색
  • 모든 DB 데이터를 객체로 변환해서 검색하는 것은 불가능
  • 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
  • JPQL은 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
    • SQL을 추상화했기 때문에 특정 데이터베이스에 의존 X
  • SQL 문법과 유사, SELECT FROM, GROUP BY, WHERE, HAVING, JOIN 지원
  • JPQL은 엔티티 객체를 대상으로 쿼리
  • SQL은 데이터베이스 테이블을 대상으로 쿼리
  • JPQL은 뒤에서 아주 자세히 다룸

 

 

 

 

 

 

 

 

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

 

강의 정리를 시작하기 앞서

왜?? 라는 질문에 대답하기 위해선 해당 분야에 많은 지식이 있어야지 대답을 해줄 수 있습니다.'
앞으로 스스로에게 왜?? 라는 질문을 던지며 정리를 하려고합니다. 왜?? 라는 질문은 강의와 책을 보며 찾은 해답을 정리할 것이고, 아직 해답을 못찾은 왜?라는 질문에 대한 대답은 후에 정리하도록 하겠습니다.

 

JPA 왜 공부해야 하는가?

1. 현재 JPA를 공부하는 이유는 점유율입니다. JPA, Mybatis 등 중에서 세계적으로 앞도적인 점유율을 보여주는 JPA를 사용할 줄 알고, 이해하고 있어야지 취업에 도움이 될 것이라고 생각하기 때문입니다.

2. 무한 반복, 지루한 코드를 적어야하는 SQL의 문제점을 보완해주기 때문입니다. (간단하게 쿼리문 작성이 필요하지 않습니다.) 이에따라서, 개발 시간이 줄어듬으로써 설계나 테스트 등에 시간을 더 사용할 수 있습니다.

3. 시간을 줄여준다고 성능이 입증되지 않은 것도 아닙니다. 현재 조 단위의 거래 금액이 발생하는 다양한 서비스에서 사용하고 있으며 문제없이 사용되기 때문에 문제없다고 생각했습니다.

 

SQL 중심적인 개발의 문제점

 

무한 반복, 지루한 코드를 적어야하는 예를 한번 들어봅시다. JPA가 없는 SQL만으로 작성된 아래의 코드로 작성된 프로젝트가 있다고 가정해봅시다.

여기서, 기획자에 요청에 의해서 객체에 번호 필드를 추가해야되는 상황이 주어졌습니다. 객체에 tel이란 필드를 생성하면 끝나는 것이 아닌 모든 쿼리문에 tel을 추가해야되는 상황이 발생합니다. 

결국, 관계형 데이터베이스를 사용하는 환경에서는 SQL에 의존적인 개발을 피하기 어렵습니다.

 

그리고 단순한 SQL 문을 작성하는 문제를 넘어서서 또 하나의 문제점이 있습니다. 바로 패러다임의 불일치 입니다.

 

 

패러다임의 불일치

객체 vs 관계형 데이터베이스

관계형 데이터베이스의 사상과 객체 지향 사상이 다릅니다.

 

객체 지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공합니다. 반면, 관계형 데이터베이스는 데이터를 잘 정규화해서 보관하는 것을 목표로합니다.

 

하지만 생각을 뒤집어 봅시다. 먼저 객체를 잘 정규화해서 저장한다고 봅시다. 선택지는 RDB, NoSQL, File 등 여러가지가 있습니다. 여기서 데이터를 저장하는 다양한 방법 중 현실적인 대안은 RDB입니다. 이유는 데이터가 백만건 있다고 할 때 파일에 집어 넣는 경우는 검색이 불가능하고 백만건 모두를 Object를 만든 뒤 검색하기에는 답이 없기 때문입니다. NoSQL도 대안이 될 수 있기도 하지만 아직까지 Main은 RDB를 쓰기 때문에 힘들다고 합니다. 그렇기 때문에 RDB를 사용해야합니다.

결론적으로, RDB를 사용함으로써 객체를 SQL로 바꿔야하기 때문에 SQL은 필수로 사용해야 합니다. 이러한 작업은 개발자가 해야합니다. 

 

 

객체와 관계형 데이터베이스의 차이

  1. 상속
    • 객체 : 상속 관계 O
    • RDB : 상속 관계 X
  2. 연관관계
    • 객체 : 레퍼런스 ex) ArrayList에서의 get
    • RDB : PK, FK
  3. 데이터 타입
  4. 데이터 식별 방법

 

상속

위와 같은 상속 관계가 있다고 가정해봅시다.

 

삽입

  1. 객체 분해
  2. INSERT INTO ITEM ...
  3. INSERT INTO ALBUM ...

insert의 경우 3번의 작업을 거치긴하지만 가능합니다.

 

조회

  1. 각각의 테이블에 따른 조인 SQL 작성 ..
  2. 각각의 객체 생성 ..
  3. 상상만 해도 복잡합니다.

그렇기때문에 DB에 저장할 객체에는 상속 관계를 쓰지 않습니다.

 

하지만, DB가 아닌 자바 컬렉션에 저장한다면 어떻게 될까요??

삽입

list.add(album);

조회

Album album = list.get(albumId);

// 부모 타입으로 조회 후 다형성 활용
Item item = list.get(albumID);

 자바 컬렉션에 넣으면 심플해지지만, RDB에 넣고 빼는 순간 중간에 SQL 매핑 작업을 개발자가 한땀한땀 해야합니다.

 

 

연관관계

  • 객체는 참조를 사용 : member.getTeam()
  • 테이블은 외래 키를 사용 : JOIN ON M.TEAM_ID = T.TEAM_ID

객체의 경우 Member에서 Team을 찾을 수 있고, Team에서 Member를 찾을 순 없습니다. 하지만 테이블의 경우 MEMBER에서 TEAM을 join할 수 있고, TEAM에서 MEMBER를 join할 수 도 있습니다.

 

객체를 테이블에 맞추어 모델링

class Member {
    String id;		// MEMBER_ID 컬럼 사용
    Long teamId;	// TEAM_ID FK 컬럼 사용 // **
    String username;// USERNAME 컬럼 사용
}

class Team {
    Long id;		// TEAM_ID PK 사용
    String name;	// NAME 컬럼 사용
}

 

테이블에 맞춘 객체 저장

하지만 이는 객체다운 모델링 같지 않습니다. Member 클래스에 있는 teamId의 타입이 Team이 아닌 Long으로 되어있기 때문입니다. 

 

객체다운 모델링

class Member {
    String id;		// MEMBER_ID 컬럼 사용
    Team teamId;	// 참조로 연관관계를 맺는다. // **
    String username;// USERNAME 컬럼 사용
    
    Team getTeam() {
    	return team;
    }
}

class Team {
    Long id;		// TEAM_ID PK 사용
    String name;	// NAME 컬럼 사용
}

 

객체 모델링 저장

이렇게 설계하면 정상적으로 저장이 가능합니다. 하지만 조회할 때 문제가 생깁니다.

 

 

객체 모델링 조회

위와 같이 주석처리 된 부분의 코드가 상당할 것으로 예상됩니다. 결론적으로 매우 번거롭습니다.

 

 

객체 모델링, 자바 컬렉션에 관리 

훨씬 편리해진 것을 볼 수 있습니다. 

 

 

객체 그래프 탐색

객체는 자유롭게 객체 그래프를 탐색할 수 있어야 합니다. 아래의 그래프를 예시로 들어 봅시다.

여기서 자유롭게 그래프를 탐색한다는 것은 자바에서 get을 통해 모든 객체로 갈 수 있다는 것을 의미합니다.

 



처음 실행하는 SQL에 따라 탐색 범위 결정

이처럼 SELECT로 MEMBER와 TEAM을 가져오면 getTeam은 가능하지만, getOrder은 불가능합니다. 이것을 해결하기 위해선 일일이 코드를 다 살펴봐야하는 문제가 발생하게 됩니다. 즉, 엔티티에 신뢰할수 없다는 것 입니다.

 

 

엔티티 신뢰 문제

위에 코드가 있다고 가정합시다. Member를 받아와서 getTeam(), getOrder().getDelivery()를 수행하려 합니다. 하지만 엔티티를 모두 가져올 수 있을지는 장담할 수 없습니다. 이를 알아보기 위해선 Member를 가져올 때 TEAM, ORDER, DELIVERY 데이터를 모두 긁어 오는지를 확인해야 합니다.

 

 

모든 객체를 미리 로딩할 수는 없다.

그렇다면 모든 객체를 미리 모두 로딩해놓으면 되는 것 아닌가라는 의문점이 생깁니다. 하지만 사실상 현업에서 사전에 미리 로딩하는 작업을 하게되면 서비스를 이용하기 직전에 불필요한 데이터까지 조회를 하기때문에 너무 오랜 시간을 기다릴 수 있어 서비스적인 측면에서 좋지 못합니다. (개인적인 생각입니다.) 그렇기때문에 위와같이 테이블을 조회하는 모든 경우의 수를 메소드화 시키는 것이 좋지만 이또한 코드가 지저분해지고 지루한 코드를 오랜시간 작성해야하는 번거로움이 있습니다. 

 

이렇듯 SQL을 직접 다루게 되면 계층형 아키텍처에서 진정한 의미의 계층 분할이 어렵습니다. (진정한 게층 분할은 뭘까??)

 

 

비교하기

SQL에서 조회

member1과 member2가 식별자는 똑같지만 다른 이유는 다른 쿼리문으로 받아온 값을 new Member를 통해 반환하기 때문에 당연히 다릅니다. 그렇다면 자바 컬렉션에서는 어떨까요?

자바 컬렉션에서 조회

이것은 당연하게 같은 참조값으로 조회하기때문에 같은 Member를 반환합니다.

 

이처럼 자바 컬렉션에서 객체를 다룰 때와 SQL(RDB)에서 객체를 다룰 때와 중간에 많은 미스매치가 나는 것을 볼 수 있습니다. 그렇기때문에 RDB를 객체답게 모델링 할수록 매핑 작업만 늘어나게 됩니다.

 

이러한 무한 반복, 지루한 코드를 계속해서 작성하던 선배 개발자분들은 "객체를 자바 컬렉션에 저장 하듯이 DB에 저장"하고 싶어집니다. 이러한 고민의 결과가 바로 JPA이고, 공부를 해야하는 이유 입니다.

 

 

JPA 소개

JPA란?

Java Persistence API의 약자로 자바 진영의 ORM 기술 표준입니다. 

 

ORM이란?

Object-Relational Mapping (객체 관계 매핑)의 약자로 객체는 객체대로 설계, 관계형 데이터베이스는 관계형 데이터베이스대로 설계하는 것을 의미합니다. 각각 설계된 객체와 RDB를 ORM 프레임워크가 중간에서 매핑합니다.

 

 

JPA 동작

JPA는 JAVA 애플리케이션과 JDBC 사이에서 동작합니다. 그렇기때문에 개발자가 직접 JDBC API를 쓰는 것이 아닌 JPA에게 명령하면 JPA가 JDBC API를 사용해서 SQL을 호출하고 만들어서 보내고 결과를 받는 등의 동작을 하게 됩니다.

 

 

JPA 동작 - 저장

예를들어, MemberDAO에서 객체를 저장하고 싶다고 가정해봅시다. 과거에는 JDBC API나 JDBC Template, Mybatis를 썼다면 JPA를 사용하게 될 경우에는 JPA에게 Member 객체를 넘깁니다. 그렇게되면 JPA가 Member 객체를 분석하고 적절한 INSERT SQL을 생성하고 JDBC API를 사용해서 쿼리를 DB에 보냅니다. 여기서 JPA의 장점은 쿼리를 개발자가 작성하는 것이 아닌 JPA가 작성해줍니다. 그렇기때문에 패러다임의 불일치가 해결됩니다. (아직까지는 어떻게 패러다임의 불일치가 해결되는지 이해가지 않지만 뒤에서 해결해준다고합니다.)

 

JPA 동작 - 조회

조회할 때도 마찬가지로 JPA에게 PK값만 넘기면 JPA가 알아서 쿼리문을 작성하고 JDBC API를 사용해서 DB를 보내고 결과를 받은 다음에 ResultSet 결과를 객체에 다 매핑해줍니다. 그렇기때문에 코드한줄로 조회기능을 애플리케이션 단에서 가능하게 됩니다.

 

 

JPA 역사

해당 역사는 기본편에서 한번 보았던 내용인 것 같아 따로 정리하지않고 링크를 아래 추가했습니다.

 

 

[Spring/기본편] 1. 객체 지향 프로그래밍 및 스프링 개념 정리

목표 스프링을 왜 만드는가? 이유와 핵심원리 스프링 기본 기능 학습 스프링 본질 깊은 이해 객체 지향 설계에 대한 고민을 할 수 있게 해줌 서론 2000년대 초반에는 자바 정파 기술에는 EJB(Enterpr

qazyj.tistory.com

 

 

JPA는 표준 명세

  • JPA는 인터페이스의 모음
  • JPA 2.1 표준 명세를 구현한 3가지 구현체
  • 하이버네이트(대표적), EclipseLink, DataNucleus

 

 

JPA를 왜 사용해야 하는가?

  • SQL 중심적인 개발에서 객체 중심으로 개발
  • 생산성
  • 유지보수
  • 패러다임의 불일치 해결
  • 성능
  • 데이터 접근 추상화와 벤더 독립성
  • 표준

 

생산성 - JPA와 CRUD

  • 저장
    • jpa.persist(member)
  • 조회
    • Member member = jpa.find(memberId)
  • 수정 (fantastic)
    • member.setName("updateName")
  • 삭제
    • jpa.remove(member)

코드가 모두 만들어져있고 가져다 쓰기만해서 기존 CRUD를 하기위해 SQL 문을 작성하던 번거로움이 사라졌습니다.

 

 

유지보수 - 기존 : 필드 변경시 모든 SQL 수정

위에서 느꼈던 기획자에 요청에 의해 필드가 변경될 때 모든 SQL 문을 수정해야하는 번거로움이 있었습니다. 하지만 아래와 같이 JPA에서는 JAVA 애플리케이션 단에서 컬럼만 추가하면 됩니다. 따라서 유지보수 측면에서 훨씬 우월하다고 볼 수 있습니다.

 

JPA와 패러다임의 불일치 해결

  1. JPA와 상속
  2. JPA와 연관관계
  3. JPA와 객체 그래프 탐색
  4. JPA와 비교하기

 

JPA와 상속

이전에 보았던 상속 관계입니다. 

 

JPA와 상속 - 저장

이전에 SQL 문을 작성했던 번거로움이 사라진 것을 볼 수 있습니다. 또한, 상속관계에 있는 album을 보고 알아서 ITEM의 INSERT 쿼리문도 작성해주는 것을 볼 수 있습니다.

 

JPA와 상속 - 조회

기가 막힙니다. JPA에 앨범의 PK 값을 넘기면 JPA에서 ITEM과 ALBUM을 조인해서 데이터를 가져오는 것을 볼 수 있습니다. 패러다임이 맞지 않는 부분을 JPA가 해결해줍니다. 

 

 

JPA와 연관관계, 객체 그래프 탐색

member에 team을 set한 뒤 member를 jpa에 보내 DB에 저장합니다. 후에 조회하여 member로 team을 조회하면 가져올 수 있습니다. 마치 자바 컬렉션에 넣었던 것 처럼!!

 

 

신뢰할 수 있는 엔티티, 계층

JPA를 통해서 member 객체를 가져온거면 객체 그래프를 자유롭게 모두 다 탐색할 수 있습니다. JPA는 지연로딩이라는 기능덕분에 member.getTeam()이나 member.getOrder()를 해서 실제 객체를 사용해서 조회하는 시점에 SQL을 보내 채워주는 기능이 있습니다. 그렇기때문에 자유롭게 탐색할 수 있습니다. 그렇기때문에 신뢰할 수 있는 엔티티가 될 수 있습니다.

 

JPA와 비교하기

동일한 트랙잭션에서 조회한 엔티티는 같음을 보장합니다. 이것이 가능한 이유는 강의를 보다보면 이해할 수 있다고 합니다. (아직 이해 안됨)

 

 

JPA의 성능 최적화 기능

  1. 1차 캐시와 동일성(identity) 보장
  2. 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
  3. 지연 로딩 (Lazy Loading)

JPA를 사용하면 성능이 떨어질 수도 있다고 생각할 것 입니다. 이렇게 생각하는 이유는 제 생각으론  기존에는 JAVA 애플리케이션에서 JDBC API로 바로 사용했지만, JPA를 사용하면 중간에 JPA를 거치기때문에 구조가 한단계 더 깊어졌기 때문입니다. 

계층 사이에 중간 계층이 있으면 항상 할 수 있는 것이 있습니다. 바로 모아서 쏘는 버퍼링과 읽을 때 캐싱하는 것을 할 수 있습니다. (마치 CPU와 메모리 사이 캐시기능) JAVA 애플리케이션과 JDBC API의 중간계층으로 JPA가 있기 때문에 SQL을 사용하는 것보다 이러한 부분들을 최적화 할 수 있습니다. 

 

 

1차 캐시와 동일성 보장

  1. 같은 트랜잭션 안에서는 같은 엔티티를 반환 - 약간의 조회 성능 향상
  2. DB Isolation Level이 Read Commit이어도 애플리케이션에서 Repeatable Read 보장 (DB 지식이 많이 있어야하기 때문에 나중에 이해하고 이해해야할 부분, 현재는 알아두기만 할 것)

아까 조회했던 코드입니다. 첫번째 조회는 SQL을 통해 조회하고 두번째는 캐시를 통해 데이터를 가져옵니다. 그렇기때문에 m1과 m2는 같고 조회 성능을 향상시킬 수 있습니다. 실제로 캐싱기능을 지원해주는 것이 아닌 트랜잭션을 통해 제공해주는 기능이기 때문에 굉장히 짧은 시간에만 사용이 가능해서 실무에서 큰 도움은 되지 않는다고 합니다. 

 

 

트랜잭션을 지원하는 쓰기 지연 - INSERT

  1. 트랜잭션을 커밋할 때까지 INSERT SQL을 모음
  2. JDBC BATCH SQL 기능을 사용해서 한번에 SQL 전송

JDBC BATCH 기능인 한번에 여러 SQL 기능을 사용해서 한번에 SQL 전송을 하는 기능이 있습니다. JPA를 사용하지 않는 경우에는 상당히 복잡하다고 하는데 JPA를 사용하면 위와같이 간단하게 사용할 수 있습니다. 

 

 

트랜잭션을 지원하는 쓰기 지연 - UPDATE

  1. UPDATE, DELETE로 인한 로우(ROW)락 시간 최소화
  2. 트랜잭션 커밋 시 UPDATE, DELETE SQL 실행하고, 바로 커밋

 

 

지연 로딩과 즉시 로딩

  • 지연 로딩 : 객체가 실제 사용될 때 로딩
  • 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회

JPA에서 중요한 부분 중 하나입니다. 위 그림과 같이 지연 로딩은 객체가 사용될 때 로딩을 합니다. 지연 로딩의 많은 쿼리가 발생하는 단점을 보완한 기능인데 Member 조회해서 사용할 때 team을 거의 같이 사용한다고 가정될 때 member를 로딩할 때 team도 같이 조회해서 가져오는 것을 말합니다. 옵션을 통해 지연 로딩과 즉시 로딩을 껐다 켤 수 있다고 합니다. 김영한님은 지연 로딩으로 코드를 다 작성한 후 최적화할 때 즉시 로딩이 필요한 부분만 최적화를 진행한다고 합니다.

 

 

 

 

마지막으로

ORM이라는 기술은 객체와 RDB 두 기둥위에 있는 기술입니다. JPA만 잘 안다고해서 잘 할 수 있는 것이 아니고 RDB만 잘 안다고해서 잘할 수 있는 것이 아닙니다. JPA는 객체와 RDB 두 가지 사이에서 밸런스를 잘 맞춰야합니다. 한 가지에 편향하지 않고 JPA를 공부하더라도 RDB도 꾸준하게 같이 공부해야 한다. 꼭! 명심해!

 

 

 

 

 

출처

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

캐시 기본 동작

 

캐시가 없을 때

첫 번째 요청

두 번째 요청

위와 같이 똑같은 작업을 합니다.

 

캐시가 없을 때

  • 데이터가 변경되지 않아도 계속 네트워크를 통해서 데이터를 다운로드 받아야 합니다.
  • 인터넷 네트워크는 매우 느리고 비쌉니다.
  • 브라우저 로딜 속도가 느립니다.
  • 때문에 느린 사용자 경험

 

 

캐시 적용

첫 번째 요청

cache-control을 통해 캐시가 유효한 시간(초)를 설정할 수 있습니다. 위의 경우 60초동안은 캐시가 유효하게 됩니다.

두 번째 요청

 

캐시 적용

  • 캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 됩니다.
  • 비싼 네트워크 사용량을 줄일 수 있습니다.
  • 브라우저 로딩 속도가 매우 빠릅니다.
  • 때문에 빠른 사용자 경험

 

세 번째 요청 - 캐시 시간 초과

 

캐시 시간 초과

  • 캐시 유효 시간이 초과하면, 서버를 통해 데이터를 다시 조회하고, 캐시를 갱신해야 합니다.
  • 이때 다시 네트워크 다운로드가 발생합니다.

 

 

 

검증 헤더와 조건부 요청1

캐시 유효 시간이 초과해서 서버에 다시 요청하면 알의 두 가지 상황이 나타납니다.

  1. 서버에서 기존 데이터를 변겸함
  2. 서버에서 기존 데이터를 변경하지 않음

1번의 경우는 다시 받아야 하는 것이지만, 2번의 경우는 다시 받을 필요가 없는데 다시 다운받는 불필요한 다운로드를 하게됩니다.

이러한 문제를 해결하기 위한 것이 검증 헤더와 조건부 요청입니다.

검증 헤더는 Last-Modified라는 마지막으로 수정된 시간을 통해 검증을 하는 헤더입니다.

조건부 요청은 if-modified-since라는 수정된 시간을 통해 캐시를 다시 사용해도 되는지에 대한 요청입니다.

 

검증 헤더 추가

첫 번째 요청

Last-Modified는 마지막에 수정된 시간입니다. (원래는 UTC 표기법을 사용해야 합니다.)

기존 캐시엔 최종 수정일을 등록하지 않았지만, 서버 응답에서 Last-Modified로 왔기때문에 캐시에 추가해줍니다.

캐시 시간 초과된 후 두 번째 요청

1                                                                                                                            2
3                                                                                                                            4
5                                                                                                                            6

  1. 캐시에 데이터 최종 수정일이 적혀있으면, HTTP 요청 헤더에 if-modified-since에 날짜를 붙인다음 서버로 요청합니다.
  2. 서버에서 요청을 받을 때, if-modified-since가 왔음을 확인합니다.
  3. 서버에서 if-modifiefd-since와 같은 데이터 수정일을 확인합니다.
  4. 같은 수정일이 있는 데이터가 있으면, HTTP 응답을 만들 때 HTTP Body를 비우고 304 Not Modified로 보냅니다. 나머진 이전에 보냈던 헤더와 똑같이 보냅니다. (이렇게하면 0.1M 전송하는 데이터 용량을 줄일 수 있습니다.)
  5. 웹 브라우저에서는 304 Not Modified를 보면 캐시에 있는데이터와 서버에 있는 데이터가 동일하니 캐시에 있는 데이터를 사용해도 됨을 알 수 있습니다. 이를받고 응답 결과를 재사용하며, 헤더 데이터를 갱신합니다.
  6. 해당 캐시는 다시 60초동안 유효하게됩니다.

 

검증 헤더와 조건부 요청 - 정리

  • 캐시 유효 시간이 초과해도, 서버의 데이터가 갱신되지 않으면
  • 304 Not Modified + 헤더 메타 정보만 응답(body X)
  • 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신
  • 클라이언트는 캐시에 저장되어 있는 데이터 재활용
  • 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드
  • 매우 실용적인 해결책

실제로 개발자 도구를 열었을 때 Status의 색이 연한 것은 캐시에서 가져온 데이터입니다.

캐시에 저장된 데이터를 더블클릭 -> 개발자 도구 -> 새로고침 을 하면 304 status로 나온다고하는데 저는 잘 안되네요ㅠㅠ

 

정리

  • If-Modified-Since : 이후에 데이터가 수정 되었으면?
  • 데이터 미변경 예시
    • 캐시 : 2020년 11월 10일 10:00:00 vs 서버 : 2020년 11월 10일 10:00:00
    • 304 Not Modified, 헤더 데이터만 전송
    • 전송 용량 0.1M
  • 데이터 변경 예시
    • 캐시 : 2020년 11월 10일 10:00:00 vs 서버 2020년 11월 10일 11:00:00
    • 200 OK, 모든 데이터 전송
    • 전송 용량 1.1M

 

검증 헤더와 조건부 요청 2

  • 검증 헤더
    • 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터
    • Last-Modifeid (위에서 알아보았던 것), ETag (알아 볼 것)
  • 조건부 요청 헤더
    • 검증 헤더로 조건에 따른 분기
    • If-Modified-Since : Last-Modified 사용
    • If-None-Match : ETag 사용
    • 조건이 만족하면 200 OK
    • 조건이 만족하지 않으면 304 Not Modified

 

검증 헤더와 조건부 요청

Last-Modified, If-Modified-Since 단점
  • 1초 미만(0.x초) 단위로 캐시 조정이 불가능 (최대 할 수 있는 단위가 1초이상 단위입니다.)
  • 날짜 기반의 로직 사용
  • 데이터를 수정해서 날짜가 다르지만, 같은 데이터를 수정해서 데이터 결과가 똑같은 경우 (A -> B -> A로 다시 수정된 경우, 데이터는 같지만 마지막 수정일은 다르게 됩니다.)
  • 서버에서 별도의 캐시 로직을 관리하고 싶은 경우
    • 예) 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우

 

검증 헤더와 조건부 요청

ETag, If-None-Match (Last-Modified, If-Modified-Since 단점 보완)
  • ETag(Entity Tag)
  • 캐시용 데이터에 임의의 고유한 버전 이름을 달아둠
    • 예) ETag: "v1.0", ETag : "a2jiodwjekjl3"
  • 데이터가 변경되면 이 이름을 바꾸어서 변경함 (Hash를 다시 생성)
    • 예) ETag : "aaaaa" -> ETag : "bbbbb"
  • 진짜 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받기!

 

ETag, If-None-Match 첫 번째 요청

 

ETag, If-None-Match 두 번째 요청
- 캐시 시간 초과

1                                                                                                                            2

 

3                                                                                                                            4

 

5                                                                                                                            6

 

정리

  • 단순하게 ETag만 서버에 보내서 같으면 유지, 다르면 다시 받기
  • 캐시 제어 로직을 서버에서 완전히 관리
  • 클라이언트는 단순히 이 값을 서버에 제공(클라이언트는 캐시 메커니즘을 모름)
  • 예)
    • 서버는 배타 오픈 기간인 3일 동안 파일이 변경되어도 ETag를 동일하게 유지
    • 애플리케이션 배포 주기에 맞추어 ETag 모두 갱신

 

 

 

캐시와 조건부 요청 헤더

 

캐시 제어 헤더

  • Cache-Control : 캐시 제어
  • Pragma : 캐시 제어 (하위 호환)
  • Expires : 캐시 유효 기간 (하위 호환)

 

Cache-Control

캐시 지시어 (directives)
  • Cache-Control : max-age
    • 캐시 유효 시간, 초 단위
  • Cache-Control : no-cache
    • 데이터는 캐시해도 되지만, 항상 원(origin) 서버에 검증하고 사용 (원 서버란 캐시 서버, 프록시 서버가 아닌 진짜 서버)
  • Cache-Control : no-store
    • 데이터에 민감한 정보가 있으므로 저장하면 안됨 (메모리에서 사용하고 최대한 빨리 삭제)

 

Pragma

캐시 제어 (하위 호환)
  • Pragma : no-cache
  • HTTP 1.0 하위호환

 

Expires

캐시 만료일 지정 (하위 호환)
  • expires : Mon, 01 Jan 1990 00:00:00 GMT
  • 캐시 만료일을 정확한 날짜로 지정
  • HTTP 1.0 부터 사용
  • 지금은 더 유연한 Cache-Control:max-age 권장
  • Cache-Control:max-age와 함께 사용하면 Expires는 무시

 

 

 

프록시 캐시

 

원 서버 직접 접근

origin 서버

이렇게 클라이언트마다 원 서버로 접근하게되면 500ms를 다운로드 받을 때마다 0.5초를 소모하게 됩니다. 이러한 시간을 단축시키고자 프록시 서버라는 것이 등장하게 됩니다.

 

프록시 캐시 도입

첫 번째 요청

이러한 프록시 캐시 서버 덕분에 유튜브, 넷플릭스 같은 서비스를 모든 나라에서 빨리 볼 수 있는 것 입니다. (이를 증명하는 것은 한국서버에서 한국인이 자주 보는 동영상은 로딩 속도가 되게 빠르지만, 한국인이 잘 보지 않는 외국 기술 동영상들을 보면 로딩 속도가 매우 느립니다.)

 

Cache-Control

캐시 지시어(directives) - 기타
  • Cache-Control : public
    • 응답이 public 캐시에 저장되어도 됨
  • Cache-Control : private
    • 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(기본값) (공용으로 사용되는 이미지는 public에 저장 되도 되지만, 개인 정보같은 경우는 public에 저장되면 안되기 때문)
  • Cache-Control : s-maxage
    • 프록시 캐시에만 적용되는 max-age
  • Age : 60 (HTTP 헤더)
    • 오리진 서버에서 응답 후 프록시 캐시내에 머문 시간(초)

 

 

 

캐시 무효화

 

Cache-Control

확실한 캐시 무효화 응답
  • Cache-Control을 적지 않아도 많이 사용 되는 경우 임의로 캐시에 등록 될 수 있기 때문에, 절대 캐시가 되면 안되는 페이지는 아래와 같은 작업을 해주어야 합니다.
  • Cache-Control : no-cache, no-store, must-revalidate
  • Pragma : no-cahce
    • HTTP 1.0 하위 호환

 

Cache-Control

캐시 지시어(directives) - 확실한 캐시 무효화
  • Cache-Control : no-cache
    • 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용
  • Cache-Control : no-store
    • 데이터에 민감한 정보가 있으므로 저장하면 안됨
  • Cache-Control : must-revalidate
    • 캐시 만료후 최초 조회 시 원 서버에 검증해야 함
    • 원 서버 접근 실패시 반드시 오류가 발생해야 함 - 504 Gateway Timeout
    • must-revalidate는 캐시 유효 시간이라면 캐시를 사용함
  • Pragma : no-cahce
    • HTTP 1.0 하위 호환

 

no-cache vs must-revalidate

no-cache 기본 동작

1

 

2

 

3

3번의 경우 프록시 캐시에서 원 서버의 장애로 장애를 웹 브라우저에 보내기보다는 이전 데이터라도 쓰라며 200 OK를 보내는 경우가 있다고 합니다. 이것이 no-cache의 정책입니다.

 

must-revalidate

위의 no-cache인 캐시 지시어는 프록시 캐시에서 원 서버로 접근하는 네트워크가 단절되도 웹 브라우저한테는 이전의 데이터로라도 사용하라며 200 OK를 보내주지만, must-revalidate의 경우는 원 서버와 네트워크가 되지 않으면 웹 브라우저에게 504 Gateway Timeout의 응답을 줍니다. 이것이 no-cache와 must-revalidate의 차이 입니다.

 

 

 

 

 

출처

 

모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

HTTP 헤더 개요

HTTP 헤더

  • header-field = field-name ":" OWS field-value OWS (OWS: 띄어쓰기 허용)
  • field-name은 대소문자 구문 없음

 

HTTP 헤더 용도

  • HTTP 전송에 필요한 모든 부가정보
  • 예) 메시지 바디의 내용, 메시지 바디의 크기, 압축, 인증, 요청 클라이언트, 서버 정보, 캐시 관리 정보....
  • 표준 헤더가 너무 많음
  • 필요시 임의의 헤더 추가 가능

 

HTTP 헤더

분류 - RFC2616(과거)
  • 헤더 분류
    • General 헤더 : 메시지 전체에 적용되는 정보, 예) Connection : close
    • Request 헤더 : 요청 정보, 예) User-Agent: Mozilla/5.0 (Macintosh; ..)
    • Response 헤더 : 응답 정보, 예) Server: Apache
    • Entity 헤더 : 엔티티 바디 정보, 예) Content-Type: text/html, Content-Length:3423
  • 메시지 본문(message body)은 엔티티 본문(entity body)을 전달하는데 사용
  • 엔티티 본문은 요청이나 응답에서 전달할 실제 데이터
  • 엔티티 헤더는 엔티티 본문의 데이터를 해석할 수 있는 정보 제공
    • 데이터 유형(html, json), 데이터 길이, 압축 정보 등등

 

RFC723x 변화

  • 엔티티 -> 표현
  • Representation = Representation Metadata + Representation Data
  • 표현 = 표현 메타데이터 + 표현 데이터

 

HTTP BODY(Message body) - RFC7230(최신)

  • 메시지 본문(message body)를 통해 표현 데이터 전달
  • 메시지 본문 = 페이로드(payload)
  • 표현은 요청이나 응답에서 전달할 실제 데이터
  • 표현 헤더는 표현 데이터를 해석할 수 있는 정보 제공
    • 데이터 유형(html,json), 데이터 길이, 압축 정보 등등
  • 참고 : 표현 헤더는 표현 메타데이터와, 페이로드 메시지를 구분해야 하지만, 여기서는 생략

 

 

 

표현

  • Content-Type : 표현 데이터 형식 (html, json..)
  • Content-Encoding : 표현 데이터의 압축 방식
  • Content-Language : 표현 데이터의 자연 언어 (한국어, 영어)
  • Content-Length : 표현 데이터(페이로드 헤더)의 길이
  • 표현 헤더는 전송, 응답 둘 다 사용

 

Content-Type

표현 데이터의 형식 설명
  • 미디어 타입, 문자 인코딩
  • 예)
    • text/html;charset=utf-8
    • application/json
    • image/png

 

Content-Encoding

표현 데이터 인코딩
  • 표현 데이터를 압축하기 위해 사용
  • 데이터를 전달하는 고셍서 압축 후 인코딩 헤더 추가
  • 데이터를 읽는 쪽에서 인코딩 헤더의 정보로 압축 해제
  • 예)
    • gzip
    • deflate
    • identity

 

Content-Language

표현 데이터의 자연 언어
  • 표현 데이터의 자연 언어를 표현
  • 예)
    • ko
    • en
    • en-US

 

Content-Length

표현 데이터의 길이
  • 바이트 단위

 

 

 

콘텐츠 협상 (콘텐츠 네고시에이션)

클라이언트가 선호하는 표현 요청
  • Accept : 클라이언트가 선호하는 미디어 타입 전달
  • Accept-Charset : 클라이언트가 선호하는 문자 인코딩
  • Accept-Encoding : 클라이언트가 선호하는 압축 인코딩
  • Accept-Language : 클라이언트가 선호하는 자연 언어
  • 협상 헤더는 요청시에만 사용 (google 홈페이지에서 F12로 검색해보면 헤더에 포함된 Accep-Language등을 볼 수 있습니다.)

콘텐츠 협상은 클라이언트가 원하는 표현으로 달라고 서버한테 요청하는 것이라고 생각하면됩니다. Accept-Language를 예시로 들어보겠습니다.

 

Accept-Language 적용 전

 

Accept-Language 적용 후

 

Accept-Language 복잡한 예시

독일어, 영어만 지원하는 서버에서 한국어를 요청하면 1번째 우선순위인 독일어로 응답이 옵니다.

위의 서버가 있을 수도 있기때문에 우선순위가 필요합니다.

 

 

 협상과 우선순위 1

Quality Values(q)
  • Quality Values(q) 값 사용
  • 0~1, 클수록 높은 우선순위
  • 생략하면 1
  • Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
    1. ko-KR;q=1 (q 생략)
    2. ko;q=0.9
    3. en-US;q=0.8
    4. en;q=0.7

 

이러한 우선순위를 통해 아까의 복잡한 예시로 똑같이 서버로 요청을 보내게 되면 서버에서 응답 가능한 언어 중에서 클라이언트가 요청한 언어 중 가장 높은 우선순위를 가진 언어로 응답을 보내줍니다.

 

협상과 우선순위 2

Quality Values(q)
  • 구체적인 것이 우선한다.
  • Accept : text/*, text/plain, text/plain;format=flowed, */*
    1. text/plain;format=flowed
    2. text/plain
    3. text/*
    4. */*

위처럼 구체적인 것일 수록 우선순위가 높습니다.

 

협상과 우선순위 3

Quality Values(q)
  • 구체적인 것을 기준으로 미디어 타입을 맞춥니다.
  • Accept : text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5
Media Type Quality
text/html;level=1 1
text/html 0.7
text/plain 0.3
image/jpeg 0.5
text/html;level=2 0.4
text/html;leve=3 0.7

 

 

 

전송 방식

  • Transfer-Encoding
  • Range, Content-Range

 

전송 방식 설명

  • 단순 전송
  • 압축 전송
  • 분할 전송
  • 범위 전송

 

단순 전송

한 번에 요청하고 한 번에 쭉 받는 것 

 

압축 전송

보낼 message body를 gzip 등을 이용해 압축해서 전송하는 것
참고) HTTP HEAD 부분에 Contetn-Encoding을 적어주어야함.

 

분할 전송

보낼 데이터를 byte 단위로 끊어서 전송하는 것
참고) HTTP HEAD에 Transfer-Encoding = chunked를 추가해줘야함.
참고) Content-Length를 넣으면 안된다. (총 몇 바이트인지 예상이 안되기때문)
아래의 Body로 예시를 들면
5byte Hello 먼저 전송
5byte World 전송
0Byte 전송
용량이 되게 큰 경우에 이렇게 나누어서 전송함

 

범위 전송

이미지를 받는데, 절반정도 받고 나머지 절반을 받는 형식
이유 : 큰 용량의 데이터를 받는 경우 중간에 받지못하면 많은 양의 데이터를 다시 처음부터 받아야하기 때문

 

 

 

일반 정보

  • From : 유저 에이전트의 이메일 정보
  • Referer : 이전 웹 페이지 주소
  • User-Agent : 유저 에이전트 애플리케이션 정보
  • Server : 요청을 처리하는 오리진 서버의 소프트웨어 정보
  • Data : 메시지가 생성된 날짜

 

From

유저 에이전트의 이메일 정보
  • 일반적으로 잘 사용되지 않음
  • 검색 엔진 같은 곳에서, 주로 사용
  • 요청에서 사용

 

Feferer

이전 웹 페이지 주소
  • 현재 요청된 페이지의 이전 웹 페이지 주소
  • A -> B로 이동하는 경우 B를 요청할 때 Referer: A를 포함해서 요청
  • Referer를 사용해서 유입 경로 분석 가능 (내 사이트를 어디 사이트를 통해 들어왔는지 데이터 분석할 때 많이 사용)
  • 요청에서 사용
  • 참고 : referer는 단어 referrer의 오타 => ...??ㅋㅋㅋ그렇다고합니다...

실제 구글에서 hello를 검색한 뒤, 나무위키를 들어가서 개발자 도구에서 Request Headers를 보시면 referer에 이전 페이지의 주소가 적혀있는 것을 볼 수 있습니다.

 

User-Agent

유저 에이전트 애플리케이션 정보
  • user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/ 537.36 (KHTML, like Gecko) Chrome/86.0.4240.183 Safari/537.36
  • 클라이언트의 애플리케이션 정보 (웹 브라우저 정보, 등등)
  • 통계 정보
  • 어떤 종류의 브라우저에서 장애가 발생하는지 파악 가능 (Chrome, Edge 등등..)
  • 요청에서 사용

 

Server

요청을 처리하는 ORIGIN 서버의 소프트웨어 정보 (프록시, 캐시 서버를 제외한 진짜 나의 요청을 응답하는 서버 - ORIGIN 서버)
  • Server : Apache/2.2.22 (Debian)
  • server : nginx
  • 응답에서 사용

 

Date

메시지가 발생한 날짜와 시간
  • Date : Tue, 15 Nov 1994 08:12:31 GMT
  • 응답에서 사용

 

 

 

특별한 정보

  • HOST : 요청한 호스트 정보 (도메인)
  • Location : 페이지 리다이렉션
  • Allow : 허용 가능한 HTTP 메서드
  • Retry-After : 유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간

 

HOST

요청한 호스트 정보 (도메인)
  • 요청에서 사용
  • 필수
  • 하나의 서버가 여러 도메인을 처리해야 할 때 (구글, 네이버 등등과 같은 경우)
  • 하나의 IP 주소에 여러 도메인이 적용되어 있을 때

하나의 서버가 여러 도메인을 처리한다는 것은 가상호스트를 통해 여러 도메인을 한번에 처리할 수 있는 서버로 실제 애플리케이션이 여러개 구동될 수 있다고 합니다.

문제는 호스트가 없이 /hello만 온다면 아래와 같이 어떤 도메인에 /hello인지 모른다는 것 입니다.

그렇기 때문에 HOST를 적어주어야지 서버에서는 정확한 응답을 해줄 수 있습니다.

 

Location

페이지 리다이렉션
  • 웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 이동(리다이렉트)
  • 응답코드 3xx에서 설명
  • 201 (Created) : Location 값은 요청에 의해 생성된 리소스 URI
  • 3xx (Redirection) : Location 값은 요청을 자동으로 리다이렉션하기 위한 대상 리소스를 가리킴

 

Allow

허용 가능한 HTTP 메서드
  • 허용하지 않는 HTTP 메서드로 접근하는 경우
  • 405 (Method Not Allowd)에서 응답에 포함해야함
  • Allow : GET, HEAD, PUT

 

Retry-After

유저 에이전트가 다음 요청을 하기까지 기다려야 하는 시간
  • 503 (Service Unavailable) : 서비스가 언제까지 불능인지 알려줄 수 있음
  • Retry-After : Fri, 31 Dec 1999 23:59:59 GMT (날짜 표기)
  • Retry-After : 120 (초단위 표기)

 

 

 

인증

  • Authorization : 클라이언트 인증 정보를 서버에 전달
  • WWW-Authenticate : 리소스 접근시 필요한 인증 방법 정의

 

Authorization

클라이언트 인증 정보를 서버에 전달
  • Authorization : Basic xxxxxxxxxxxxxxxxxxx
  • Oauth 인증 등등 value에 들어가는 입력이 다름

 

WWW-Authenticate

리소스 접근시 필요한 인증 방법 정의
  • 리소스 접근시 필요한 인증 방법 정의
  • 401 Unauthorized 응답과 함께 사용
  • WWW-Authenticate : Newauth realm= apps",type=1, title="Login to \"apps\"", Basic realm="simple"
    • 인증을 하려면 "New~simple"과 정보를 참고해서 제대로된 인증 정보를 만들라고 응답

 

 

 

쿠키

  • Set-Cookie : 서버에서 클라이언트로 쿠키 전달 (응답)
  • Cookie : 클라이언트가 서버에서 받은 쿠키를 저장하고, HTTP 요청시 서버로 전달

 

쿠키 미사용

처음 welcome 페이지 접근

로그인

로그인 이후 welcome 페이지 접근

HTTP는 메시지를 다 보내고나면 연결을 끊기 때문에 쿠키를 사용하지 않으면 서버에서 이전에 로그인을 했던 사용자인지 처음온 사람인지 알 수 있는 방법이 없습니다. (HTTP가 Stateless이기 때문)

 

대안 - 모든 요청에 사용자 정보 포함

하지만, 위의 대안으로 하면 모든 요청과 링크에 사용자 정보를 포함해야 합니다.

 

쿠키 사용

로그인

로그인 이후 welcome 페이지 접근

쿠키는 모든 요청에 쿠키 정보가 자동으로 포함됩니다.

 

쿠키

  • 예) set-cookie: sessionId=abcde1234; exires=Sat, 26-Dec-2020 00:00:00 GMT; path=/; domain=.google.com; Secure
  • 사용처
    • 사용자 로그인 세션 관리
    • 광고 정보 트래킹 (이 웹브라우저를 사용하는 사람이 ~~~한 광고를 보는구나 트래킹용도)
  • 쿠키 정보는 항상 서버에 전송됨
    • 네트워크 트래픽 추가 유발 (따라서, 최소한의 정보만을 사용해야함. 로그인정보만)
    • 최소한의 정보만 사용 (세션 id, 인증 토큰)
    • 서버에 전송하지 않고, 웹 브라우저 내부에 데이터를 저장하고 싶으면 웹 스토리지 (localStorage, sessionStorage) 참고
  • 주의!
    • 보안에 민감한 데이터는 저장하면 안됨(주민번호, 신용 카드 번호 등등)

 

쿠키 - 생명주기

Expires, max-age
  • Set-Cookie: expires=Sat, 26-Dec-2020 04:39;21 GMT
    • 만료일이 되면 쿠키 삭제
  • Set-Cookie: max-age=3600 (3600초)
    • 0이나 음수를 지정하면 쿠키 삭제
  • 세션 쿠키 : 만료 날짜를 생략하면 브라우저 종료시 까지만 유지
  • 영속 쿠키 : 만료 날짜를 입력하면 해당 날짜까지 유지

 

쿠키 - 도메인

Domain
  • 예) domain=example.org
  • 명시 : 명시한 문서 기준 도메인 + 서브 도메인 포함
    • domain=example.org를 지정해서 쿠키 생성
      • example.org는 물론이고
      • dev.example.org도 쿠키 접근
  • 생략 : 현재 문서 기준 도메인만 적용
    • example.org에서 쿠키를 생성하고 domain 지정을 생략
      • example.org 에서만 쿠키 접근
      • dev.example.org는 쿠키 미접근

 

쿠키 - 경로

Path
  • 예) path=/home
  • 이 경로를 포함한 하위 경로 페이지만 쿠키 접근
  • 일반적으로 path=/ 루트로 지정
  • 예)
    • path=/home 지정
    • /home -> 가능
    • /home/level1 -> 가능
    • /home/level1/level2 -> 가능
    • /hello -> 불가능

 

쿠키 - 보안

Secure, HttpOnly, SameSite
  • Secure
    • 쿠키는 http, https를 구분하지 않고 전송
    • Secure를 적용하면 https인 경우에만 전송
  • HttpOnly
    • XSS 공격 방지
    • 자바스크립트에서 접근 불가 (document.cookie)
    • HTTP 전송에만 사용
  • SameSite
    • XSRF 공격 방지
    • 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송

 

 

 

 

 

출처

 

모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

상태 코드

클라이언트가 보낸 요청을 처리 상태를 응답에서 알려주는 기능
  • 1xx (Informational) : 요청이 수신되어 처리중
  • 2xx (Successful) : 요청이 정상 처리
  • 3xx (Redirection) : 요청을 완료하려면 추가 행동이 필요
  • 4xx (Client Error) : 클라이언트 오류, 잘못된 문법등으로 서버가 요청을 수행할 수 없음
  • 5xx (Server Error) : 서버 오류, 서버가 정상 요청을 처리하지 못함

 

 

 

2xx (성공)

클라이언트의 요청을 성공적으로 처리
  • 200 OK
    • 요청 성공
  • 201 Created
    • 요청이 성공해서 새로운 시소스가 생성
    • 응답 헤더에 "Location : (새로생성된 리소스 URI)"를 포함해서 응답
  • 202 Accepted
    • 요청이 접수되었으나 처리가 완료되지 않는 경우
    • 배치 처리 같은 곳에서 사용
    • 예) 요청 접수 후 1시간 뒤에 배치 프로세스가 요청을 처리함
  • 204 No Content
    • 서버가 요청을 성공적으로 수행했지만, 응답 페이로드 본문에 보낼 데이터가 없음
    • 예) 웹 문서 편집기에서 save 버튼 (클라이언트 입장에서 서버에서 데이터를 보내줄 필요가 없음)
    • 결과 내용이 없는 204 메시지만으로 성공을 인식해도 되는 경우

 

 

 

3xx (리다이렉션1)

요청을 완료하기 위해 클라이언트 웹 브라우저의 추가 조치 필요
  • 300 Multiple Choices
  • 301 Moved Permanently
  • 302 Found
  • 303 See Other
  • 304 Not Modified
  • 307 Temporary Redirect
  • 308 Permanent Redirect

리다이렉션 이해

  • 웹 브라우저는 3xx 응답의 결과에 Location 헤더가 있으면, Location 위치로 자동 이동(리다이렉트)

 

기존 event 시에 /event로 접근하였는데 새로운 이벤트는 /new-event로 접근하는 경우 301 코드와 새로바뀐 path를 Location에 포함해서 응답을 보냅니다. 그렇게되면 웹 브라우저가 스스로 해당 url로 바꾼뒤 새롭게 검색합니다. (자동 리다이렉트)

 

리다이렉션 종류

  • 영구 리다이렉션 - 특정 리소스의 URI가 영구적으로 이동
    • 예) /members -> /users
    • 예) /event -> /new-event
  • 일시 리다이렉션 - 일시적인 변경
    • 주문 완료 후 주문 내역 화면으로 이동
    • PRG 패턴 : Post/Redirect/Get
  • 특수 리다이렉션
    • 결과 대신 캐시를 사용
    • 캐시가 만료된 것 같아 캐시와 관련된 정보를 서버에 제공했는데, 서버에서 캐시사용해도 되니깐 계속 사용하라고 응답하는 경우

 

영구 리다이렉션 - 301, 308

  • 리소스의 URI가 영구적으로 이동
  • 원래의 URL을 사용하지 않고, 검색 엔진 등에서도 변경 인지
  • 301 Moved Permanently
    • 리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음
    • POST로 요청을 했을 시에는 다시 처음부터 입력을 해야됩니다. (POST 요청 메서드가 GET 메서드로 바뀌었기 때문)
  • 308 Permanent Redirect
    • 301과 기능은 같음
    • 리다이렉트시 요청 메서드와 본문 유지(처음 POST를 보내면 리다이렉트도 POST 유지)

 

일시적인 리다이렉션 - 302, 307, 303

  • 리소스의 URI가 일시적으로 변경
  • 따라서 검색 엔진 등에서 URL을 변경하면 안됨
  • 302 Found
    • 리다이렉트시 요청 메서드가 GET으로 변하고, 본문이 제거될 수 있음
  • 307 Temporary Redirect
    • 302와 기능은 같음
    • 리다이렉트시 요청 메서드와 본문 유지(요청 메서드를 변경하면 안된다.)
  • 303 See Other
    • 302와 기능은 같음
    • 리다이렉트시 요청 메서드가 GET으로 변경

 

PRG : Post / Redirect / Get

일시적인 리다이렉션 - 예시
  • POST로 주문후에 웹 브라우저를 새로고침하면?
  • 새로고침은 다시 요청
  • 중복 주문이 될 수 있다.

 

원칙적으로는 서버에서 막아줘야합니다. 그래도 클라이언트에서도 하나정도는 막아주면 좋습니다. 위의 문제를 해결하기 위해 PRG라는 패턴을 주로 씁니다.

  • POST로 주문후에 새로 고침으로 인한 중복 주문 방지
  • POST로 주문후에 주문 결과 화면을 GET 메서드로 리다이렉트
  • 새로고침해도 결과 화면을 GET으로 조회
  • 중복 주문 대신에 결과 화면만 GET으로 다시 요청

  • 3번의 응답 상태 코드는 302, 303 모두 가능합니다.

이렇게되면 사용성이 좋습니다. 사용자의 입장에서 실수로 새로고침해도 결과화면이 깔끔하게 보이고 서버입장에서는 오류가 줄어들기 때문입니다.

 

위의 경우 303, 307을 권장하지만 현실적으로 이미 많은 애플리케이션 라이브러리들이 302를 기본값으로 사용하고 있다고 합니다. 위처럼 자동 리다이렉션시에 GET으로 변해도 되면 302를 사용해도 큰 문제는 없습니다.

 

기타 리다이렉션

  • 300 Multiple Choices : 안쓴다.
  • 304 Not Modified
    • 캐시를 목적으로 사용
    • 클라이언트에게 리소스가 수정되지 않았음을 알려줍니다. 따라서 클라이언트는 로컬에 저장된 캐시를 재사용합니다.
    • 304 응답은 응답에 메시지 바디를 포함하면 안됩니다. (로컬 캐시를 사용해야 함)
    • 조건부 GET, HEAD 요청 시 사용

 

 

 

4xx (클라이언트 오류), 5xx (서버 오류)

 

4xx (Client Error)

  • 클라이언트의 요청에 잘못된 문법등으로 서버가 요청을 수행할 수 없음
  • 오류의 원인이 클라이언트에 있음
  • 중요! 클라이언트가 이미 잘못된 요청, 데이터를 보내고 있기 때문에, 똑같은 재시도가 실패함

 

400 Bad Request

클라이언트가 잘못된 요청을 해서 서버가 요청을 처리할 수 없음
  • 요청 구문, 메시지 등등 오류
  • 클라이언트는 요청 내용을 다시 검토하고, 보내야함
  • 예) 요청 파라미터가 잘못되거나, API 스펙이 맞지 않을 때

 

401 Unauthorized

클라이언트가 해당 리소스에 대한 인증이 필요함
  • 인증(Authentication) 되지 않음
  • 401 오류 발생시 응답에 WWW-Authenticate 헤더와 함께 인증 방법을 설명
  • 참고
    • 인증 (Authentication) : 본인이 누구인지 확인, (로그인)
    • 인가 (Authorization) : 권한 부여 (ADMIN 권한처럼 특정 리소스에 접근할 수 있는 권한, 인증이 있어야 인가가 있음)
    • 오류 메시지가 Unauthorized 이지만 인증 되지 않음 (이름이 아쉬움)

 

403 Foribidden

서버가 요청을 이해했지만 승인을 거부함
  • 주로 인증 자격 증명은 있지만, 접근 권한이 불충분한 경우
  • 예) ADMIN 등급이 아닌 사용자가 로그인은 했지만, ADMIN 등급의 리소스에 접근하는 경우

 

404 Not Found

요청 리소스를 찾을 수 없음
  • 요청 리소스가 서버에 없음
  • 또는 클라이언트가 권한이 부족한 리소스에 접근할 때 해당 리소스를 숨기고 싶을 때

 

 

5xx (Server Error)

  • 서버 문제로 오류 발생
  • 서버에 문제가 있기 때문에 재시도하면 성공할 수도 있음(서버가 복구 되는 경우)

 

500 Internal Server Error

서버 문제로 오류 발생, 애매하면 500 오류
  • 서버 내부 문제로 오류 발생
  • 애매하면 500 오류

 

503 Service Unavailable

서비스 이용 불가
  • 서버가 일시적인 과부하 또는 예정된 작업으로 잠시 요청을 처리할 수 없음
  • Retry-After 헤더 필드로 얼마뒤에 복구되는지 보낼 수도 있음

 

 

 

 

 

출처

 

모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

클라이언트에서 서버로 데이터 전송

  1. 쿼리 파라미터를 통한 데이터 전송
    • GET
    • 주로 정렬 필터 (검색어 - URL에 치는 검색어)
  2. 메시지 바디를 통한 데이터 전송
    • POST, PUT, PATCH
    • 회원 가입, 상품 주문, 리소스 등록, 리소스 변경

 

클라이언트에서 서버로 데이터 전송 - 4가지 상황

  • 정적 데이터 조회
    • 이미지, 정적 텍스트 문서
  • 동적 데이터 조회
    • 주로 검색, 게시판 목록에서 정렬 필터(검색어)
  • HTML Form을 통한 데이터 전송
    • 회원 가입, 상품 주문, 데이터 변경
  • HTTP API를 통한 데이터 전송
    • 회원 가입, 상품 주문, 데이터 변경
    • 서버 to 서버, 앱 클라이언트, 웹 클라이언트(Ajax)

 

정적 데이터 조회 - 쿼리 파라미터 미사용

이미지 경로만 URI에 넣어서 GET 요청을하면 서버에서는 해당 경로에 있는 데이터를 클라이언트에게 반환해줍니다. 정적 데이터를 조회할 때는 파라미터가 필요하지 않습니다.

 

정적 데이터 조회 - 정리

  • 이미지, 정적 테스트 문서
  • 조회는 GET 사용
  • 정적 데이터는 일반적으로 쿼리 파라미터 없이 리소스 경로로 단순하게 조회 가능

 

동적 데이터 조회 - 쿼리 파라미터 사용

동적 데이터를 조회할 때는 데이터를 서버측에 넘겨줘야합니다. 이때, 쿼리 파라미터를 URI에 추가하여 사용합니다. 


동적 데이터 조회 - 정리

  • 주로 검색, 게시판 목록에서 정렬 필터 (검색어)
  • 조회 조건을 줄여주는 필터, 조회 결과를 정렬하는 정렬 조건에 주로 사용
  • 조회는 GET 사용
  • GET은 쿼리 파라미터를 사용해서 데이터를 전달

 

HTML Form 데이터 전송 - POST 전송 (저장)

form tag를 사용해 웹브라우저가 submit 버튼을 클라이언트에서 누르게 되면 form의 데이터를 읽어서 http 메시지를 생성해줍니다. 

  • username=kim&age=20 
    • 쿼리 파라미터와 거의 유사하게 key=value 스타일로 데이터를 만들고 http body에 넣어준 모습입니다.

만약, 위의 method를 "GET"으로 바꾸면 어떻게 될까요??

 

GET이니까 body에 데이터가 들어가지 않고 쿼리 파라미터처럼 넣어서 서버로 전달합니다.

 

 

HTML Form 데이터 전송 - multipart/form-data

multipart/form-data는 파일 같은 것을 전송할 때 쓰는 인코딩 타입입니다. 이럴 때는 message body에 넣는 방식을 사용해야합니다. message Body에 boundary를 통해 각각의 key의 value가 어떤 값인지 나타낼 수 있또록 자동으로 생성해줍니다. 

 

HTML Form 데이터 전송

  • HTML Form submit시 POST 전송
    • 예) 회원 가입, 상품 주문, 데이터 변경

  • Content-Type : application/x-www-form-urlencoded 사용
    • form의 내용을 메시지 바디를 통해서 전송(key=value, 쿼리 파라미터 형식)
    • 전송 데이터를 url encoding 처리
      • 예) abc김 -> abc%EAA%B9%80
  • HTML Form은 GET 전송도 가능

  • Content-Type: multipart/form-data
    • 파일 업로드 같은 바이너리 데이터 전송시 사용
    • 다른 종류의 여러 파일과 폼의 내용 함께 전송 가능 (그래서 이름이 multipart)
  • 참고: HTML Form 전송은 GET, POST만 지원

 

HTTP API 데이터 전송

보통 라이브러리가 있는데 사용해서 넘기면 됩니다.

 

 

HTTP API 데이터 전송 - 정리

  • 서버 to 서버
    • 백엔드 시스템 통신
  • 앱 클라이언트
    • 안드로이드, 아이폰
  • 웹 클라이언트
    • HTML에서 Form 전송 대신 자바 스크립트를 통한 통신에 사용 (AJAX)
    • 예) React, VueJs 같은 웹 클라이언트와 API 통신
  • POST, PUT, PATCH : 메시지 바디를 통해 데이터 전송
  • GET: 조회, 쿼리 파라미터로 데이터 전달
  • Content-Type: application/json을 주로 사용 (사실상 표준)
    • TEXT, XML, JSON 등등

 

 

 

HTTP API 설계 예시

  • HTTP API - 컬렉션
    • POST 기반 등록
    • 예) 회원 관리 API 제공
  • HTTP API - 스토어
    • PUT 기반 등록
    • 예) 정적 컨텐츠 관리, 원격 파일 관리
  • HTML FORM 사용
    • 웹 페이지 회원 관리
    • GET, POST만 지원

 

회원 관리 시스템 API 설계 - POST 기반 등록

  • 회원 목록 /members -> GET
  • 회원 등록 /members -> POST
  • 회원 조회 /members/{id} -> GET
  • 회원 수정 /members/{id} -> PATCH, PUT, POST
  • 회원 삭제 /members/{id} -> DELETE

 

회원 관리 시스템 POST - 신규 자원 등록 특징 (서버에서 리소스 URI를 결정하고 만들어줌)

  • 클라이언트는 등록될 리소스의 URI를 모릅니다.
    • 회원 등록 /members -> POST
    • POST /members
  • 서버가 새로 등록된 리소스 URI를 생성해줍니다.
    • HTTP/1.1 201 Created
      Location: /members/100
  • 컬렉션(Collection)
    • 서버가 관리하는 리소스 디렉토리
    • 서버가 리소스의 URI를 생성하고 관리
    • 여기서 컬렉션은 /members

 

파일 관리 시스템 API 설계 - PUT 기반 등록

  • 파일 목록 /files -> GET
  • 파일 조회 /files/{filename} -> GET
  • 파일 등록 /files/{filename} -> PUT
  • 파일 삭제 /files/{filename} -> DELETE
  • 파일 대량 등록 /files -> POST

 

파일 관리 시스템 PUT - 신규 자원 등록 특징

  •  클라이언트가 리소스 URI를 알고 있어야 합니다.
    • 파일 등록 /files/{filename} -> PUT
    • 여기서 filename을 클라이언트가 알고 있어야합니다.
  • 클라이언트가 직접 리소스의 URI를 지정합니다.
  • 스토어(Store)
    • 클라이언트가 관리하는 리소스 저장소
    • 클라이언트가 리소스의 URI를 알고 관리
    • 여기서 스토어는 /files

 

POST 등록과 PUT 등록 중 많이 사용하는 것은 POST기반의 컬렉션을 많이 사용합니다. 

 

 

HTML FORM 사용

  • HTML FROM은 GET, POST만 지원
  • AJAX 같은 기술을 사용해서 해결 가능 -> 회원 API 참고
  • 여기서는 순수 HTML, HTML FORM 이야기
  • GET, POST만 지원하므로 제약이 있음

 

회원 관리 시스템 API 설계 - HTML FORM 기반

  • 회원 목록    /members -> GET
  • 회원 등록 폼    /members/new -> GET
  • 회원 등록    /members/new, /members -> POST
  • 회원 조회    /members/{id} -> GET
  • 회원 수정 폼    /members/{id}/edit -> GET
  • 회원 수정    /members/{id}/edit, /members/{id} -> POST 
  • 회원 삭제    /members/{id}/delete -> POST
  • 회원 등록(new), 수정(edit), 삭제(delete)의 경우 GET, POST만 사용할 수 있는 HTML FORM에서는 컨트롤 URI(동사로된 URI)를 쓸 수 밖에 없습니다.
  • 단, 컨트롤 URI는 최대한 리소스로 설계를 하고 도저히 안될때만 사용해야 합니다.

 

정리

  • HTTP API - 컬렉션
    • POST 기반 등록
    • 서버가 리소스 URI 결정
  • HTTP API - 스토어
    • PUT 기반 등록
    • 클라이언트가 리소스 URI 결정
  • HTML FORM 사용
    • 순수 HTML + HTML form 사용
    • GET, POST만 지원

 

참고하면 좋은 URI 설계 개념

  • 문서(document)
    • 단일 개념(파일 하나, 객체 인스턴스, 데이터베이스 row)
    • 예) /members/100, /files/star.jpg
  • 컬렉션(collection)
    • 서버가 관리하는 리소스 디렉터리
    • 서버가 리소스의 URI를 생성하고 관리
    • 예) /members
  • 스토어(store)
    • 클라이언트가 관리하는 자원 저장소
    • 클라이언트가 리소스의 URI를 알고 관리
    • 예) /files
  • 컨트롤러(controller), 컨트롤 URI
    • 문서, 컬렉션, 스토어로 해결하기 어려운 추가 프로세스 실행
    • 동사를 직접 사용
    • 예) /members/{id}/delete

 

 

참고할만한 사이트

 

REST Resource Naming Guide

In REST, having a strong and consistent REST resource naming strategy – will prove one of the best design decisions in the long term.

restfulapi.net

 

 

 

출처

 

모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

HTTP API를 만들어보자

요구사항 - 회원 정보 관리 API를 만들어라.

  • 회원 목록 조회
  • 회원 조회
  • 회원 등록
  • 회원 수정
  • 회원 삭제

 

API URI 설계

  • 회원 목록 조회 /read-member-list
  • 회원 조회 /read-member-by-id
  • 회원 등록 /create-member
  • 회원 수정 /update-member
  • 회원 삭제 /delete-member

 

 

이것은 좋은 URI 설계일까?

URI를 설계할 때 기준을 두어야할 것은 리소스 식별입니다.

 

 

API URI 고민

  • 리소스의 의미는 뭘까?
    • 회원을 등록하고 수정하고 조회하는게 리소스가 아닙니다.
    • 예) 미네랄을 캐라 -> 미네랄이 리소스
    • 회원이라는 개념 자체가 바로 리소스입니다.
  • 리소스를 어떻게 식별하는게 좋을까?
    • 회원을 등록하고 수정하고 조회하는 것을 모두 배제
    • 회원이라는 리소스만 식별하면 됩니다. -> 회원 리소스를 URI에 매핑

그래서 설계를 해보면 아래와 같이 됩니다.

 

 

API URI 설계

  • 회원 목록 조회 /members
  • 회원 조회 /members/{id} -> 어떻게 구분하지??
  • 회원 등록 /members/{id} -> 어떻게 구분하지??
  • 회원 수정 /members/{id} -> 어떻게 구분하지??
  • 회원 삭제 /members/{id} -> 어떻게 구분하지??

회원이라는 리소스만 식별했지만 조회, 등록, 수정, 삭제를 구분하기가 불가능해졌습니다. 

 

 

리소스와 행위를 분리 - 가장 중요한 것은 리소스를 식별하는 것

  • URI는 리소스만 식별
  • 리소스와 해당 리소스를 대상으로 하는 행위를 분리
    • 리소스 : 회원
    • 행위 : 조회, 등록, 삭제, 변경
  • 리소스는 명사, 행위는 동사(미네랄을 캐라)
  • 행위(메서드)는 어떻게 구분할까??

이러한 행위는 HTTP Method가 합니다.

 

 

 

HTTP 메서드 - GET, POST

 

HTTP 메서드 종류 - 주요 메서드

  • GET : 리소스 조회
  • POST : 요청 데이터 처리, 주로 등록에 사용
  • PUT : 리소스를 대체, 해당 리소스가 없으면 생성 (파일에 폴더를 넣는 것과 같다고 생각하면 됨.)
  • PATCH : 리소스 부분 변경 (회원의 이름을 바꾸거나 특정 필드를 바꿀 때 사용)
  • DELETE : 리소스 삭제

 

GET 

  • 리소스 조회
  • 서버에 전달하고 싶은 데이터는 query(쿼리 파라미터, 쿼리 스트링)을 통해서 전달
  • 메시지 바디를 사용해서 데이터를 전달할 수 있지만, 지원하지 않는 곳이 많아서 권장하지 않음

 

 

POST

  • 요청 데이터 처리
  • 메시지 바디를 통해 서버로 요청 데이터 전달
  • 서버는 요청 데이터를 처리
    • 메시지 바디를 통해 들어온 데이터를 처리하는 모든 기능을 수행한다.
  • 주로 전달된 데이터로 신규 리소스 등록, 프로세스 처리에 사용

POST 클라이언트 -> 서버 -> 클라이언트

 

POST - 요청 데이터를 어떻게 처리한다는 뜻일까에 대한 예시

  • 스펙 : POST 메서드는 대상 리소스가 리소스의 고유 한 의미 체계에 따라 요청에 포함 된 표현을 처리하도록 요청합니다. (구글번역)
  • 예를 들어 POST는 다음과 같은 기능에 사용됩니다.
    • HTML 양식에 입력 된 필드와 같은 데이터 블록을 데이터 처리 프로세스에 제공
      • 예) HTML FORM에 입력한 정보로 회원 가입, 주문 등에서 사용
    • 게시판, 뉴스 그룹, 메일링 리스트, 블로그 또는 유사한 기사 그룹에 메시지 게시
      • 예) 게시판 글쓰기, 댓글 달기
    • 서버가 아직 식별하지 않은 새 리소스 생성
      • 예) 신규 주문 생성
    • 기존 자원에 데이터 추가
      • 예) 한 문서 끝에 내용 추가하기
    • 정리 : 이 리소스 URI에 POST 요청이 오면 요청 데이터를 어떻게 처리할지 리소스마다 따로 정해야 함 -> 정해진 것이 없음

 

POST 정리

  1. 새 리소스 생성(등록)
    • 서버가 아직 식별하지 않은 새 리소스 생성
  2. 요청 데이터 처리
    • 단순히 데이터를 생성하거나, 변경하는 것을 넘어서 프로세스를 처리해야 하는 경우
    • 예) 주무에서 결제완료 -> 배달시작 -> 배달완료 처럼 단순히 값 변경을 넘어 프로세스의 상태가 변경되는 경우
    • POST의 결과로 새로운 리소스그ㅏ 생성되지 않을 수도 있음
    • 예) POST /orders/{orderId}/start-delivery (컨트롤 URI)
  3. 다른 메서드로 처리하기 애매한 경우
    • 예) JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우
    • 애매하면 POST

 

 

 

HTTP 메서드 - PUT, PATCH, DELETE

 

PUT

  • 리소스가 있으면 대체
  • 리소스가 없으면 생성
  • 쉽게 이야기해서 덮어버림
  • 중요! 클라이언트가 리소스를 식별
    • 클라이언트가 리소스 위치를 알고 URI 지정 (/members/100 - 100번째라는 리소스 위치를 알고 있음)
    • POST와 차이점

PUT은 resource를 바꾸는게 아닌 덮어버림

 

PATCH

  • 리소스 부분 변경



DELETE

  • 리소스 제거

 

 

 

HTTP 메서드의 속성

 

안전

  • 호출해도 리소스를 변경하지 않는다.

 

멱등

  • f(f(x)) = f(x)
  • 한 번 호출하든 두 번 호출하든 100번 호출하든 결과가 같아야 합니다.
  • 멱등 메서드
    • GET : 한 번 조회하든, 두 번 조회하든 같은 결과가 조회됩니다.
    • PUT : 결과를 대체합니다. 따라서 최종 결과는 같습니다.
    • DELETE : 결과를 삭제합니다. 따라서 최종 결과는 같습니다.
    • POST : 멱등이 아닙니다. 두 번 호출하면 같은 결제가 중복해서 발생할 수 있습니다.
  • 활용
    • 자동 복구 메커니즘
    • 서버가 TIMEOUT 등으로 정상 응답을 못주었을 때, 클라이언트가 같은 요청을 다시 해도 되는가? 판단 근거
  • 생각
    • Q: GET을 재 요청할 때 중간에 리소스를 변경해버리면?
      • 사용자 1 : GET -> username : A, age : 20
      • 사용자 2 : PUT -> age : 30
      • 사용자 1 : GET -> username: A, age: 30
    • A: 멱등은 외부 요인으로 중간에 리소스가 변경되는 것 까지는 고려하지 않습니다.

 

캐시가능

  • 응답 결과 리소스를 캐시해서 사용해도 되는가?
  • GET, HEAD, POST, PATCH 캐시가능
  • 실제로는 GET, HEAD 정도만 캐시로 사용 (body를 사용하지 않기 때문에 url만 Key로 잡으면 되기 때문에 심플)
    • POST, PATCH는 본문 내용까지 캐시 키로 고려해야 하는데, 구현이 쉽지 않음

 

 

 

출처

 

모든 개발자를 위한 HTTP 웹 기본 지식 - 인프런 | 강의

실무에 꼭 필요한 HTTP 핵심 기능과 올바른 HTTP API 설계 방법을 학습합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

+ Recent posts