자바 ORM 표준 JPA 프로그래밍 서적을 학습하면서, 정리하고 있는 내용입니다.

 

 

EntityManagerFactory

사용자의 요청이 들어옴에 따라 EntityManager를 생성하는 책임을 가지고 있다.

 

JPA를 동작시키기 위한 내부 객체들이 이 Factory를 통해 생성되고, 구현체에 따라선 커낵션 풀도 별도로 구성하므로 생성 비용이 크다고 한다.

 

여러 EntityManagerFactory를 생성하여 공유할 수 있으나. 하나의 객체만 생성해서 전 영역에 공유하는 것이 권장된다. (configuration lite mode를 사용하면 여러 개를 생성하고 공유할 수 있을 것 같다.)

 

 

 

EntityManager (Persistence Context)

엔티티 정보를 저장하고 있는 환경. 요청 결과가 DB에 반영되기 이전에 위치하는 논리적인 영역을 의미한다. 가상의 데이터베이스 영역이라고 봐도 무방하다.

 

사용자의 요청을 받아 Connection을 통해 DB와 로컬 트랜잭션을 맺으며, 데이터를 관리하고 영속적으로 반영하는 혹은 삭제하는 책임을 가지는 객체이다.

 

JPA의 대부분의 기능은 해당 객체가 지원한다. 커넥션과 밀접한 관계를 가지기에 스레드 간의 공유, 재사용이 어렵다.

멀티스레드에 안전하지 않은 상태가 된다는 것 아닐까?

 

 

 

Entity의 Life Cycle

  • 비영속 (new, transient) : 영속성 컨텍스트와 관계가 없는 새로 생성된 상태

      // 비영속 상태의 Entity. 즉 entityManager가 모르고 있는 Entity이다.
      Entity entity = new Entity();
      entity.setXxxA("test");
      entity.setXxxB("value");
  • 영속 (managed) : 영속성 컨텍스트에게 관리되는 상태 (환경 안에 정보가 있는 상태)

    JPA는 기본적으로 트랜잭션 안에서 데이터를 변경하여야 한다.

      // 요청에 따른 entityManager(Persistence Context) 생성
      EntityManager entityManager = entityManagerFactory.createEntityManager();
    
      // 영속성 컨택스트에 대한 글로벌 트랜잭션 관리 시작
      EntityTransaction transaction = entityManager.getTransaction();
      transaction.begin();
    
      try {
    
              // 비영속 상태의 Entity.
              Entity entity = new Entity();
              entity.setXxxA("test");
              entity.setXxxB("value");
    
              // 영속성 컨택스트에 Entity 정보를 저장. 즉 entityManager가 알고있는 Entity이다.
              entityManager.persist(entity);
    
              // transcation 결과 반영 - (DB 저장 X, 영속성 컨텍스트에만 반영)
              transaction.commit();
      } catch (RuntimeException exception) {
              // 위 로직 실패 시 결과 반영 X -> 원복
              transaction.rollback(); 
      } finally {
              // 성공, 실패 상관없이 transaction 종료
              transaction.close();
      }
  • 준영속 (detached) : 영속성 컨텍스트에 관리되다가 이후 분리된 상태

    영속성 컨텍스트가 지원하는 기능을 사용할 수 없기에 비영속 상태와 유사하다. 다른 점은 식별자 값(id)이 존재한다는 점이다.

      // 특정 회원 엔티티를 영속성 컨텍스트에서 분리한다.
      // 쉽게 말하면 해당 엔티티를 영속성 컨텍스트에 존재하는 1차 캐시에서 삭제한다.
      entityManager.detach(entity);
    
      // 해당 영속성 컨텍스트에서 관리되는 모든 엔티티를 준영속화한다.
      // 쉽게 말하면 영속성 컨텍스트의 1차 캐시를 초기화한다.
      emtityManager.clear();
    
      // 영속성 컨택스트를 종료시킨다.
      entityManager.close();
  • 삭제 (removed) : 엔티티가 삭제된 상태

      // 해당 객체를 삭제. 즉 DB에 저장된 정보를 삭제하는 요청을 전송한다. 
      entityManager.remove(entity);

    준영속과 삭제의 다른 점은 Entity가 DB에 반영되기 전이냐 이후이냐의 차이이다.

 

 

 

JPA Internal Flow

 

 

 

Persistence Context의 특징

  • 영속성 컨텍스트와 식별자 값

    영속성 컨텍스트는 @ID로 매핑된 값을 통해 엔티티를 구분한다. 즉 해당 값이 무조건 존재해야 하며, 없는 경우 예외가 발생한다.

  • 데이터베이스 저장 시점

    영속성 컨텍스트에 저장, 관리되는 엔티티는 flush()가 호출된 시점에 실제 DB에 반영된다.

  • 엔티티 관리 방식의 장점 - 아래

 

 

 

JPA 엔티티 관리 방식의 장점

  • 1차 캐시

    영속성 컨텍스트에서 관리되는 데이터를 우선적으로 조회하는 것을 의미한다.

    사용자의 요청이 종료된다면 해당 영역도 제거되기에 하나의 비즈니스 로직에서만 이점을 제공한다.

     

    전역적으로 관리되는 캐시는 2차 캐시라고 부른다.

     

     

    해당 정보가 컨텍스트 안에 존재하지 않는다면,

    • DB에서 해당 정보를 조회한 후 (SELECT)

    • 쿼리 결과를 이용하여 엔티티를 생성한 다음

    • 컨텍스트 내부에 엔티티를 저장하고 반환한다.

    • 1차 캐시는 내부적으로 Map을 통해 관리되며, @Id로 매핑된 값을 Key로 가진다. 이 값은 DB의 PK 값인 경우도 존재한다.*

       

  • 동일성 보장

    1차 캐시를 이용하여 반복 가능한 읽기 등급의 격리 수준을 Application 수준에서 제공한다. 즉 같은 트랜잭션 내에 여러 번 조회 쿼리를 진행하더라도 동일한 결과를 반환받는 것을 이야기한다.

     

    이때에는 동일성 비교가 가능하다. → Map에서 동일한 엔티티를 제공한다.

     

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

    한 트랜잭션 내에서 여러 엔티티를 등록할 때 각각의 엔티티에 대한 Insert Query를 DB에 바로 요청하지 않고, 일종의 버퍼처럼 컨택스트 내부에 존재하는 SQL 저장소에 Query를 쌓아놓다가 1번에 반영할 수 있다.

     

    entityManager.commit();

     

    큰 오버헤드를 가지는 i/o 처리를 최소화할 수 있다.

     

  • 변경 감지

    영속성 컨텍스트에게 관리되고 있는 엔티티 정보에 대해서 Service logic에서 setter 등을 통해 수정을 가할 경우, 그 변경 결과가 트랜잭션을 커밋하는 시점에 영속성 컨텍스트에게 감지되고 최종적으로 Update Query가 DB로 전송되어 최신 정보로 변경하게 된다.

     

    엔티티 정보와 엔티티 스냅샷 정보를 비교하여 감지한다.

     

    스냅샷 정보는 DB에서 값을 읽어와 캐시에 저장한 시점의 엔티티 정보를 복사하여 저장해둔 것이다. 이것을 트랜잭션이 커밋된 시점에서 엔티티 정보와 비교하여 변경된 사항들에 대해서 Update Query를 생성하는 것이다.

     

    엔티티 삭제도 동일 메커니즘을 공유하고 트랜잭션 커밋 시 Delete Query를 전송한다.

     

     

     

Flush?

영속성 컨텍스트의 변경 내용들을 데이터베이스에 반영시키는 기능이다.

 

영속성 컨텍스트에 저장된 정보를 비우는 것은 아니다.

 

 

Flush 발생 시 Flow는

  • 엔티티에 대한 변경을 감지하고
  • 추가, 수정, 삭제된 엔티티 정보에 대한 Query를 내부 SQL 저장소에 등록한 뒤
  • 해당 정보를 DB에 전송한다.

이렇게 이루어진다.

 

 

Flush를 발생시키는 방법으로는

  • em.flush()를 통한 직접 호출

  • 트랜잭션 commit() 을 통한 자동 호출

  • JPQL으로 만들어진 Query 실행 시 자동 호출

    영속성 컨텍스트에만 관리되고 있는 영속 상태의 엔티티들은 사실상 DB에 존재하지 않으므로 JPQL을 통한 Query 요청 시 결과에서 이 것들을 확인할 수 없게 된다. 이를 방지하기 위해 미리 Flush()를 호출하여 DB에 반영한 뒤 정상적인 결과를 제공하게 된다.

가 있다.

 

 

Flush 모드 옵션

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 Flush를 실행한다.
  • FlushModeType.COMMIT : 커밋할 때만 Flush를 실행한다.

'Programming' 카테고리의 다른 글

Open Authorization Framework 2.0?  (0) 2021.03.01
Live Study_Week 14. Generic  (0) 2021.02.28
객체 지향 디자인의 핵심 개념 : 책임 [ GRASP ]  (4) 2021.02.11
Java의 Reflection API와 성능 이슈?  (0) 2021.02.02
G1 GC  (0) 2021.01.26

 

 

스트림 (Stream) / 버퍼 (Buffer) / 채널 (Channel) 기반의 I/O

 

I/O?

Input : 입력, Output : 출력

Application(JVM) 외부의 입력을 받고, 내부의 결과를 외부로 전달하는 모든 흐름을 의미한다.

  • Scanner, InputStream을 통한 키보드, CSV 등의 포맷 파일 입력 등
  • OutputStream을 통한 Console 출력, TCP/IP를 이용한 외부 Application(JVM)에 전달 등

 

 

Stream - Java I/O?

데이터를 전달하고, 입력받을 때 필요한 단방향 통로를 말한다. 기본적인 Java I/O API에서는 입력과 출력을 위한 각각의 Stream이 필요하다.

  • 기본적으로 Buffer를 사용하지 않는 방식이며, 데이터를 받은 즉시 처리한다.
  • 데이터를 순차적으로 처리한다. (FIFO : 들어간 순서대로 나온다.)
  • Blocking 방식으로 동작한다. 스트림 호출 시 동작 스레드는 대기 상태가 된다.
  • 시스템 콜과 CPU 자원 사용, 커널에서 Direct Buffer → 다시 JVM 내부 Buffer로 메모리를 복사하는 과정이 존재하는 등 큰 오버헤드가 존재한다.

 

 

Buffer?

일반적으로 데이터가 한 위치에서 다른 위치로 이동하는 동안 일시적으로 데이터를 보유하는 데이터 구조 또는 메모리 영역을 말한다.

 

각각의 데이터가 큰 경우에 해당 영역에 버퍼 크기만큼의 데이터를 저장하고 전송함으로써 디스크와의 상호 작용 (호출) 수를 줄일 수 있다. - I/O 비용을 최소화

  • 데이터를 받는 순서대로 쌓아 보관하다 일정량이 되면 한번에 전송한다.
  • 데이터를 실시간으로 처리할 필요가 없고, 지속적으로 데이터를 넘긴다면 사용하는 것이 좋다.
  • 서버가 요청을 처리하는 시간이 받는 요청을 따라가지 못한다면, 요청이 유실될 수 있다. → 이때 요청들을 담아둘 버퍼를 이용할 수 있다. server socket 구현체에는 buffer가 포함되어 있다.

 

 

Channel - Java N I/O?

데이터가 양방향으로 전송될 수 있는 통로를 말하며, Java 4, 7에 추가된 N I/O 1, 2 API는 기본적으로 Channel과 Buffer 그리고 Selector를 통하여 기능을 제공한다.

 

셀럭터에는 채널들을 등록하고, 사용 가능한 채널을 통해서 각각의 데이터를 전송하거나 받고 이를 버퍼에 보관한다. Application은 버퍼를 통해 데이터를 처리한다.

  • "Non-blocking 방식으로도" 동작 가능하다. files API의 기능들은 Blocking으로 동작한다.
  • Blocking 방식으로 동작하는 API에 대해서 인터럽트를 던짐으로써 대기 상태에서 빠져나올 수 있다.
  • 기존 I/O 방식에서 접근할 수 없었던 Direct Buffer를 직접 참조할 수 있다. → 버퍼 내용을 복사할 필요가 없다.

 

 

InputStream과 OutputStream

바이트 기반 입출력 스트림들의 최상위 추상 클래스들이 InputStream, OutputStream이다.

 

InputStream?

입력과 관련된 API와 Closeable 인터페이스가 구현되어 있다.

바이트 기반의 입력 스트림을 구현하는데 사용되는 클래스이다.

 

하위 구현체로는

  • FileInputStream : 파일을 읽는 데 사용하는 구현체 - 텍스트, 바이트 코드 등을 읽을 때 사용한다.
  • FilterInputStream : InputStream을 재정의한 구현체 - 다른 입력 스트림을 포괄한다.
  • ObjectInputStream : ObjectOutputStream으로 저장된 데이터를 읽는 데 사용한다. → 역직렬화

등이 있다.

 

 

OutPutStream?

출력과 관련된 API와 Closeable 인터페이스가 구현되어 있다.

바이트 기반의 출력 스트림을 구현하는데 사용되는 클래스이다.

하위 구현체로는

  • FileOutputStream : 파일을 출력하는 데 사용하는 구현체
  • FilterOutputStream : OutputStream을 재정의한 구현체
  • ObjectOutputStream : Object를 Byte 기반으로 직렬 화할 때 사용한다.

등이 있다.

 

 

 

Byte와 Character 스트림

 

Byte Stream

기본적으로 사용되는 InputStream, OutputStream과 같이 1 바이트 단위를 처리하는 Stream들을 의미한다. 외부 요청을 서버가 처리하는 경우에는 이러한 데이터를 InputStreamReader를 통해 char로 변환하여 처리하게 되는데, 이 경우 한글 등으로 작성된 Unicode 기반의 정보들이 깨지는 문제가 발생하게 된다.

이를 위해 Character Stream이나 BufferdReader라는 것들이 만들어지게 되었다.

 

 

Character Stream

2 바이트 단위의 데이터를 처리하는 Stream들을 말하며, 기본적으로 Unicode charset에 맞게 데이터를 처리한다. 즉 Text 기반의 데이터를 입출력하는데 사용되는 Stream이다. JSON, HTML, XML...

 

 

보조 스트림

입출력 대상이 되는 파일이나 Binary 데이터를 직접 읽거나 쓰는 기능이 존재하지 않지만, 부가적인 기능을 추가하는 데 사용되는 Stream들을 말한다. Wrapper Stream이라고 하기도 한다.

 

이러한 구조를 가지는 구현 방식을 Decorator Pattern이라고 한다. 보조 스트림 중의 최상위는 FilterInputStream & FilterOutputStream이다.

 

하위 구현체로는

  • BufferedInputStream : 입력받는 바이트 스트림에 버퍼 기능을 제공하는 보조 스트림
  • BufferedOutputStream : 출력하는 바이트 스트림에 버퍼 기능을 제공하는 보조 스트림
  • BufferedReader : 문자 단위로 입력받는 스트림에 버퍼 기능을 제공하는 보조 스트림
  • BufferedWriter : 문자 단위로 출력하는 스트림에 버퍼 기능을 제공하는 보조 스트림
  • InputStreamReader : 입력되는 바이트 정보를 문자로 변환해주는 보조 스트림
  • OutputStreamReader : 출력되는 바이트 정보를 문자로 변환해주는 보조 스트림
  • DataInputStream : 입력되는 바이트를 자료형에 맞게 제공해주는 보조 스트림
  • DataOutputStream : 입력되는 바이트를 자료형에 맞게 출력해주는 보조 스트림

등이 있다.

 

 

 

표준 스트림 (System.in, System.out, System.err)

 

System.in

System.in 은 Static 한 InputStream 타입의 변수이다.

JVM이 메모리로 올라오면서 사용될 하위 구현체를 인스턴스화 하고, 기본적으로 키보드의 입력을 받을 때 사용한다.

바이트 단위로 동작한다.

 

 

System.out

System.out 은 Static 한 PrintStream 타입의 변수이다.

자체적인 Buffer를 가지고 있으며, print 요청을 모아두었다가 적절한 출력 시점에 flush()를 호출한다.

 

콘솔에 특정 문자열을 출력하는 데에 주로 사용된다. 로직 Logging을 위해서 등.. 하지만

https://lob-dev.tistory.com/entry/Logging-slf4j-Logback-Framework

여기에 정리하였듯이 Application의 전체 성능에 큰 영향을 줄 수 있으므로 조심하여야 한다.

 

이는 print가 단순히 구문에 대해서만 최적화를 시도하는 반면, Logger들은 Application이 다른 요청을 처리하지 않을 때 동작하기 때문이다.

 

 

System.err

System.err 도 System.out과 마찬가지로 static 한 PrintStream 타입의 변수이다.

 

그렇기에 마찬가지로 자체적인 Buffer를 가지고 있지만, out과 다른 점은 출력 시점을 미루지 않고, 호출되는 순간마다 flush() 하기 때문에, out과 동시로 사용했을 경우 코드 흐름과 다른 결과를 낼 수 있다.

 

그 외에는 동일한 사용법을 제공한다.

 

 

 

파일 읽고 쓰기

 

텍스트 파일 읽기

 

Java 6

해당 버전에서 Text File을 쓸 때에는 FileInputStream과 InputStreamReader를 사용한다.

BufferdReader는 부가적으로 사용한다.

File file = new File("../test.txt")

BufferedReader br = null;
StringBulider sb = new StringBulider();

try {
        br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"));

        for (String line; (line = br.readLine()) != null) {
                sb.append(line);
        }
} catch (UnsupportedEncodingException exception || FileNotFoundException exception ||
                 IOExcecption exception) {
        //
} finally {
        if (br != null) {
            try {
                    br.close();
            }    catch (IOException exception) {
                    //
            }
        }
}

 

 

Java 7

해당 버전부터는 Files API의 newBufferedReader 메서드를 이용하여 구현할 수 있다.

Path path = Paths.get("../test.txt");

StringBulider sb = new StringBulider();

try (BufferedWriter br = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {

        for (String line; (line = br.readLine()) != null;) {
                sb.append(line);
        }

} catch (IOException exception) {
        //
}

 

 

텍스트 파일 쓰기

 

Java 6

해당 버전에서는 FileOutputStream과 OutputStreamWriter 클래스를 사용하여 파일을 기록할 수 있다.

File file = new File("../test.txt");

BufferedWriter bw = null;

try {
        bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))

        bw.append("1");
        bw.newLine();
        bw.append("2");

} catch (UnsupportedEncodingException exception || IOExcecption exception) {
        //
} finally {
        if (bw != null) {
            try {
                    bw.close();
            }    catch (IOException exception) {
                    //
            }
        }
}

 

 

Java 7

해당 버전에서는 Files API의 newBufferedWriter() 메서드를 이용하여 구현할 수 있다.

Path path = Paths.get("../test.txt");

try (BufferedWriter bw = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {

        bw.append("1");
        bw.newLine();
        bw.append("2");

} catch (IOException exception) {
        //
}

 

 

Files API

파일과 관련된 다양한 Utils Method 들을 Static 하게 제공하는 API이다. Path 객체를 이용하여 **사용할 수 있다.

  • Files.createFile(Path...) : 파일을 만들 때 사용하는 Static Method.
  • Paths.get("test.csv").toAbsolutePath() : Path 객체를 사용하여 파일의 절대 경로를 획득.
  • Files.exists(Path...) : 파일 존재 여부를 확인하는 Static Method.
  • Files.createDirectory(Path...) : 디렉터리를 생성하는 Static Method.
  • Files.delete(Path...) : 디렉터리나 파일을 삭제하는 Static Method.
  • Files.write(Path, "text". getBytes(charset), StandardOpenOption) : 파일 쓰기 Static Method.
  • Files.copy(기존 Path, 복사할 Path, StandardCopyOption) : 파일 복사 Static Method.
  • Files.lines(Path). count() : 파일의 현재 줄 수를 가져오는 Static Method.

 

자료 출처

  • 자바 마스터북

'Live Study' 카테고리의 다른 글

Live Study_Week 10. Multithreading programming  (3) 2021.03.02
Live Study_Week 15. Lambda Expression  (0) 2021.03.01
Live Study_Week 12. Annotation  (0) 2021.02.02
Live Study_Week 11. Enum  (0) 2021.01.28
Live Study_Week 09. 예외 처리  (0) 2021.01.11

GRASP : General Responsibility Assignment Software Patterns?

해당 내용은 상호작용하는 클래스 혹은 객체에 책임을 할당하는데 도움이 되는 개념과 방법 즉 패턴들로 이루어져 있습니다. 이러한 내용들을 준수함으로써 좋은 객체지향 디자인 패턴을 만들어낼 수 있습니다.

 

 

 

Responsibility?

책임은 SOLID나 지금 다룰 GRASP 등 Oriented-Object-Design 패턴과 원칙에서 핵심이 되는 개념입니다. 이는 클래스와 객체가 어떤 메시지(요청)에 대해 처리할 수 있거나, 적절한 행동을 해야 하는 의무가 있는 경우 해당 객체가 이러한 책임을 가진다라고 이야기할 수 있습니다.

 

책임은 메서드를 통해 구현되게 됩니다.

 

 

Example.

  • String은 문자열을 표현하고 처리하는 책임을 가집니다.
  • File은 파일에 대한 정보를 알려주고 처리하는 책임을 가집니다.
  • DTO는 데이터를 다른 Layer로 운반하고 특별한 경우 비즈니스 로직에 따라 데이터 값을 반영시키는 책임을 가질 수 있습니다.

이런 것이 모두 책임 입니다.

 

책임의 분류는 행위 관점과 상태 관점으로 나눌 수 있습니다.

 

행위 관점.

  • 객체를 생성하는 행위, 결과를 계산하는 행위 - 객체 스스로 하는 것.
  • 다른 객체의 행동을 지시하는 것 - Controller
  • 다른 객체의 활동을 제어하고 조절하는 것 - Chain of Responsibility, Proxy

상태 관점.

  • private로 접근 제어된 데이터에 관하여 아는 것
  • 관련된 객체에 대해 아는 것
  • 자신이 유도하거나 계산할 수 있는 것에 관해 아는 것

 

 

 

Creator

새로운 객체를 생성하는 책임을 말합니다.

 

이러한 책임을 가지기 위해서는 하나 이상의 조건을 만족하여야 합니다.

  • 생성하는 객체를 포함합니다.
  • 생성하는 객체에 대한 초기화 정보를 가집니다.
  • 생성하는 객체를 사용합니다.
  • 생성하는 객체에 대한 정보를 기록합니다.

이와 관련된 것은 Creation Pattern 중 Abstract Factory, Factory Method가 있습니다.

 

 

 

Information Expert

새로운 기능이나 방법을 추가하는 것은 그에 필요한 정보를 가지는 클래스, 객체를 대상으로 하여야 합니다. 그러한 클래스, 객체는 최소한의 변경으로 기능을 구현할 수 있기 때문입니다.

 

결국 객체가 가지는 데이터에 대한 기능을 직접 처리하게끔 하라는 것입니다.

 

하지만 모든 필드에 대한 무분별한 Getter, Setter는 캡슐화를 지키지 못합니다.

해당 행위를 주관하는 객체(Client, Service)에서 데이터 흐름을 만들어내는 객체(DTO)의 데이터를 꺼내오는 것보다는 다음 행위를 하는 객체에게 해당 객체를 전달하는 것이 캡슐화를 지키는 방법입니다.

 

그리고 상태에 대한 질의 후 분기해야 하는 로직의 경우 Tell Don't Ask 원칙을 준수하는 것이 좋습니다.

Tell Don't Ask : https://martinfowler.com/bliki/TellDontAsk.html

 

 

 

Controller

요청에 대한 비즈니스 로직과 요청을 전달, 지시하는 로직은 분리되어야 합니다. 그리고 우리는 일반적으로 MVC Pattern을 통해 이러한 요구사항을 만족합니다.

 

Controller는 요청을 받고 적절한 행위를 하는 객체에게 지시하는 행위 관점의 패턴입니다.

 

이러한 책임을 가지는 객체는 내부적으로 별도의 비즈니스 로직을 가져서는 안 되며, 요청을 전달, 위임하는 것에 중점을 두어야 합니다.

 

 

 

Low Coupling

낮은 결합도를 준수하는 것은 시스템 설계의 전체적인 결합도를 낮게 유지되도록 구현하고 책임을 할당하라는 의미입니다. 객체 간의 결합이 없을 수는 없기에 우리는 최대한 영향을 주지 않도록 노력하여야 합니다.

 

이를 위해서는

  • 클래스 간의 종속성을 낮추어야 합니다.
  • 한 클래스의 변경이 다른 클래스에 주는 영향이 적어야 합니다.
  • 객체는 더 높은 재사용 가능성을 가져야 합니다.

이는 Interface 등을 이용한 상호 작용이나 Facade 패턴 등을 사용하여 이루어낼 수 있습니다.

 

반대로 높은 결합도는 각각의 객체가 서로의 구현 세부사항을 인식할 때(접근 가능할 때, 사용할 때) 발생되게 되는 것입니다. 이런 경우 한 객체의 변경이 다른 객체의 변경을 야기하게 됩니다. extend의 경우 이런 변경에 대한 파급효과가 강해지게 됩니다.

 

 

 

High Cohesion

높은 응집력을 준수하라는 것은 객체 책임에 의해 변경될 수 있는 요소들을 한 곳에 모아서 관리하라는 것입니다.

이것을 통해 요구사항으로 인해 책임의 변경이 이루어진다면, 그러한 영향이 하나의 객체에 대해서만 이루어질 수 있습니다.

 

이것과 관련된 원칙은 SOLID의 Single responsibility principle가 있습니다. 이 원칙은 객체를 변경할 수 있는 책임이 하나만 존재해야 한다는 원칙입니다.

 

 

 

Indirection

간접이란 두 객체 간의 직접적인 결합을 분리하고 간접적인 결합을 지원하게끔 설계하라는 것입니다. 이는 별도의 중개자 객체를 통해 Dependency Injection을 진행하거나, 요청에 대한 위임, 지시하는 컨트롤러와 같은 요소를 도입하는 것을 말합니다.

 

Observer, Facade, Bridge, Mediator, Adapter는 간접 접근 방식을 활용하는 패턴입니다.

 

 

 

Polymorphism

하나의 타입을 가지는 서로 다른 객체들이 동일한 책임, 메시지에 대해서 서로 다르게 반응하는 것을 말합니다. 즉 책임을 공유하는 상태입니다.

 

이러한 원칙은 객체들 사이의 대체 가능성을 의미하며 그를 통해 설계를 유연하고 재사용 가능하게 만들 수 있습니다.

 

각각의 요청에 대하여 Reflection API 등의 동적 로딩을 통하여 Runtime Dependency를 구현하거나 Chain of Responsibility와 같은 행위 패턴을 구현하고 분기를 위한 상태 변수를 전달함으로써 컴파일 시점에서 필요한 객체를 Dependency Injection 할 수 있습니다.

 

 

 

Protected Variations

요구사항에 대한 책임, 동작 변화는 객체의 내부에서만 이루어져야 하며, 공용 인터페이스를 동일하게 유지함으로써 Client 역할을 하는 객체에게는 변경 사실을 숨겨야 합니다.

 

변경될 여지가 있는 로직에 대해서는 객체 간의 캡슐화를 지켜야 한다는 것입니다.

 

이와 관련된 원칙으로는 SOLID의 Open/closed principle이 있습니다. 이 원칙은 객체의 책임의 확장과 변경이 다른 객체의 행위에 영향을 주지 않아야 된다는 것을 말합니다.

 

 

 

Pure Fabrication

객체가 광범위한 책임을 가지지 않도록 (God Class) 분리하라는 것을 말합니다. 이는 기능에 대한 공통적인, 비즈니스 로직을 하나의 객체를 만들어 담당하게끔 하는 것을 뜻합니다.

 

Layer Architecture에서 이러한 책임을 담당하는 객체를 Service라고 합니다.

 

 

 

참고 자료

'Programming' 카테고리의 다른 글

Live Study_Week 14. Generic  (0) 2021.02.28
JPA 학습 정리 Persistence Context, Entity, Flush?  (0) 2021.02.27
Java의 Reflection API와 성능 이슈?  (0) 2021.02.02
G1 GC  (0) 2021.01.26
Concurrency, Parallelism, Parallel and Concurrency  (0) 2021.01.23

 

 

Annotation을 정의하는 방법


 

Annotation이란?

JDK 1.5에 추가된 기능으로 실제 코드에 직접적인 영향을 주지 않고, 해당 코드를 처리하는 ****Compiler , Interpreter 혹은 Runtime에서 JVM에게 처리를 위한 정보를 제공하는 것이 주목적이다.

 

Single-Value Annotation

단일 변수만을 갖는 Annotation을 말하며, 값을 명시하여 데이터를 전달할 수 있다.

@SuppressWarnings, @Retention, @Target...

 

Full Annotation

두 개 이상의 변수를 갖는 어노테이션으로 여러 데이터를 ", "를 기준으로 Key : value 형식으로 전달하게 된다. @Generated...

 

Marker Annotation과 Interface

구현할 메서드, 상수 필드가 존재하지 않는 Interface나 필드 변수가 존재하지 않는 Annotation을 명칭 하며, Compiler, Interpreter, JVM에게 어떠한 정보를 제공하는 용도로만 사용된다.

Cloneable, Serializable, Remote Interface or @Overide, @Native, @Nullable, @Documented...

 

 

Annotation 종류

Compile Time에서 정보를 제공하는 Annotation (: Compiler )

  • @Override : 상위 클래스의 메서드를 재정의하였음을 나타내는 Annotation이다. 상위 클래스에 해당 메서드가 존재하지 않는 경우 Compiler가 경고하는 기능을 제공한다.
  • @SuppressWarnings : 컴파일러가 경고하는 내용을 Ignore 할 때 사용하는 Annotation이다.
  • @Native : Native Code에 의해 참조되는 상수 필드에 적용하는 Annotation으로 JNI를 통해 (C, C++ code) Native Method가 호출된다.
  • @Generated : 소스 코드를 생성하는 도구에 의해서 해당 코드가 자동적으로 생성되었음을 나타내는 Annotation이다.

 

Load-Build Time에서 정보를 제공하는 Annotation (: Compiler )

  • @Nullable : parameter에 대해 Null을 허용함을 나타내는 Annotation이다. ( JSR 305 )

 

Runtime Time에서 정보를 제공하는 Annotation (: Reflection API)

  • @FunctionalInterface : JDK 8부터 추가된 Annotation으로 해당 Interface가 하나의 메서드만을 제공하는 함수형 인터페이스임을 나타내는 Annotation이다.
  • @Deprecated : 더 이상 지원하지 않는 기능임을 알려주는 Annotation이다.
  • @SafeVarargs : Generic과 같은 가변 인자 Parameter를 사용할 때 발생하는 경고를 ignore 하는 Annotation이다.
  • @Repeatable : 해당 Annotation을 반복적으로 사용할 수 있게 지원하는 Annotation이다.
  • @Inherited : Meta Annotation 중 하나로 써 적용된 대상 Annotation을 하위 Class, Interface에게 상속시키는 Annotation이다.
  • @Retention
  • @Target
  • @Documented

 

Custom Annotation 정의

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface SampleAnnotation {

        String name() default "";

}

 

 

@Retention

 

Meta Annotation 중 하나로써 적용된 대상 Annotation이 어느 시점까지 정보를 유지할 것인지 정의하는 Annotation이다.

 

 

RetentionPolicy 필드 변수

  • SOURCE : Annotation 정보가 Compile Time에서 사라진다.
  • CLASS : Annotation 정보가 Compiler에 의해 참조되고 Runtime 시점에선 사라지게 된다.
  • RUNTIME : Annotation 정보가 Runtime 시점에 JVM이나 Reflection API에 의해 참조 가능하다.

 

 

@Target

Meta Annotation 중 하나로써 적용된 대상 Annotation이 어느 위치에 적용될 수 있는지를 정의하는 Annotation이다.

 

 

ElementType 필드 변수

  • TYPE : Class, Interface 등의 Level에 선언 가능하다.
  • METHOD : 메서드에 선언 가능하다.
  • FIELD : Enum, 상수, Field 변수에 대해 선언 가능하다.
  • PARAMETER : 매개변수에 선언 가능하다.
  • CONTRUCTOR : 생성자 Type에 선언 가능하다.
  • LOCAL_VARIABLE : 지역 변수에 대해 선언 가능하다.
  • ANNOTATION_TYPE : Annotation에 선언 가능하다.
  • PACKAGE : 패키지에 선언 가능하다.

 

 

@Documented

Meta Annotation 중 하나로써 적용된 대상 Annotation에 대한 정보를 Javadoc에 나타내는 Annotation이다.

 

 

Annotation Processor

Compile 시점에서 정의된 Annotation을 분석하고 처리하기 위해 사용되는 API이다.

 

소스 코드를 구문 분석하여 나타나는 결과인 Abstract Syntax Tree를 조작하여 코드를 추가하거나, 설정 정보를 변경하거나, 컴파일 에러, 경고 등을 생성하기도 한다.

 

Annotation Processing Flow

  1. 자바 컴파일러에 의해 빌드가 시작된다.

  2. Annotation Processor들이 각각의 처리를 시작한다.

  3. 주석이 달린 Class, Method, Field를 찾는다.

  4. 발견된 부분들의 정보를 이용하여 코드를 생성, 검증한다. (Lombok 등의 코드 생성 시점)

  5. 생성된 정보, 코드, 문자열 등을 class로 작성한다.

  6. 컴파일러가 모든 Processor가 실행되었는지 확인하고 다음 Round를 진행하거나 실행 종료한다.

    3~6을 반복하게 되며, 각 Round 마다 처리할 각각의 Annotation을 식별한다.

 

Annotation Processor 구현

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE})
public @interface SampleAnnotation {

        String name() default "";

}

//------

@SupportedAnnotationTypes("com.example.annotation.SampleAnnotation")
@AutoService(Processor.class)
public class SampleProcessor extends AbstractProcessor {

        @Override
        public SourceVersion getSupportedSourceVersion() {
                return SourceVersion.latestSupported();
        }

        @Override
    public boolean process(Set<? extends TypeElement> annotations, 
      RoundEnvironment roundEnv) {

                for (TypeElement annotation : anotations) {
                        for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {
                                processingEnv.getMessager().printMessage(Diagnostic.kind.NOTE, "found SampleAnnotation : " + element);
                        }
                }

                return true;
    }

}

 

 

 

참고 자료

'Live Study' 카테고리의 다른 글

Live Study_Week 15. Lambda Expression  (0) 2021.03.01
Live Study_Week 13. I/O  (0) 2021.02.20
Live Study_Week 11. Enum  (0) 2021.01.28
Live Study_Week 09. 예외 처리  (0) 2021.01.11
Live Study_Week 08. 인터페이스  (0) 2021.01.05

http://jessezhuang.github.io/article/java-reflection-test/

 

Reflection이란?


Compile Time에 Class나 Method 명을 알지 못하더라도 Runtime에 Type, Classpath를 이용하여 인스턴스화, 객체의 상태, 메서드 정보 등을 가져올 수 있도록 지원하는 API이다.

 

 

사용하는 Library, Framework, API, Feature

  • Jackson, GSON 등의 JSON Serialization Library

  • Log4 j2, Logback 등의 Logging Framework

  • Apache Commons BeanUtils 등의 Class Verification API

  • Spring의 @Autorwired와 같은 DL, DI 기능 (: processInject(), inject() Method )

    내부적으로 Spring의 ReflectionUtils라는 Abstraction Library를 사용한다.

  • Eclipse, Intellij 등의 IDE, Junit, Hamcrest와 같은 Test Framework

등이 있다.

 

 

 

Refliection Flow?


  1. JVM의 ClassLoader는. class를 Perm Gen (8+ : Metaspace)에 Load 한다.

  2. Class 형식의 Instance가 생성된다. Type.class 형식으로 Heap에 저장된다.

  3. Animal.class, animal.getClass(), = Class.forName(Classpath) 등으로 접근 가능하다.

     // 1
     Class<Animal> clazz = Animal.class;
    
     // 2
     Animal animal = new Animal();
     Class<? extends Animal> aClass = animal.getClass();
    
     // 3
     try {
             Class<?> aClass1 = Class.forName("...Animal");
     } catch (ClassNotFoundException e) {
             e.printStackTrace();
     }

 

 

 

Reflection API 사용 시 장단점


장점

  • Runtime 시점에서 사용할 Instance를 선택하고 동작시킬 수 있는 유연성을 제공한다.
  • 특정 객체를 감싸 추가적인 기능을 제공할 수 있다. RTW : JDK Dynamic Proxy

단점

  • Compile time에 Type, Exception 등의 검증을 진행할 수 없다. Runtime에서 가져오기때문
  • Runtime에서 Instance가 선택되기 때문에 해당 로직의 구체적인 동작 흐름을 파악하는 것에 대해 어려움을 가지게 된다.
  • Private 접근 제어자로 캡슐화된 필드, 메서드에 대해 접근 가능하기 때문에 기존 동작을 무시하고 깨트리는 행위가 가능해진다. Singleton 객체, Internal API 사용 등
  • Java 보안 관리자에게 Runtime 때 특정 권한을 지정받게 되는데, 이는 Linux의 Root 계정처럼 보안 취약점을 만들고, 제약 사항을 위반할 수 있다.

 

 

Reflection 성능 이슈?


"Java Reflection API가 느리고 높은 비용을 사용한다"라는 이야기는 흔히 듣게 되는 이야기이며, 이는 Reflection API의 Method Invoke() 실행 시간을 측정하는 ( 정적 메서드 디스패치와 비교하는 ) 많은 테스트들에서 나타나는 결과이다.

하지만 이는 Reflection만을 테스트하는 것이 아니라, 동적으로 Class를 Load 하고, Heap에 객체를 띄우는 선행 절차가 존재하기에 나타나는 결과이다.

 

그렇다고 Reflection API가 느리지 않고, 동일한 비용을 사용한다는 것은 아니다. 그러한 이유 중 하나로는 Reflection을 통한 초기 호출 시 JVM이 해당 정보를 미리 최적화할 수 없기 때문이다.

JIT Compiler의 Bytecode Caching, Opcode Optimization.. 등

 

즉 초기 호출 이후로는 캐싱을 통해서 Reflection API를 통한 메서드 호출도 최적화된다는 것을 의미한다.

초기 호출에서는 5배 이상의 차이를 보이더라도 이후 호출부터는 그러한 간격이 줄어들게 된다. 하지만 setAccessible과 같은 Class 정보 설정 기능을 사용하는 경우에는 그렇지 않을 수 있다.

 

 

 

2월 8일 추가 

테스트 코드


public class ReflectionTest {


	// get class name
	
	// Reflection API 19~25ms
	@Test
	public void reflectionTest_getClassName() throws ClassNotFoundException {
		// instance을 생성하지 않고 Metaspace 영역의 Type 정보를 가져온다.

		for (int i = 0; i < 1000; i++) {
			String name = Class.forName("notefive.oop.Animal").getSimpleName();
			System.out.println(name);
		}
	}

	// Non-Reflection API 17~22ms
	@Test
	public void reflectionTest_getName() {
		// 생성된 instance를 기반 즉 Heap 영역의 Type 정보를 가져온다.

		Animal animal = new Animal();
		for (int i = 0; i < 1000; i++) {
			String name = animal.getClass().getSimpleName();
			System.out.println(name);
		}
	}

	
	
	// method invoke
	
	// Reflection API 34~46ms
	@Test
	public void reflectionTest_getClassAndGetMethod() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
		
		for (int i = 0; i < 1000; i++) {
			Object o = Class.forName("notefive.oop.Animal").newInstance();
			Method getName = Class.forName("notefive.oop.Animal").getMethod("getName");
			String res = (String) getName.invoke(o);
		}
	}

	// Non-Reflection API 27~32ms
	@Test
	public void reflectionTest_getMethod() {

		for (int i = 0; i < 1000; i++) {
			String res = new Animal().getName();
		}
	}
}

Reflection API의 경우 초기 호출 시에 JVM의 Class Loader와 Excuter Engine을 통해 Class의 Metadata를 가져온 이후에는 Non-Reflection 방식과 동일하게 동작한다. 매번 인스턴스를 생성하고 메서드를 호출하는 절차가 진행하게 된다.

 

즉 초기 호출을 제외하고는 Reflection API를 사용하는 것이 별 차이가 없음을 알게 되었다. 

특히 이미 Class Compile시 Loading 된 Class 정보를 Reflection API를 통해 가져오는 경우에는 기존 방식과 비교하여 오버헤드가 사실상 존재하지 않았다.  

이러한 결과를 보았을 때 단순히 성능이 좋지 않다는 이야기 때문에 도입하지 못했던 것에 대해 Reflection API를 고려해볼 수 있는 어떠한 지표를 얻게 된 것 같다.

 

위에 작성된 getClassName 관련 테스트에선 Reflection API가 인스턴스를 생성하지 않고 정보를 가져오기 때문에, 인스턴스를 생성하여 정보를 가져오는 Non-Reflection API 방식이 일정 반복 횟수 이후에 GC가 발생하여 상대적으로 느린 실행시간을 보였었다.

 

하지만 이 부분은 잘못된 테스트 방식일 수도 있기에 수정을 고려해볼 것이다.

 

 

참고 자료

해당 예제는

01. RESTful 개념과 사전 지식

에서 정리하였던 일부 내용들을 복습하는 용도로 작성된 글입니다.

 

 

 

Spring RESTful API


개발환경 구성하기

 

프로젝트 생성

https://start.spring.io/

기호에 맞게 Maven, Gradle, Java 버전 등을 선택하시면 됩니다.

 

사용되는 의존성은

  • Spring Web
  • Lombok
  • H2 Database
  • Validation
  • Spring Data JDBC입니다.

 

해당 프로젝트는 단일 Entity를 가지는 단순한 RESTful API 예제입니다.

 

HATEOAS를 만족시키진 않았습니다. 해당 내용과 관련해서 인프런에 백기선 님의 RESTful 강의를 수강해보시길 추천드립니다.

 

 

 

Entity, DTO 만들기


@Builder
@Getter
public class Notice {

    private final Long id;
    private final String author;
    private final String title;
    private final String content;
    private final LocalDateTime createDate;
    private final LocalDateTime modifyDate;

}

Builder 패턴과 Getter, final을 사용하여 불변 객체로 만듭니다. 이를 통해 변경될 수 있는 지점을 제거하고, 별도 동기화 없이 멀티스레드 환경에서 안전하게 사용하는 것이 주목적입니다.

 

 

불변 객체를 만드는 방법?

  • 모든 필드를 Final로 만듭니다.
  • 모든 필드를 비공개(Private)로 설정합니다.
  • 필드에 대한 접근자(Getter)만을 제공합니다.
  • 필드에 대한 변경자(Setter)를 제공하지 않습니다.
  • 컬랙션이나 Date(변경되는 객체 등)를 사용하는 필드에 대하여서는 복사본이나 수정 불가능한 타입의 구현체로 반환합니다. Collections.UnmodifiableCollection 등

 

모든 코드가 멀티스레드에 취약한 것은 아닙니다. 상태를 가지는 싱글톤 객체를 사용할 때 , 다른 스레드가 가시 할 수 있는 필드 등에서 여러 변경을 시도하는 경우 문제가 발생할 수 있습니다.

 

 

해당 코드에서 사용된 Lombok Annotation

  • @Builder : 객체 생성 방식을 빌더 패턴으로 제공합니다. 몇몇 다른 라이브러리 관점에서는 (Mybatis, Jackson 등) AllArgumentContructor와 동일한 취급을 받습니다.

      // example
      Notice notice = Notice.builder()
                                          .author()
                                          .title()
                                          .content()
                                          .createDate()
                                          .modifyDate()
                                          .build();
  • @Getter : 현재 객체의 필드에 대한 Getter Method를 생성해줍니다. 생성되는 메서드 이름은 Java Bean Properties를 따릅니다. (getXxx, setXxx, isXxx, hasXxx)

 

 

 

CreateNoticeDto

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class CreateNoticeDto {

    @NotBlank
    private String author;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

    public Notice toEntity() {
        return Notice.builder()
                .author(author)
                .title(title)
                .content(content)
                .createDate(LocalDateTime.now())
                .modifyDate(LocalDateTime.now())
                .build();
    }
}

이 DTO는 Client에서 넘어온 요청을 담는 객체입니다. 해당 프로젝트에서도 별도의 처리 없이 Data Transfer Object의 역할만을 담당하여 Controller와 Service를 이동하게 됩니다.

 

toEntity라는 Converting 메서드가 존재하는데요. 해당 DTO는 Service단에서 Entity로 변환되고 DAO로 접근하게 됩니다. 해당 메서드가 Entity에 있지 않고 DTO에 있는 이유는 Client와 밀접한 관계를 가지는 DTO의 요구사항 변경이 Entity에 영향을 주는 것을 방지하는 목적으로 작성하게 되었습니다.

 

 

해당 코드에서 사용된 Lombok Annotation

  • @AllArgsConstructor : 객체의 모든 필드를 가지는 생성자를 만드는 어노테이션입니다.
  • @NoArgsConstructor : 인자가 없는 생성자를 만드는 어노테이션입니다.

 

해당 코드에서 사용된 javax의 validation Annotation

  • @NotBlank : String에 적용되는 Annotation으로 null, "", " "인지 확인합니다. 조건이 충족된다면 MethodArgumentNotValidException이 발생하게 됩니다.

    문자열과 관련해서 NotNull과 NotEmpty가 합쳐진 Annotation이라고 보아도 무방합니다.

 

MethodArgumentNotValidException?

해당 Exception은 Validation Annotation에 의해 발생하게 되는 RuntimeException으로 특이하게 BindingResult 타입의 필드 변수가 존재하는데요. 해당 변수에는 Validation을 통과하지 못한 필드 명과 메시지가 저장되게 됩니다.

 

 

 

UpdateNoticeDto

@Getter
@AllArgsConstructor
@NoArgsConstructor
public class UpdateNoticeDto {

    @NotBlank
    private String author;

    @NotBlank
    private String title;

    @NotBlank
    private String content;

}

해당 DTO는 위와 다르지 않으므로 넘어가겠습니다.

 

 

 

DAO 만들기


해당 프로젝트는 빠른 시작(?)을 위하여서 JDBCTemplate를 사용하였습니다.

 

Notice를 저장하기

public int save(Notice notice) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        final String sql = "INSERT INTO NOTICE(AUTHOR, TITLE, CONTENT, CREATE_DATE, MODIFY_DATE) VALUES(?, ?, ?, ?, ?)";

        jdbcTemplate.update(con -> {
            PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            ps.setString(1, notice.getAuthor());
            ps.setString(2, notice.getTitle());
            ps.setString(3, notice.getContent());
            ps.setString(4, String.valueOf(notice.getCreateDate()));
            ps.setString(5, String.valueOf(notice.getModifyDate()));
            return ps;
        }, keyHolder);

        return Objects.requireNonNull(keyHolder.getKey()).intValue();
    }

CRUD 중 C! 객체를 DB에 저장하는 로직입니다. 단순히 저장만 할 것이라면 쿼리와 인자만을 사용해도 되지만, 위와 같이 Key(Notice ID) 값을 클라이언트에게 넘겨줌으로써 그에 대한 정보나,  상태를 가질 수 있도록 할 수 있습니다.

 

예를 들어 HATEOAS를 지원하는 RESTful API라면, 현재 호출된 URI가 api/notices 일 텐데요. LinkBuilder를 이용하여서 클라이언트에게 api/notices/{생성된 id}를 전달할 수 있고, 클라이언트는 이 링크를 사용만 함으로써 쉽게 생성된 정보를 확인할 수 있는 페이지로 전환 가능할 것입니다.

 

 

Notice를 조회하기 + RowMapper

public List<Notice> findAll(Long page, Long offset) {
        final String sql = "SELECT * FROM NOTICE ORDER BY CREATE_DATE DESC LIMIT " + page + " OFFSET " + offset;
        return jdbcTemplate.query(sql, rowMapper());
}

private RowMapper<Notice> rowMapper() {
        return (rs, rowNum) -> Notice.builder()
                .id(rs.getLong("ID"))
                .author(rs.getString("AUTHOR"))
                .title(rs.getString("TITLE"))
                .content(rs.getString("CONTENT"))
                .createDate(rs.getTimestamp("CREATE_DATE").toLocalDateTime())
                .modifyDate(rs.getTimestamp("MODIFY_DATE").toLocalDateTime())
                .build();
}

CRUD 중 R! DB에서 조건에 맞는 객체를 가져와 클라이언트에게 전달하는 로직입니다. 쿼리에 LIMIT와 OFFSET을 이용함으로써 간단한 페이징 기능을 구현하였습니다.

 

RowMapper는 반환되는 ResultSet을 객체로 변환하는 로직을 구현하는 것입니다. 각각의 결과 행들을 Mapping 합니다.

 

 

Notice를 수정하기

public int updateById(UpdateNoticeDto noticeDto, Long noticeId) {
        final String sql = "UPDATE NOTICE SET AUTHOR = ?, TITLE = ?, CONTENT = ?, MODIFY_DATE = ? WHERE ID = ?";
        return jdbcTemplate.update(sql, noticeDto.getAuthor(), noticeDto.getTitle(),
                noticeDto.getContent(), LocalDateTime.now(), noticeId);
}

CRUD 중 U! Client에서 전달한 notice의 수정 정보를 DB에 반영하는 로직이 작성되어 있습니다.

 

 

Notice를 삭제하기

public int deleteById(Long id) {
        final String sql = "DELETE FROM NOTICE WHERE ID = ?";
        return jdbcTemplate.update(sql, id);
}

마지막으로 CRUD 중 D! Client에서 전달한 Notice ID를 삭제하는 로직이 작성되어 있습니다. 현재 작성된 로직은 Hard Delete 방식으로 바로 데이터를 삭제하게 되는데, 다른 방법으로는 Soft Delete라는 것이 존재합니다.

 

Soft Delete는 테이블 칼럼의 Flag를 변경하여 최종 결과에서 필터링하거나, 별도의 테이블에 데이터를 이동시켜 관리하는 것으로, Batch와 조건식을 통해서 일정 시간, 일정 상황에 데이터가 삭제되게끔 작성할 수 있습니다.

 

 

Domain Service 만들기


해당 프로젝트에서 Service는 단순하게 Controller와 DAO를 분리하는 Layer의 용도로만 작성되었습니다. 해당 내용에 대해서는 계층형 아키텍처를 참고하는 것이 좋습니다.

 

Service Layer의 주요 목적은 비즈니스 로직 수행, 트랜잭션 관리(글로벌 트랜잭션 경계 설정 등), 접근 권한 확인, Controller와 DAO의 결합 분리 등이 있다고 생각합니다.

 

Service에서 save 호출하기

@Service
public class NoticeService {

    private final NoticeDao noticeDao;

    public NoticeService(NoticeDao noticeDao) {
            this.noticeDao = noticeDao;
    }

    public int save(CreateNoticeDto noticeDto) {
            Notice notice = noticeDto.toEntity();

            int result = noticeDao.save(notice);
            if (isNotReflected(result)) {
                    throw new RuntimeException("Notice save Failed");
            }
            return result;
    }

크게 복잡한 로직은 존재하지 않습니다. DTO를 Entity로 Converting 하고 DAO의 save를 호출합니다. 호출된 결과를 int 값으로 받게 되는데, 1 이상이 아니라면 결과가 반영되지 않았으므로 Runtime Exception을 발생시키게 됩니다.

Service 로직에서 발생하는 Exception들은 이후 ExceptionHandler를 통해 Handling 합니다.

@Service Annotation은 Component를 확장한 MetaAnnotation 중 하나로 ComponentScan의 대상입니다.

 

 

Service에서 findAll 호출하기

public List<Notice> findAll(Long page, Long offset) {
        return noticeDao.findAll(page, offset);
}

Dao의 findAll을 호출하고 그 결과를 Controller로 전달합니다.

 

 

Service에서 update 호출하기

public void updateById(UpdateNoticeDto noticeDto, Long noticeId) {
        if (isNotReflected(noticeDao.updateById(noticeDto, noticeId))) {
            throw new RuntimeException("Notice update Failed");
        }
}

save와 마찬가지로 로직을 호출하고 그 반영 결과를 검증하여 Runtime Exception을 발생시킵니다.

 

 

Service에서 delete 호출하기

public void deleteById(Long noticeId) {
        if (isNotReflected(noticeDao.deleteById(noticeId))) {
            throw new RuntimeException("Notice delete Failed");
        }
}

update와 동일함으로 넘어가겠습니다.

 

 

Service의 결과 검증 메서드

private boolean isNotReflected(int result){
        return result < 1;
}

 

 

 

 

Domain Controller 만들기


 

Controller의 save

@RestController
@RequestMapping("/api")
public class NoticeController {

    private final NoticeService noticeService;

    public NoticeController(NoticeService noticeService) {
        this.noticeService = noticeService;
    }

    /**
     * 사용자의 글 작성
     *
     * @return 작성된 notice 에 대한 id 반환
     * @author lob
     */
    @PostMapping("/notices")
    public ResponseEntity<NoticeInfo> createNotice(@Valid @RequestBody CreateNoticeDto noticeDto) {

        int result = noticeService.save(noticeDto);
        return ResponseEntity.status(HttpStatus.OK).body(new NoticeInfo(result, "notice created"));
    }

Controller 코드의 최상단입니다. Class Level에는 @RequestMapping("path 정보")를 적용하여 모든 메서드의 URL prefix를 설정하였습니다. 이후 NoticeService를 생성자 주입으로 DI 받고, 메서드를 호출합니다.

 

createNotice는 @PostMapping("/notices")로 설정되어 있는 상태인데, 이는 POST.../api/notices 형태의 요청과 Mapping 되는 것임을 나타냅니다.

 

메서드의 인자로 DTO를 받고 해당 DTO에 대한 @Valid와 @RequestBody를 적용하였습니다. 이는 json으로 된 요청 정보를 DTO 생성 후 매핑하고, DTO Field Level에 설정된 Valid Annotation을 통해 Validation을 진행한다는 것을 나타냅니다. @NotBlank, NotEmpty..

 

json Mapping 정보를 DTO로 Mapping 할 때 필드가 private로 캡슐화되어 있는 상태라면, Getter를 통해 필드 이름을 특정하고 직렬 화합니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @RestController : Controller와 ResponseBody 기능이 Annotation입니다.

    • @Controller는 요청을 받고 결과를 반환하는 역할을 하는 Bean을 등록할 때 사용되며 내부적으로 Component Annotation이 적용되어 있습니다.

    • @ResponseBody는 컨트롤러가 반환하는 결과를 Http Message Body에 저장합니다.

      JSON 형식을 반환한다라는 글들이 많은데, 실제로는 HTTP Header의 Content-type 값을 따릅니다. 즉 byte 값, XML, TEXT 등으로도 반환된다는 것입니다.

  • @PostMapping : HTTP POST Method 형식을 처리하는 것을 나타내는 Annotation입니다. 이는 RequestMapping을 확장한 것이며, URL 값을 나타내는 value, Headers, 요청 타입을 협상하고 반환 타입을 지정하는 produces 필드를 자주 사용하게 됩니다.

  • @RequestBody : 요청된 HTTP Message Body에 저장된 값을 직렬 화하여 객체로 변환하는 데 사용되는 Annotation입니다. JSON, XML, Text 등을 가져올 수 있습니다.

  • @Valid : Object 필드에 선언된 Valid 조건들을 검증하도록 하는 Annotation입니다.

 

 

Controller의 findAll

/**
     * @return 작성된 순서대로 10개씩 notice 반환
     * @author lob
     */
    @GetMapping("/notices")
    public ResponseEntity<List<Notice>> findAll(
            @RequestParam(defaultValue = "10", required = false) Long page,
            @RequestParam(defaultValue = "0", required = false) Long offset) {

        List<Notice> notices = noticeService.findAll(page, offset);
        if (CollectionUtils.isEmpty(notices)) {
            return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
        }
        return ResponseEntity.status(HttpStatus.OK).body(notices);
}

findAll은 @GetMapping("/notices")으로 설정된 상태인데 이는 GET.../api/notices 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @GetMapping *: HTTP GET Method 형식을 처리하는 것을 나타내는 Annotation입니다. 이것도 RequestMapping을 확장한 Annotation입니다.*

  • @RequestParam : HTTP URL에 붙어서 날아오는 QueryString을 변수에 매핑하는 Annotation입니다. defaultValute를 통해 요청에 담겨오지 않는 경우의 값을 설정할 수 있으며, required를 통해 요청에 QueryString 존재 유무에 따라서 Exception을 발생시킬지를 설정할 수 있습니다.

    required의 기본 값은 True입니다.

 

 

Controller의 update

    /**
     * @return notice 수정 후 안내 문자열 반환
     * @author lob
     */
    @PutMapping("/notices/{noticeId}")
    public ResponseEntity<String> updateById(@Valid @RequestBody UpdateNoticeDto noticeDto,
                                             @PathVariable Long noticeId) {

        noticeService.updateById(noticeDto, noticeId);
        return ResponseEntity.status(HttpStatus.CREATED).body("Notice Updated");
    }

updateById은 @PutMapping("/notices/{noticeId}")으로 설정된 상태인데 이는 PUT.../api/notices 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @PutMapping : HTTP PUT Method 형식을 처리하는 것을 나타내는 Annotation입니다. RequestMapping을 확장하였습니다.

 

 

Controller의 delete, NoticeInfo Object

    /**
     * @return notice 삭제 후 안내 문자열 반환
     * @author lob
     */
    @DeleteMapping("/notices/{noticeId}")
    public ResponseEntity<String> deleteById(@PathVariable Long noticeId) {
        noticeService.deleteById(noticeId);
        return ResponseEntity.status(HttpStatus.OK).body("Notice Deleted");
    }

    @Getter
    @AllArgsConstructor
    private static class NoticeInfo {
        private final int NoticeId;
        private final String message;
    }

deleteById은 @DeleteMapping("/notiecs/{noticeId}")으로 설정된 상태인데 이는 Delete.../api/notieces 형태의 요청과 Mapping 됨을 알 수 있습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @DeleteMapping : HTTP DELETE Method 형식을 처리하는 것을 나타내는 Annotation입니다. RequestMapping을 확장하였습니다.

 

 

Exception Handler 적용해보기


@ControllerAdvice("com.example.rest.notice")
public class NoticeExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    protected ResponseEntity<ErrorResponse> HandlerRuntimeException(RuntimeException exception) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse(exception.getMessage()));
    }

    @Getter
    @AllArgsConstructor
    private static class ErrorResponse {
        private final String errorMessage;
    }
}

간단하게 작성한 Handler입니다. Service에서 발생하는 RuntimeException에 의한 White Page를 방지하고, Client에게 별도의 데이터를 제공합니다.

예제의 간소화를 위하여 RuntimeException 형식만을 지정하였습니다.

 

 

해당 코드에서 사용된 Spring Annotation

  • @ControllerAdvice : Spring Application에서 전역적인 예외 처리를 위해 사용되는 객체에 적용하는 Annotation입니다. Controller에서 결과를 반환한 이후 즉 AfterReturning 시점에서 적용되며, 내부에 정의된 ExceptionHandler 설정에 따라 처리하게 됩니다.

    Class Level Annotation이며, 특정 Package에만 적용하는 것이 가능하고 Order Annotation을 통해 적용 우선순위도 지정할 수 있습니다.

  • @ExceptionHandler : Spring Application에서 특정 예외 처리를 위해 사용되는 Method Level의 Annotation입니다. 기본적으로 @ExceptionHandler(XxxException.class) 형식으로 정의되어 해당 Exception을 가로채고 매개변수로 받아올 수 있습니다.

 

 

02-01 12 : 58 추가

간단한 MockMvc Test 작성해보기


@SpringBootTest
@AutoConfigureMockMvc
class NoticeControllerTest {

	@Autowired
	MockMvc mockMvc;

	@Autowired
	ObjectMapper objectMapper;

	CreateNoticeDto createNoticeDto;
	UpdateNoticeDto updateNoticeDto;

	@BeforeEach
	void setUp() {
		createNoticeDto = new CreateNoticeDto("author", "title", "content");
		updateNoticeDto = new UpdateNoticeDto("update", "update", "update");
	}

	@Test
	void noticeControllerTest_createAndFind() throws Exception {

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(get("/api/notices")
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());

	}

	@Test
	void noticeControllerTest_createAndFindAndUpdate() throws Exception {

		mockMvc.perform(post("/api/notices")
				.content(objectMapper.writeValueAsString(createNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isCreated());

		mockMvc.perform(put("/api/notices/{noticeId}", 1L)
				.content(objectMapper.writeValueAsString(updateNoticeDto))
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());

		mockMvc.perform(get("/api/notices")
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());
	}

	@Test
	void noticeControllerTest_deleteById() throws Exception {

		mockMvc.perform(delete("/api/notices/{noticeId}", 2L)
				.contentType(MediaType.APPLICATION_JSON))
				.andDo(print())
				.andExpect(status().isOk());
	}

}

 

 

코드 설명 추가 예정

 

 

 

지금까지 간단하게 Controller부터 Service, Dao까지 구현해보았습니다. 

 

 

관련된 기능, 어노테이션에 대하여서 추가적인 예제가 필요하신 분들은

www.baeldung.com/rest-with-spring-series

 

REST with Spring Tutorial | Baeldung

Step by step tutorial on building a REST API with Spring (and securing it with Spring Security).

www.baeldung.com

해당 사이트를 확인해보시길 바랍니다.

 

 

enum 정의하는 방법

해당 타입은 JDK 5에 추가되었으며, 상수들을 정의, 열거하는 데 사용되는 키워드이다.

 

기존에 사용되던 final static keyword를 사용한 변수 선언 등을 대체하였다.

 

 

해당 키워드의 특징으로는

  1. 모든 Java의 enum들은 암시적으로 java.lang.Enum Class를 확장한다.
  2. 정의된 상수 하나당 하나의 인스턴스가 생성된다. public static final type instance
  3. 데이터를 비교할 때 실제 값과 함께 타입을 체크한다.
  4. 메서드와 필드를 추가하고 인터페이스를 구현할 수 있다.
  5. 다른 Primitive Type Variable 과 같이 Switch Statement에서 사용할 수 있다.
  6. 정의 시 Class Level에서 사용된다.

정도가 있다.

 

 

enum 선언, 할당 방식

 

JDK 5.0 이전의 방식

Class Role {
        public final static int ADMIN = 0;
        public final static int USER = 1;
        public final static int GUEST = 2;
}

 

일반적인 선언, 할당

// 선언
public enum Role {
        ADMIN, USER, GUEST 
}

// 할당
Role role;

role = Role.ADMIN;
role = Role.USER;
role = Role.GUEST;

 

enum Class 내부에 Method, Variable 선언

public enum Role {
        ADMIN, USER, GUEST;

        //내부 변수
        private String a;
        private String b;

    //생성자 
        Role(String a, String b) {
                this.a = a;
                this.b = b;
        }

        public String getA() {
                return a;
        }

        public String getB() {
                return b;
        }    
}

 

 

enum 사용 방식

 

조건문 : if

if (role == Role.USER) {
        System.out.println("USER");
}
if (role == Role.GUEST) {
        System.out.println("GUEST");
}
if (role == Role.ADMIN) {
        System.out.println("ADMIN");
}

 

조건문 : Switch

switch (role) {
        case USER : 
                System.out.println("USER"); 
                break;
        case GUEST : 
                System.out.println("GUEST"); 
                break;
        case ADMIN : 
                System.out.println("ADMIN"); 
                break;
}

 

반복문 : for-each

for (Role role : Role.values()) {
        System.out.println(role);
}

 

 

enum이 제공하는 메서드 (values()와 valueOf())

컴파일러가 자동으로 추가해주는 메서드들이라고 한다.

 

values()

정의된 모든 상수를 배열에 담아 반환한다.

Role[] role = Role.values();

for (Role r : role) {
        System.out.println(r);
}

for (Role role : Role.values()) {
        System.out.println(role);
}

 

valueOf()

정의된 상수와 변수로 넘긴 문자열을 비교한 뒤 그 결과를 반환한다.

 

전달된 문자열과 동일한 상수가 없을 경우 NPE가 발생한다.

role = Role.valueOf("USER");

// NPE
role = Role.valueOf("aaa");

 

기타 메서드

 

ordinal()

현재 상수 값이 선언된 enum Class 내에서 어느 위치(index)에 있는지 반환하는 메서드이다.

상수의 위치가 변경되거나 새로운 상수가 추가된다면 해당 메서드를 사용한 로직들은 깨지게 된다.

int idx = role.ordinal();

 

compareTo()

상수의 ordinal 값을 이용하여 두 개의 상수를 비교하는 메서드이다. 반환 값은 int이다.

// return = self.ordinal - role.ordinal;
Role.USER.compareTo(role);

 

name()

정의된 상수의 이름을 문자열 형식 값으로 반환한다.

 

해당 메서드는 final 메서드이기에 재정의가 불가능하다.

String str = Role.User.name();

 

toString()

기본 정의로는 상수의 문자열 형식 값을 반환한다.

 

해당 메서드는 재정의가 가능하다.

String str = Role.USER.toString();

 

 

java.lang.Enum

Java의 enum들이 암시적으로 확장하는 Super Class이다.

 

컴파일러가 호출하는 생성자와 위에서 작성된 메서드를 제외하고도 Object.Class의 메서드들과 getDeclaring()가 포함되어 있다.

 

 

EnumSet

Set 인터페이스와 enum을 함께 사용하기 위해 일부 구현한 추상 클래스이다.

하위 구현체로는 RegularEnumSet, JomboEnumSet이 있다.

 

RegularEnumSet

단일 long의 각각의 비트가 enum 값을 나타내는 Set이다. 최대 64개의 요소를 저장한다.

 

JomboEnumSet

long 배열을 이용하여 enum 값을 나타내는 Set이다. 위와 같이 하나의 long 요소는 64개를 저장한다.

 

 

해당 Set의 특징

  1. 동기화되지 않았다.
  2. Null을 허용하지 않는다.
  3. Fail-safe Iterator를 사용한다. 이는 복제본을 만들고, 그 요소를 순회하는 방식을 말한다.
  4. enum 유형만을 사용하는 것을 강제한다.
  5. 각 요소의 저장되는 순서는 상수의 정의된 순서를 따른다.
  6. 모든 메서드는 산술 비트 연산을 사용하여 구현된다. 매우 빠른 성능을 지닌다.

 

EnumSet의 몇 가지 메서드

  • allOf() : 넘겨받은 enum.class의 모든 요소를 포함한 Set에 반환한다.
  • noneOf() : 비어있는 enum.class Type의 Set을 반환한다.
  • range() : 정의된 상수의 index 값을 기준으로 요소를 저장한다.
  • complementOf() : 해당 Set을 만들 때 전달된 요소들을 제외할 수 있다.
  • add, contains, forEach, remove 등과 같이 기존 메서드는 동일하게 사용 가능하다.

'Live Study' 카테고리의 다른 글

Live Study_Week 13. I/O  (0) 2021.02.20
Live Study_Week 12. Annotation  (0) 2021.02.02
Live Study_Week 09. 예외 처리  (0) 2021.01.11
Live Study_Week 08. 인터페이스  (0) 2021.01.05
Live Study_Week 07. 패키지  (0) 2020.12.28

 

RESTful Tutorial!


이 글은 RESTful에 대하여서 완벽하게 정리하는 것이 아닌 기본적인 내용을 학습하도록 작성한 글입니다.

 

세세한 내용에 대해서는 각 목차를 Keyword로 검색하시길 바랍니다.

 

 

 

해당 예제들은 다음 글에서 업로드할 예정입니다.

 

 

Notion에서 작성하고 옮겨오다 보니 몇몇 양식이 깨져있을 수 있습니다. 그렇기에 Notion Link도 남겨드립니다. www.notion.so/Week_02-01-REST-8d07b91683d548c2aa4e20f2f404eeef

 

 

RESTful을 위한 사전 지식


HTTP (Hypertext Transfer Protocol)

Web client와 Server 간의 데이터 전송을 위해 사용되는 Application Layer Protocol입니다.

 

요청과 응답을 하나의 트랜잭션 단위로 묶어놓고, 응답 이후에는 별도의 정보나 상태를 가지지 않는 Stateless Protocol이고, 데이터를 평문으로 전송되게 구현되어 있습니다.

 

 

사용 시 고려해야 할 점

  • Stateless 하기에 상대적으로 서버의 Resource를 적게 사용할 수도 있으나, TCP Connection에 대한 Overhead가 심화될 수 있기에 이를 최적화하기 위한 여러 방법을 고려해야 합니다.
  • 데이터가 평문으로 전송되기에 패킷 탈취, 변조에 대한 보안 공격에 취약점을 가지기에, HTTP Over SSL(HTTPS)과 같은 방법을 사용하여야 합니다.

 

추가적으로 학습할 Keyword로는 Handshaking, HTTP 구조와 1.1, 2.0, quic 정도가 있습니다.

 

 

HTTP Method

 

  • GET (멱등성 O)
  • Resource를 조회하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 기본적으로 제공되지 않습니다. 이는 요청 Body를 이용할 수 있음을 의미합니다. 
  • 몇 번을 요청하더라도 호출의 결과는 같기에 멱등성을 지킨다고 이야기합니다. 이 요청을 통해 받을 수 있는 State code는 200, 400, 404 등이 있습니다.

 

  • POST (멱등성 X)
  • Resource를 생성하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 존재합니다. 해당 요청 시 Resource를 만들 수 있는 충분한 정보를 가져야 합니다.
  • 요청 시마다 새로운 Resource가 생성되기에 멱등성을 지키지 못한다고 이야기합니다. 받을 수 있는 State code는 201, 204 등이 있습니다.

 

  • DELETE (멱등성 O, X)이 요청은 구현 방식에 따라 멱등성을 지킬 수도 있고, 지키지 못할 수도 있습니다. 기본적인 제약으로는 멱등성을 지킨다고 하는데, 이를 만족시키기 위해서는 해당 요청에 따라 Resource가 바로 삭제되는 것이 아닌 Flag 등을 통한 Soft Delete가 되어야 합니다.
  • Resource를 삭제하는 상황에서 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 불가능하고 요청 Body가 존재하지 않습니다.

https://tools.ietf.org/html/rfc7231#section-4.3.5

If the target resource has one or more current representations, they
might or might not be destroyed by the origin server, and the
associated storage might or might not be reclaimed, depending
entirely on the nature of the resource and its implementation by the
origin server (which are beyond the scope of this specification).

 

  • PUT (멱등성 O)
  • Resource를 전체적으로 수정할 때 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 불가능하고 요청 Body가 존재합니다.
  • 이 요청은 Resource가 존재한다면 모든 정보를 수정하고, 존재하지 않는다면 새로운 Resource를 생성하게 됩니다. 즉 POST와 같이 Resource 생성 시 필요한 모든 정보를 포함하고 있어야 합니다.

 

  • PATCH (멱등성 X)
  • Resource의 일부 정보를 수정하기 위해 사용하는 HTTP Method입니다. 이 요청에 대해서는 캐싱이 가능하고 요청 Body가 존재합니다.
  • 이 요청은 Resource가 존재한다면 일부 정보를 수정하고, 존재하지 않는다면 Update 되지 않을 수 있습니다. 매 실행마다 다른 결과를 받을 가능성이 존재하기에 기본적으로 멱등하지 않다고 이야기합니다.
  •  
  • 해당 방식을 PUT과 동일한 방식으로 작성한다면 멱등성을 가질 수 있게 될 수도 있습니다. https://developer.mozilla.org/ko/docs/Web/HTTP/Methods/PATCH

 

이외에도 알아볼 Keyword로는 HEAD, CONNECT, OPTIONS, TRACE 등이 있습니다.

 

 

HTTP Status

HTTP Protocol은 Status Code를 정의, 제공하기에 요청 Client에게 State를 알려줄 수 있습니다.

 

 

1xx 번대

  • 100 Continue
  • 임시 응답, Client 가 요청하거나 요청이 완료된 경우에는 무시해도 되는 코드입니다.
  • 101 Switching Protocol
  • 서버에서 통신 Protocol을 변경할 것임을 알려줍니다.
  • 102 Processing
  • 서버가 요청에 대해서 처리 중이지만, 아직 응답할 수 없음을 알려주는 코드입니다.

 

2xx 번대

  • 200 OK
  • 메서드에 따라 의미가 변경되는 code로써 일반적으로 요청이 성공적으로 진행되었다는 의미입니다.
  • 201 Created
  • 요청이 성공적이었고, 그에 따른 새로운 Resource가 생성되었다는 의미입니다.
  • 204 No Content
  • 요청은 성공적이었으나, 서버에서 제공할 Content는 존재하지 않는다는 의미입니다.

 

3xx 번대

  • 300 Multiple Choice
  • 요청에 대해 하나 이상의 응답이 가능함을 의미하고, Client는 응답 방식을 선택하여야 합니다.
  • 301 Moved Permanently
  • 요청한 Resource의 URI가 변경되었음을 의미합니다.
  • 303 See Other
  • 요청한 Resource에 대해 다른 URI로 GET 요청을 보내야 함을 알려주는 것입니다.

 

4xx 번대

  • 400 Bad Request
  • 잘못된 요청 정보에 의하여 서버가 해당 요청을 처리할 수 없음을 의미합니다.
  • 401 Unauthorized
  • 해당 요청이 인증되지 않았음을 의미합니다. 이 Resource에 접근하기 위해서는 인증이 필요합니다.
  • 403 Forbidden
  • 해당 요청이 인가되지 않았음을 의미합니다. 이 Resource에 접근하기 위한 권한이 부족합니다.
  • 404 Not Found
  • 해당 요청에 대해서 해당하는 Resource를 찾지 못했음을 의미합니다.
  • 405 Method Not Allowed
  • 해당 요청에 따른 메서드가 존재하나, 현재 사용할 수 없음을 알려줍니다. 제거된 경우도 포함합니다.

 

5xx 번대

  • 500 Internal Server Error
  • 서버가 현재 요청을 처리하지 못하는 상황이거나, 처리할 수 없는 요청임을 의미합니다.
  • 502 Bad Gateway
  • 서버가 요청을 처리하는데 필요한 응답이 잘못 수신되었음을 의미합니다.
  • 503 Service Unavailable
  • 서버가 요청을 처리할 준비가 되지 않은 상태임을 의미합니다.

 

 

API

운영체제나 프로그래밍 언어가 제공하는 기능 등에 대해 제어할 수 있게 만든 인터페이스입니다.

 

API의 종류

  • Private API
  • 자사 제품, 서비스를 개선하기 위해 내부적으로 발행(구현)하는 API입니다. 외부에 노출되지 않습니다.
  • Public API
  • 개방형 API로써 모두에게 공개되는 API입니다. Kakao OpenBuilder나 Map API 등이 있습니다.
  • Partner API
  • 기업이 데이터 공유에 동의한 특정 기업, 사용자들에게 제공하는 API를 말합니다. B2B Solution가 이에 속할 수 있습니다.

 

 

Resource

리소스는 컴퓨터, 서버에 저장할 수 있는 모든 것을 포함할 수 있습니다.

 

이는 작성된 페이지, 문서, 이미지와 같은 Static Resource와 요청에 따른 논리적인 결과 값인 Dynamic Resource를 포함하는 개념입니다.

 

 

추가 학습 자료 : https://geobgu.xyz/web-mapping/web-servers-1.html#overview

 

 

URI

서버에서 제공하는 Resource는 식별자로 관리되고 접근할 수 있습니다. 이를 URI라고 합니다.

 

Client는 URI를 이용하여 서버에게 필요한 Resource에 대한 제공 요청을 할 수 있습니다.

 

 

URI 종류

  • URL (Uniform Resource Locator)http://www.oreilly.com/index.html , http://www.naver.com/index.html
  • URL은 특정 서버의 한 Resource에 대한 구체적인 위치를 서술합니다.
  • URN (Uniform Resource Name)urn:examle:index
  • Resource의 위치에 영향받지 않는 유일무이한 식별 값의 역할을 합니다.

 

 

Represent

표현은 Client에게 Resource를 어떻게 제공할 것인지에 대한 개념입니다.

 

서버는 XML, JSON, CSV, YAML, TEXT 등 여러 Format을 Client의 요청에 맞게 표현하여 제공해야 합니다. Client는 Content-Type Header를 통해 제공받을 Format을 정의할 수 있습니다.

 

 

JSON

JavaScript Object Notation은 데이터를 저장, 전송할 때 사용되는 경량 Data Format입니다.

 

JavaScript 에서 작성되는 Object 형식을 사용하였기에 JSON이라고 명명되었습니다.

 

 

Format Example

{
    "id" : "1",
    "name" : "lob",
  "email" : "test@email.com"
}

이와 같이 Key : Value Format을 사용합니다. 추가적으로 UTF-8 Encoding을 제공하며, 별도의 주석을 제공하지 않습니다.

 

 

Client-Server Model

Resource 요청자와 Resource 제공자 간에 작업을 분리하는 분산 애플리케이션이자 네트워크 아키텍처를 의미합니다. 일반적으로 웹 페이지 ↔ 서버 관계를 말합니다.

 

이외에도 알아볼 Keyword로는 DNS, Router, TCP/IP, UDP, Packet 등이 있습니다.

 

 

 

 

REST이란? (REpresentational State Transfer)


REpresentational State Transfer : 리소스의 상태를 표현하고 전송한다.

 

REST란 WWW과 같은 분산 하이퍼미디어 시스템에서 리소스와 상태를 표현하고, 전송하는 것에 대한 설계 양식입니다.

 

많은 사람들은 HTTP Protocol을 통해 REST를 구현하고 있지만, 이것은 HTTP에 종속되지 않는 개념입니다. 즉 다른 Protocol을 사용하거나 새로운 Protocol을 만들더라도 해당 설계 원칙을 지키면 "RESTful 하다."라고 말할 수 있음을 의미합니다.

 

 

해당 내용에 대해서는 CoAP RESTful API를 검색해보시면 좋을 것 같습니다.

 

 

 

REST의 6대 제약


Client-Server Model

Client - Server 아키텍처와 같이 독립적인 상태에서 구현되어야 하는 API 규약입니다.

 

 

Stateless

매 요청은 필요한 모든 정보를 담고 있어야 하며, 종료 시 상태는 없어야 한다.

 

 

Cache

SELECT - GET과 같은 조회성 트랜잭션은 캐싱하여야 한다.

 

 

Uniform Interface

제공되는 인터페이스는 일반적이고, 일관성이 있어야 하며, 간단할수록 좋습니다.

  • Identification of resourcesREST API Design 항목 참고
  • 각 Resource은 유일하게 식별 가능해야 하며, 개념적으로 분리되어야 합니다.

 

  • Manipulation of resources through representationsClient는 매 요청에 대한 충분한 정보를 제공하여야 합니다. 수정, 조회, 삭제를 위한 데이터
  • HTTP Method로 CRUD라는 표현을 담아야 합니다. URI에 담지 않습니다.

Example

GET "http://www.example.com/api/users" HTTP/1.1

PATCH "http://www.example.com/api/users/1224" HTTP/1.1

이와 같이 URI 정보에는 CRUD 표현을 담지 않고, HTTP Message의 Method를 통해 표현해야 합니다.

 

  • Self-describing messages
  • 메시지 스스로 자신에 대한 설명이 가능해야 합니다.
  • Resource를 제공할 때 Resource의 Type을 제공하고 (Content-type), Resource에 대한 링크를 Response Body에 포함함으로써 해당 조건을 만족할 수 있습니다.

 

  • Hypermedia as the engine of application state (HATEOAS)
  • 하이퍼 링크를 통해서 application의 상태 변화가 가능해야 합니다.
  • Example
{
    ... ,

    "_links": {
        "self": {
            "href" : "http://www.example.com/api/users/1334"
        },
        "prev-link" : {
            "href" : "http://www.example.com/api/users"
        }
    }
}

 

위에 제시된 예시처럼 Link를 제공하여 페이지 이동 등을 할 수 있도록 지원하여야 합니다.

데이터에 대한 링크를 제공하는 것을 HAL이라고 합니다.

 

 

Layered System

계층적으로 구성되어야 하며, Client는 리소스에 직접 접근하지 않고 (Server를) 호출하여야 합니다.

 

이는 각각의 기능이 다른 계층으로 구성되고 각각의 계층은 인접한 계층에만 통신하며, 기능 수행을 위해 하위 계층을 의존해야 한다는 것을 의미합니다.

 

Controller ↔ Service ↔ Repository와 같은 구조도 Layered 아키텍처입니다.

 

 

Code-On-Demand (Optional)

Server가 Client에게 Code(JavaScript..)를 제공하여 확장을 할 수 있어야 합니다.

 

 

 

 

Richardson Maturity Model


이는 REST 제약에 부합되는 정도를 등급으로 매긴 것입니다. 일반적으로 RESTful API이라 명명할 수 있는 Level은 2 Level부터입니다.

 

 

level 0 : The Swamp of POX

HTTP를 데이터 전송을 위한 protocol로만 사용할 뿐 상태를 나타내거나 하지 않습니다.

 

POX 란 순수한, 평범한 XML를 주고받는 것을 의미하며, 이 level은 하나의 Endpoint를 제공하는 단순한 스타일의 API를 이야기합니다. 이러한 시스템을 RPC Model이라고 명칭 하기도 합니다.

 

 

이와 관련된 키워드는 SOAP, XML-RPC, RPC Model 등이 있습니다.

 

 

 

level 1 : Resource

서버의 Resource에 대한 각각의 point를 제공하는 상태의 API를 의미합니다.

 

모든 요청은 필요한 Resource에 대한 개별적인 point로 통신하는 상태이지만, 하나의 HTTP Method만을 사용합니다.

 

 

Example

POST "http://www.example.com/users" HTTP/1.1
POST "http://www.example.com/posts" HTTP/1.1
POST "http://www.example.com/locations" HTTP/1.1

 

 

level 2 : HTTP Verbs

HTTP Method와 Resource 동사를 결합하여 REST API를 온전히 제공하는 단계입니다.

 

HTTP Protocol의 능력을 최대한 활용한 상태라는 것은 각각의 HTTP Method를 골고루 활용하고 있음을 나타냅니다.

 

Example

GET "http://www.example.com/users" HTTP/1.1
POST "http://www.example.com/users" HTTP/1.1
DELETE "http://www.example.com/users/1223" HTTP/1.1
PATCH "http://www.example.com/users/1223" HTTP/1.1

 

 

level 3 : HATEOAS

Client와 동적인 상호작용이 가능한 상태의 REST API를 말합니다. 이는 Link를 통해 제공됩니다.

 

Uniform Interface : Hypermedia as the engine of application state 항목.

 

 

 

 

REST API Design


URI는 제공되는 Resource를 기준으로 작성해야 합니다.

 

 

복수 자원

두 개 이상의 Resource를 제공할 때 작성하는 방식입니다.

                                (post 정보들)
"https://www.example.com/api/posts"

                                (location 정보들)
"https://www.example.com/api/locations"

                                (user 정보들)
"https://www.example.com/api/users"

 

 

단일 자원

하나의 Resource를 제공할 때 작성하는 방식입니다.

                (post들 중에 하나의 post)
"https://www.example.com/api/posts/{postId}"          -> "api/posts/123"

                                (location들 중에 하나의 location)
"https://www.example.com/api/loacations/{locationId}" -> "api/loacations/123"

                                (user들 중에 하나의 user)
"https://www.example.com/api/users/{userId}"          -> "api/users/123"

 

 

하위 리소스 표현

해당 Resource가 보유하고 있는 하위 Resource에 대해 작성하는 방식입니다.

        ( Customer들 중에 하나의 Customer 정보 )의( accounts 정보 들)
"https://www.example.com/api/customers/{customerId}/accounts" 

        ( Customer들 중에 하나의 Customer 정보 )의( accounts 정보 들 중 하나의 accounts  )
"https://www.example.com/api/customers/{customerId}/accounts/{accountsId}"

 

 

버저닝

변경 사항에 따른 API 버전 관리 방식입니다.

 

 

Semantic Versioning

{MAJOR}. {MINOR}. {PATCH} 형식으로 Version을 관리하는 방식을 의미합니다.

 

example) 1.0.1

 

 

버전 관리 방식의 종류

 

 

REST 요소 : Document, Collection, Store, Controller

  • Document위에서 설명된 단일 자원 표현이 이에 해당됩니다.
  • https://www.example.com/api/users/{userId}
  • 기본이 되는 Resource 형식을 의미합니다. 이는 데이터베이스의 Record를 검색하는 것과 유사합니다.
  • Collection위에서 설명된 복수 자원 표현이 이에 해당됩니다.
  • https://www.example.com/api/users
  • Server에서 관리하는 Document들이 모여있는 Store를 의미합니다. 이는 데이터베이스의 Table를 검색하는 것과 유사합니다.
  • Store복수로 작성되어야 합니다. profiles, favorites...
  • https://www.example.com/api/users/favorites
  • Client에서 관리하는 Resource Store를 의미합니다.
  • Controllersign-up, buy
  • https://www.example.com/api/users/sign-up
  • Resource에 대한 CRUD 조작 이외의 의미들을 나타냅니다.

 

 

기타 규약

  • 자원을 나타내는 명사만을 사용하여야 하며, 동사를 사용하지 않습니다.
  • "/"를 통하여 계층 관계를 나타내어야 합니다. (맨 뒤에는 / 를 사용하지 않습니다.)
  • 명사와 명사 사이에는 가독성을 위해 '-' (하이픈) 문자를 사용해야 합니다.
  • '_'을 사용하지 않습니다. 이는 일부 브라우저나 화면에서 보이지 않을 수 있기 때문입니다.
  • 소문자만을 일관적으로 사용하여야 합니다.
  • 파일 확장자를 사용하지 말아야 합니다. 이는 어떠한 이점도 주지 않고 URI 길이만 늘어나게 하기 때문입니다.

 

해당 내용들을 읽은 후 공개 API 설계 시 도움이 될 수 있는 Keyword로는 OAS 3.0이 있습니다.

 

 

 

 

참고 자료


 

G1 GC 구조

개념 구조 (모든 영역을 블록으로 표기하지 않음)

G1 GC는 여러 Background Thread를 이용하여 Heap 크기에 따라 1MB~32MB로 분할된 Region들을 지정된 Pointer를 통해 Scan 하고 제일 많은 수집 대상이 존재하는 Region에 대해 이름을 지정한다.

이름이 지정된 영역은 GC의 대상이 된다.

 

 

 

Young Generation Region (Non-Contiguous Region)

Heap 메모리 공간에서 비연속적으로 존재하는 Young Object의 주거지를 의미한다.

 

해당 영역의 크기는 Heap 전체 크기의 5~60% 까지를 차지할 수 있다.

 

 

 

G1 GC 특징

  • G1 GC는 Scan을 하는 도중에 해당 Region에 대한 Compacting도 수행한다.
  • Region은 모두 같은 크기의 영역을 할당받으며, Region에 부여된 역할에 따라 그에 맞는 Object가 존재하게 한다. (Default Region Size = Heap Size / 2048 {Region count})
    • XX : G1HeapRegionSize를 통해 Size를 명시적으로 설정할 수 있다.
  • JDK 8부터는 Heap 영역에 존재하는 중복된 문자열에 대해 식별하고 여러 참조에 대해 하나의 문자열을 가리키도록 수정하는 최적화 방식이 포함되었다.

 

 

G1 GC Flow

Evacuation Pauses (Young Generation GC, Minor GC, Young 공간 부족시 발생)

Young Generation Region에서 발생하는 GC를 말하며 Live Object가 하나 이상의 Survivor Region으로 이동하는 상황을 의미한다. (이때 S-T-W가 발생, Multi-Threading 동작)

 

Ageing을 통해 임계값을 만족하는 Object들은 Old Region으로 이동한다.

 

 

 

G1 Collection Phases (= Old GC)

  1. Initial Mark (S-T-W)

    Evacuation Pauses가 실행되는 시점에서 수행되는 동작이다. Survivor Region을 Mark

    Old GC가 필요한 경우에 동시 수행된다.

  2. Concurrent Marking (Multi-Threading, Concurrent)

    Heap 내의 모든 Live Object와 빈 Region 들을 Mark 하고 Region의 Live Object 비율 계산

    해당 동작은 Evacuation Pauses와 같이 수행될 수 있으며 Application과 같이 동작한다.

  3. Remark (S-T-W)

    Heap 내의 Live Object Mark 완료 단계. 빈 Region은 Free Region으로 등록

  4. Copying, Cleanup Phase (S-T-W)

    앞서 2단계에서 계산했던 비율을 가지고 빨리 청소할 수 있는 Region들을 선택하여 Cleanup, Copying을 진행한다.

    not-live Object 제거, 빈 Region은 Free Region으로 등록, Survivor → Old Copy 등

  5. After Copying, Cleanup Phase (S-T-W)

    선택된 Region들의 Phase가 종료된 시점, 남은 Region 들에 대해 Cleanup, Copying 진행

 

 

G1 GC의 장점

  • 별도의 STW 없이도 여유 메모리 공간을 압축하는 기능을 제공한다.

    Compecting으로 인한 STW 발생 시간을 최소화하였다.

  • Heap 크기가 클수록 잘 동작하게 된다.

  • 특별한 최적화 기능을 제공한다. (String Deduplication Optimize)

    해당 기능은 Evacuation Pauses 중에 발생하며, String의 Hashcode와 값을 비교하게 되는데, 

    최소 수명 즉 AgeThreshold의 값이 3 이상인 중복 String Object 들을 정리한다.

  • 많은 튜닝 가능성이 존재한다. (Size, Count, GC 최대 시간, 동작 Thread 수와 실행 빈도 등)

 

 

G1 GC의 유의점, 단점

  • 공간 부족 상태를 조심하여야 한다.

    이때 Full GC가 수행되게 되는데, 이 GC는 Single Thread로 동작한다.

    이를 해결하기 위한 간단한 방법으론 Old Generation Region를 크게 만드는 방법이 있지만 이 경우에는 Young Generation Region 영역이 줄어드는 트레이드 오프가 발생한다.

  • 해당 GC 방식은 6GB 이상의 Heap Management를 목적으로 개발된 모듈이다. 즉 작은 Heap 공간을 가지는 Application에서는 성능을 제대로 발휘하지 못하고 빈번한 Full GC가 발생할 수 있다.

 

 

참고 자료

 

 

데이터베이스의 index?


Index는 책의 목차, 색인과 같은 역할을 담당하는 데이터베이스 객체로써 테이블과 독립적으로 존재합니다. 하지만 테이블에 의존적이기에 해당 테이블이 삭제될 경우 같이 제거되게 됩니다.

 

존재하는 칼럼의 값과 해당 레코드가 저장된 주소를 키와 값의 구조로 묶어 저장하고, 정렬된 상태를 유지하기에 저장, 수정, 삭제 기능들의 성능을 희생하고 빠른 조회를 제공하는 것이 Index의 사용 목적입니다.

 

 

 

기본 제공되는 Index?

기본적으로 제공되는 Index는 PK index 입니다. InnoDB는 설계상 이유 때문에 모든 테이블에 PK가 필요한데요. 개발자가 테이블에 PK를 작성하지 않는다면, 암시적으로 PK를 생성하여 레코드를 탐색하고 인덱스를 생성하는데 사용되게 됩니다.

 

 

 

 

Primary Key vs Secondary Key

  • Primary Key : 테이블마다 기본 키 제약 조건을 통해 만들어지는 하나의 고유한 Key를 의미하고, Null 값과 중복 값을 허용하지 않으며, 앞서 이야기한 것처럼 자동으로 인덱스를 생성하여 데이터에 대한 빠른 접근을 지원합니다.
  • Secondary Key : 각 레코드에 대한 고유한 값을 제공하는 Key를 의미하는데, Null을 허용하며, 레코드를 식별하는데 사용할 수 있고 Index로 활용할 수 있습니다. 하나의 테이블에 여러 Key가 존재할 수 있습니다.

 

 

 

Unique Index를 사용하는 이유?

Index에도 Unique Index와 Non-Unique Index가 존재하는데요. 간단하게 Unique Index를 알아봄으로써 어떠한 차이를 가지는지 확인해보겠습니다.

  • Unique Index : 하나의 Key만 존재함을 나타냅니다. 이는 Index에 대해 동등 조건을 사용하는 쿼리에서 DB의 쿼리를 실행하고 최적화하는 옵티마이저에게 하나의 Index를 찾았을 때 더 이상 스캔하지 않아도 된다는 의미를 제공하게 되며, 이를 통해 쿼리 최적화를 수행하게 되는데요. 그렇기에 사용 가능한 모든 경우에서는 Unique Index를 사용하는 것이 권장되게 됩니다.

    추가적으로 고유한 인덱스를 사용하는 것은 해당 레코드의 데이터 무결성을 보장하게 됩니다.

 

 

index의 장점?

  • 조회(SELECT) 쿼리의 성능을 향상시킵니다. 여러 서비스에선 데이터를 조회하여 사용자에게 제공하는 것이 다른 쿼리보다 많은 비중을 차지하기 때문에 도입하는 경우도 있습니다..

  • 고유한 index 형식을 사용한다면 이는 행에 대해서도 중복 없이 구성하는 것을 보장합니다.

  • 매번 테이블을 스캔한 후 행을 정렬하는 절차를 생략하게 합니다. 미리 정렬된 목록을 제공함으로써 매 쿼리마다 정렬을 하지 않아도 빠르게 데이터를 검색할 수 있도록 지원하게 됩니다.

    DB는 기본적으로 모든 행을 스캔하고 정렬한 뒤 일치하는 행을 필터링하여 결과를 반환합니다.

 

 

index의 단점?

  • 데이터의 수정(insert, update, delete)이 발생할 때마다 연관 index도 업데이트해야 합니다. 즉 다른 쿼리의 성능이 떨어지게 되는 문제점이 있습니다. update의 경우 where 조건에 index를 사용하고 있다면 해당 칼럼을 찾아 변경하는 성능을 높일 수 있습니다. (index update는 동일하다.)
  • Index는 DB 내에서 별도의 저장 공간을 차지합니다.
  • Index를 유지하는 비용이 발생합니다. 대표적으로 데이터 수정 , 삭제, 추가 등의 경우 Index가 업데이트되다가 깨지는 경우가 발생하게 되는데 이때 복구, 정상적인 Index로의 우회 등 추가적인 처리가 필요합니다.

 

 

Index Corruption?

Corrupt Index는 DDL, DML(insert, update, delete) 쿼리를 수행한 뒤 정렬하는 도중이나, Slow Query에 의한 비정상적인 종료에 의해 발생할 수 있습니다. 하지만 확실하게 손상되었는지 확인할 수 없기 때문에, 이를 파악하기 위해서는 테스트를 통해 실행시간과 결과를 관찰하여야 합니다.

 

 

 

index를 사용하여야 하는 시점?

테이블에 인덱스를 추가하는 것은 저장 속도를 어디까지 희생하고, 읽기 속도를 얼마나 더 빠르게 만들어야 하는지의 여부에 따라 결정되어야 합니다.

 

인덱스를 추가하거나 제거할 때마다 성능 테스트를 수행하여 어떤 영향이 미치는지 실질적인 수치를 파악하여야 하고, 

이는 많은 검색 쿼리에서 빈번하게 키로 사용되는 컬럼을 인덱스로 사용한다는 것이 기본 전제가 되는데요, 이를 정량화(수치로 만들어서)하여 판단해야 한는 것이 좋습니다.

 

 

 

index의 알고리즘?

간단하게 Index 알고리즘의 종류와 개념만 작성하였습니다.

  • B-Tree Index : balanced Tree를 사용하는 방식을 의미하며, 일반적으로 사용되는 유형입니다. 칼럼의 값을 변형하지 않고, 원래의 값만을 이용해 Indexing 하는 특성을 가지게 됩니다.

    여러 형태의 변형된 알고리즘을 가집니다. B+-Tree, B-Tree 등*

  • Hash Index : 컬럼의 값을 Hash 값으로 계산하여 Indexing 하는 방식입니다. 매우 빠른 검색을 지원하나 일부 값을 통해 값을 찾는 Pattern matching을 지원하지 못합니다. 주로 메모리 기반의 DB에서 사용됩니다. 

  • Fractal-Tree Index : B-Tree 방식의 단점을 보완하기 위해 고안된 방식입니다. 값을 변경하지 않고 인덱싱 하는 것은 동일하나 데이터가 저장되거나 삭제될 때 발생하는 비용 (Disk I/O)을 줄이도록 설계된 것이 특징입니다. 각 내부 노드에 버퍼를 포함함으로써 데이터를 임시로 저장하고 버퍼가 가득 채워졌을 때 Flush 하게 됨으로 I/O 작업 단위를 크게 만들어서 유지하게 됩니다.

  • R-Tree Index : 2차원 데이터를 Indexing 하고 검색하는 목적을 지니는 방식입니다. 주로 공간 개념 값을 사용하는 GPS나 GIS 서비스에서 사용하게 됩니다.

  • Full Text Search Index : 문서의 내용 전체를 Indexing 하여 특정 키워드가 포함된 문서를 분석, 검색하는 방식에서는 B-Tree 형식을 사용할 수 없기에 사용되는 방식입니다. 크게 Stopword 방식과 N-Gram 방식으로 이야기할 수 있습니다.

 

 

자료 출처


 

 

 

Bedocs Study DbUnit 발표 후기


발표 자료

 

발표하며 느낀 것들

금주 목요일에는 저에게 생소했던 라이브러리인 DbUnit에 대해 학습하고, 정리된 내용과 예제 프로젝트를 이용해 발표하는 시간을 가지게 되었습니다.

 

새로운 개념과 라이브러리를 학습하는 것은 즐겁지만, 어려운 경우도 종종 있었는데, 특히 이번 주제인 DbUnit은 공개된 문서의 초기 설정 등이나 레퍼런스가 부족하여 더 어려움이 있었던 것 같습니다.

 

참여하시는 분들이 대부분 직장인이셔서 야근 등의 문제로 (발표) 일정이 1주일 밀렸었는데, 그 덕에 준비를 좀 더 할 수 있었고 실수(거의?) 없이 라이브로 환경 설정부터 테스트 코드 작성까지 할 수 있었던 것 같습니다.

 

 

뿌듯한 시간이었고 다들 좋은 반응들을 해주셔서 정말 감사했었습니다. 


 

 

 

Go Hello World!


지난 화요일 저녁에 여유가 생겨서 요즘 인프라, 컨테이너 관련 오픈소스나 MSA에서 자주 사용되는 Go 언어를 찾아보고 간단하게 코딩도 해보았습니다.

 

 

학습 중인 자료 (무료 E-Book)

 

중괄호 위치 등의 Coding Convention을 제한하고 메서드 명명 규칙을 기능으로써(?) 제공하는 부분이 정말 재미있었는데요. 이미 기존에 학습하시고 사용하시는 분들이라면 김 빠지실 수 있겠지만, 이런 부분이었습니다.

// private method, 외부 접근 불가능
func sayBye() {
  fmt.Println("bye")
}

// public method, 외부 접근 가능
func SayHello() {
  fmt.Println("hello")
}

소한 즐거움..?

 

현재 자바를 이용해 프로젝트를 하며 드는 생각은 문법이 읽기 좋지만, 너무 많은 코드를 작성하게 된다라는 점이 있는데요. Go는 간단한 문법을 내세우는 언어답게 간결하면서도 제가 좋아하는 정적, 강타입도 지키는 언어이기에 정말 마음에 들었습니다. (변수 타입은 컴파일 시점에서 결정되지만요.)

// Java : 1
public static void main(String[] args) {

        int a = 0, i;
        for (i = 0; i < 10; i++) {
            a += i;
        }

        System.out.println(a);
}

// Java : 2
public static void main(String[] args) {

        int a = 0, i = 0;
        while (i != 10) {
            a += i;
            i++;
        }

        System.out.println(a);
}

// Go
func main() {
        sum, i := 0, 0

    for i < 10; {
        sum += i
        i++ 
    }
    fmt.Println(sum)
}

이런 예제에서는 간결함을 크게 느낄 수는 없겠지만요.

 

물론 없는 문법도 많고.. (예외 처리라던지?) 핵심 라이브러리도 부족하다고 하지만, 모든 언어가 모든 상황에서 좋은 경우는 없기 때문에 (그렇게 믿고 있습니다.) 충분히 감내할 수 있는 부분이라고 생각합니다.

 

여유가 생길 때마다 새로운 기술과 언어를 꾸준하게 공부할 생각인데 이러한 마음가짐이 계속 갔으면 좋겠습니다. ㅎㅎ

 

 

이번 한 주 다들 노고가 많으셨고요. 주말에 푹 쉬시고 다음 주도 힘차게 나아가 봅시다! 파이팅!

 

 

+ Recent posts