728x90
반응형
728x90


람다식의 개념 및 기본 문법

람다식이란 ?

 

람다식(Lambda Expression)은 자바 8부터 도입된 함수형 프로그래밍의 개념입니다. 람다식은 간단히 말하면 익명 함수(anonymous function)를 나타내는 것으로, 메서드를 하나의 식으로 표현하는 방법입니다. 기능이 비워져 있는 상태에서 기능을 추가해서 사용할 수 있도록 하는 것이 인터페이스 속에 있는 추상메서드이며, 그 메서드에 기능을 부여(정의)하여 사용하는 것이 람다식의 핵심입니다. 코드의 간결화 및 병렬처리에 효율적입니다. (Collection API 성능을 효과적으로 개선(Stream))

객체지향 프로그래밍은 프로그램을 객체들의 모임으로 구성하는 방식입니다. 객체는 데이터와 그 데이터를 처리하는 메서드로 이루어져 있으며, 객체들 간의 상호작용을 통해 프로그램이 동작합니다. 객체지향 프로그래밍에서는 데이터와 해당 데이터를 처리하는 메서드가 논리적으로 묶여있어 유지보수와 확장이 용이하며, 코드의 재사용성을 높일 수 있습니다.

반면, 함수형 프로그래밍은 함수를 일급 객체로 취급하고, 함수의 조합을 통해 프로그램을 작성하는 방식입니다. 함수형 프로그래밍은 상태 변경이나 가변 데이터보다는 불변성과 순수 함수(pure function)의 개념을 강조합니다. 순수 함수는 동일한 입력에 대해 항상 동일한 출력을 반환하며, 부작용(side effect)을 발생시키지 않는 함수를 말합니다.

람다식은 함수형 프로그래밍에서 함수를 간결하게 표현하기 위한 방법 중 하나입니다. 람다식은 익명 함수를 나타내는데, 함수형 프로그래밍에서 함수를 일급 객체로 다루는 특성과 잘 어울립니다. 람다식을 사용하면 함수를 변수에 할당하거나 매개변수로 전달할 수 있으며, 함수형 인터페이스를 구현하는 데 유용하게 사용됩니다.

람다식은 주로 함수형 인터페이스(functional interface)를 구현하기 위해 사용됩니다. 함수형 인터페이스는 단 하나의 추상 메서드만을 가지는 인터페이스를 말하며, 람다식은 이 추상 메서드의 구현을 직접 전달하는 방식으로 사용됩니다.

  • 클래스 바깥에 올 수 있는 것은 패키지, 임포트, 외부클래스 내부에 올 수 있는 것은 필드, 메서드, 생성자, 이너클래스

    람다식 이해를 위한 기본 용어의 정리
    1. 함수(function) : 기능, 동작을 정의합니다.
      void(리턴타입) abc(메서드 이름)(입력매개변수) {함수
                     //중괄호 안에 기능 및 동작을 입력
      }

    2. 메서드(method) : 클래스 또는 인터페이스 내부에서 정의된 함수 (=메서드)
      class A {
               void abc() {  → class 내부에서만 존재가능한 함수는 메서드
                          //기능 및 동작
                     }
      }

    3. 함수형 인터페이스(functional interface) : 내부에 단 1개의 추상메서드만 존재하는 인터페이스
      interface A {
               public abstract void abc();
      }

      인터페이스 내부에 2개 이상의 메서드가 담겨있다면 , 람다식으로 함수를 생략하여 작성하게 되면 어떤 메서드가 생략된 후 호출되는지 구분되어지기 않기 때문에 꼭 1개의 추상메서드만 담겨있는 인터페이스만 람다식으로 표현이 가능합니다.

 

 

  1. 함수적 프로그래밍에서의 함수 사용 (자주 사용하는 기능을 구현) : 함수를 정의하고 바로 호출할 수 있습니다.

  2. 객체 지향형 프로그래밍에서의 메서드(함수=기능)사용 : 인터페이스 or 클래스 내의 메서드 정의 및 구현 후 타입 참조변수로 객체를 생성 한 후 타입 참조변수를 이용해서 객체 내부의 메서드를 호출 할 수 있습니다.
            package jungsuk_0624;

            //추상메서드를 담고 있는 인터페이스를 정의
            interface A {
                void abc();
            }


            //인터페이스를 구현한 자식클래스이기 때문에 미완성 메서드를 구현할 의무가 있음
            class B implements A {
            
                @Override
                public void abc() {
                    System.out.println("메서드 내용 : 자식클래스로 객체를 생성한 후 A 인터페이스 안에 정의된 메서드 호출");
                }
            }


            public class OOPvsFP_01 {
            
                public static void main(String[] args) {

                    // #1. 객체지향 프로그래밍 문법(OOP에서 메서드를 생성)
                    // 클래스 속에 있는 모든 메서드를 호출하기 위해서는 객체를 생성해야 함
                    
                    A a1 = new B();
                    a1.abc();

                    // #2. 객체 지향 프로그래밍 문법 -> 익명이너클래스 사용 방법
                    // 자식 클래스를 컴파일러에게 만들어 달라고 한 후 객체를 사용
                    
                    A a2 = new A() {
                        @Override
                        public void abc() {
                            System.out.println("메서드 내용 : 익명 이너클래스 사용하여 호출");
                        }
                    };
                    
                    a2.abc();


                    //#3. 함수적 프로그래밍 문법(람다식) : 익명 이너클래스를 축약
                    A a3 = () -> {System.out.println("메서드 내용 : 람다식으로 정의된 메서드 호출");};
                    a3.abc();
                }

            }

 

 


📋 람다식의 활용

 

🎈람다식의 3가지 활용

 

  1. 익명 이너 클래스 내 구현 메서드의 약식(람다식) 표현 (함수적 인터페이스만 가능)
    • 함수형 인터페이스는 하나의 추상 메서드만을 가지는 인터페이스를 말합니다. 예를 들어, Runnable, Comparator, ActionListener 등이 함수형 인터페이스의 예입니다.
    • 람다식은 함수형 인터페이스의 추상 메서드를 간결하게 구현하는 방법입니다. 람다식은 매개변수 목록, 화살표(>), 그리고 메서드 구현부로 이루어집니다.

  2. 메서드 참조 (인스턴스 메서드 참조 Type1, 정적 메서드 참조, 인스턴스 메서드 참조 Type2)
    ① 인스턴스 메서드 참조 Type 1 → 이미 정의된 인스턴스 메서드 참조
    인스턴스 메서드 참조를 사용하면 기존 클래스의 메서드를 함수형 인터페이스에 매우 간결하게 매핑할 수 있으며, 가독성을 향상시킬 수 있습니다. 여기서 인스턴스는 메서드를 호출할 객체의 인스턴스를 의미하고, 메서드는 호출할 메서드의 이름을 의미합니다.

    인스턴스 메서드 참조는 주로 함수형 인터페이스의 추상 메서드와 동일한 시그니처(매개변수 타입 및 반환 타입)를 가지는 인스턴스 메서드를 참조할 때 사용됩니다. 리턴타입과 매개변수가 동일해야한다는 의미입니다.

    인스턴스 메서드 참조 문법 → 인터페이스 이름 인터페이스 변수 = 인스턴스(클래스 객체이름 ) :: 인스턴스 메서드 이름

    ② 정적 메서드 참조 Type 1 → 이미 정의된 정적 메서드 참조
    정적 메서드는 객체 생성을 하지 않아도 되기 때문에 앞서 본 인스턴스 메서드 참조와 문법이 조금 다릅니다.
    정적 메서드 참조 문법 → 인터페이스 이름 인터페이스 변수 = 클래스 이름 :: 정적 메서드 이름

    ③ 인스턴스 메서드 참조 Type 2 → 첫번째 매개변수로 전달된 객체 메서드를 참조하는 경우
    정적 메서드 참조 문법 → 인터페이스 이름 인터페이스 변수 = 클래스 이름 :: 인스턴스 메서드 이름

  3. 생성자 참조

    ① 배열 생성자 참조 (Array Constructor Reference) : 배열 생성자 참조는 배열을 생성하는 데 사용되는 생성자를 참조하는 것입니다

    ② 클래스 생성자 참조 (Class Constructor Reference): 클래스 생성자 참조는 클래스의 생성자를 참조하는 것입니다. 클래스의 new 생성자를 참조하는 경우 → interface 메서드의 리턴타입 = 클래스 객체
    배열의 new 생성자를 참조하는 경우 → interface 메서드의 리턴타입 = 배열 객체 타입[]::new
728x90
반응형
728x90

3. Map<K, V>

키(key)와 값(value)의 쌍으로 데이터를 저장하는 인터페이스입니다. 키는 중복될 수 없고, 값은 중복이 가능합니다. List의 index처럼 Map의 key를 가지고 데이터에 접근하기 때문에 중복이 될 수 없습니다. 대표적인 클래스로는 HashMap, TreeMap, LinkedHashMap, HashTable 등이 있습니다.

Collection과는 별개의 interface입니다 ( List, Set과 기본 메서드가 다릅니다.) Key-Value 한 쌍을 Entry라고 부릅니다. 실제로 엔트리를 저장하는 type도 Entry로 저장되어 있는데, Map의 이너로 저장되어 있어 Map.Entry로 한 쌍의 데이터를 저장할 수 있습니다.

https://velog.io/@mmy789/Java-컬렉션-프레임워크-3

 

  • Map<K, V> 컬렉션의 특징
    • 키-값 쌍 저장: Map은 키와 값의 쌍을 저장합니다. 각 키는 유일해야 하며, 중복된 키의 저장을 허용하지 않습니다. 따라서 키를 기반으로 값을 검색하거나 업데이트할 수 있습니다.

    • 순서 보장 여부: 일반적으로 Map은 저장 순서를 보장하지 않습니다. 데이터를 저장한 순서대로 순회할 수 없으며, 순서에 의존하는 작업을 수행할 때는 LinkedHashMap을 사용하는 것이 좋습니다. 그러나 자바 8부터는 LinkedHashMap의 일부 메소드에 순서를 보장하는 기능을 추가하여 순서를 보장할 수도 있습니다.

    • 허용하는 키와 값의 종류: Map은 null 값을 키와 값으로 사용할 수 있습니다. 또한, 일부 구현체에서는 키와 값의 타입에 제한을 두지 않고 모든 종류의 객체를 사용할 수 있습니다.

    • 효율적인 검색과 업데이트: Map은 키를 기반으로 값을 검색하는데 매우 효율적입니다. 내부적으로 해시 맵(HashMap)이 가장 일반적으로 사용되며, 해시 함수를 이용하여 키의 해시코드를 생성하고 배열에 저장함으로써 빠른 검색과 업데이트를 제공합니다.

    • 다양한 구현체: 자바에서는 Map 인터페이스를 구현한 여러 가지 구현체를 제공합니다. HashMap, LinkedHashMap, TreeMap 등이 있으며, 각각의 구현체는 특정한 용도에 맞게 최적화되어 있습니다. 사용하는 상황과 요구사항에 따라 적절한 구현체를 선택할 수 있습니다.

대표적인 Map<K, V> 컬렉션의 클래스

  1. HashMap <K, V> : 해시 함수를 사용하여 키를 해시코드로 변환하고, 이를 기반으로 내부 배열에 키-값 쌍을 저장합니다.
    해시 함수는 키의 해시코드를 생성하는 역할을 수행하며, 해시코드는 내부 배열의 인덱스로 사용됩니다. 저장공간을 동적으로 관리하며 디폴트값은 16이며, 원소가 16을 넘는 경우 자동으로 저장공간을 확대합니다. 이를 통해 매우 빠른 검색과 삽입 속도를 제공합니다.
    • 중복된 키를 허용하지 않음 : HashMap은 각 키는 유일해야 하며, 중복된 키의 저장을 허용하지 않습니다. 중복된 키를 사용할 경우, 기존 값은 새 값으로 대체됩니다.

    • 순서를 보장하지 않음 : HashMap은 내부 배열에 키-값 쌍을 저장하는 방식으로 동작하며, 저장 순서를 보장하지 않습니다. 입력의 순서와 출력의 순서는 동일하지 않을 수 있습니다. (key 값이 Set으로 관리) 따라서 저장된 순서에 의존하는 작업에는 LinkedHashMap을 사용하는 것이 좋습니다.

  2. HashTable<K, V> : 효율적인 데이터 구조 중 하나로, 키-값 쌍의 저장과 검색에 사용되는 자료구조입니다. 각각의 키(key)는 해시 함수를 통해 고유한 정수 값으로 매핑되고, 이 정수 값을 인덱스로 사용하여 값을 저장하거나 검색할 수 있습니다.

4. Stack<E>

 

Stack<E> 컬렉션은 Vecter<E> 클래스의 자식 클래스이며, 유일하게 클래스입니다. List<E>의 자식이기 때문에 List라고도 할 수 있기 때문에 List<E>의 기본 메서드의 사용이 가능합니다. Stack<E> 컬렉션은 제네릭(Generic) 타입으로 정의되어 있으며, E는 스택에 저장되는 요소의 타입을 의미합니다. 예를 들어, Stack<Integer>는 정수형 요소를 저장하는 스택을 나타냅니다.

Stack<E> 컬렉션은 후입선출(LIFO, Last-In-First-Out) 원칙을 따르는 데이터 구조입니다. 나중에 들어간 데이터가 가장 먼저나오는 구조입니다. 스택은 요소의 삽입과 삭제가 한쪽 끝에서만 이루어지는 선형 자료구조로, 가장 최근에 추가된 요소가 가장 먼저 제거되는 동작 방식을 가지고 있습니다. Stack<E> = Vector<E>의 기본기능 + LIFO 기능 추가 (5개 메서드) push() , peek(), pop(), serach(), empty() 추가된 5개의 메서드를 사용하기 위해서는 꼭 Stack<E> 타입으로 선언해야 합니다.

Stack<E> 컬렉션은 다음과 같은 주요한 메서드를 제공합니다:

  • push(E item): 스택의 맨 위에 요소를 추가합니다.
  • pop(): 스택의 맨 위에 있는 요소를 제거하고 반환합니다.
  • peek(): 스택의 맨 위에 있는 요소를 반환합니다. 실제로 데이터를 꺼내는 것은 아니기때문에 데이터의 변화는 없습니다.
  • empty(): 스택이 비어 있는지 여부를 확인합니다.
  • search(Object o): 지정된 요소를 스택에서 검색하고 상대적 위치(1부터 시작)를 반환합니다. 없는 요소를 출력하면 -1 을 리턴합니다.
728x90
반응형
728x90

 

1. 원격 프로그램 실행

 

브라우저와 원격프로그램을 실행하기 위한 (WAS) 브라우저에서 URL을 입력 후(ip주소) 호출을 한다면 해당 요청을 톰캣이 받아서 8080포트를 실행합니다.

 

 


 

 

 

2 . @Controller와 @RequestMapping이란 ?

 

@Controller와 @RequestMapping은 스프링 프레임워크에서 웹 애플리케이션의 컨트롤러를 정의하고 요청 매핑을 처리하는 데 사용되는 어노테이션입니다.

  1. Controller : @Contoller 어노테이션은 클래스 레벨에 적용되며, 해당 클래스가 스프링 MVC의 컨트롤러임을 나타냅니다.
    스프링은 이 클래스를 컨트롤러로 인식하고 요청을 처리하는 데 필요한 기능을 제공합니다.

  2. RequestMapping (/hello) : @RequestMapping 어노테이션은 메서드 레벨에 적용되며, 해당 메서드가 특정 요청 경로와 연결되는 것을 나타냅니다.
    즉, 클라이언트의 요청이 특정 URL 경로로 들어오면 해당 메서드가 실행되어 요청을 처리하게 됩니다.@RequestMapping 어노테이션은 여러 가지 속성을 가지고 있어서 경로 매칭, HTTP 메서드 제한, 요청 헤더나 파라미터 검증 등 다양한 기능을 설정할 수 있습니다.
            package com.fastcampus.ch2;

            import java.util.Calendar;


            //년 월 일을 입력하면 요일을 알려주는 프로그램
            public class YoilTeller {

                public static void main(String[] args) {
                
                    //1. 입력
                    String year = args[0];
                    String month = args[1];
                    String day = args[2];


                    //문자열이니깐 숫자로 형변환을 함
                    int yyyy = Integer.parseInt(year);
                    int mm = Integer.parseInt(month);
                    int dd = Integer.parseInt(day);
                    

                    //2. 작업
                    Calendar cal = Calendar.getInstance();
                    cal.set(yyyy,mm-1, dd);
                    

                    //요일을 알아낼 수 있는데 1.일요일, 2. 월요일 ..
                    int dayOfweek = cal.get(Calendar.DAY_OF_WEEK);
                    char yoil = "일월화수목금토".charAt(dayOfweek);
                    

                    //3.출력
                    System.out.println(year + "년" + month + "월" + day + "일은");
                    System.out.println(yoil + "요일입니다.");
                }

            }

 

 

 

java 인터프리터가 YoilTeller안의 메인 클래스를 호출하면 이후에 입력된 2022 12 2 값들을 가지고 배열을 만듭니다. String 배열이고 args[0] = 2022 , args[1] = 12 , args[2] = 2 로 담기면서 main 메서드 안에서 해당 배열이 사용이 가능합니다.

원격 프로그램을 브라우저로 URL을 입력하면 톰캣이 HttpServletRequest 객체를 만든 후 요청한 정보를 담습니다. HttpServeletRequest 변수(객체가 담긴 변수) 담은 후 매개변수로 메인메서드에 전달해줍니다.


 

 

3. HttpServeletRequest란 ❓

HttpServletRequest는 서블릿 컨테이너에서 HTTP 요청을 처리하기 위한 객체입니다.
이 객체를 통해 클라이언트로부터 온 HTTP 요청의 정보를 얻을 수 있습니다. HttpServletRequest는 javax.servlet.http 패키지에 포함되어 있습니다.

HttpServletRequest 객체는 클라이언트의 IP 주소, 쿠키 정보, 인증과 관련된 정보 등 다양한 HTTP 요청의 속성과 값을 얻을 수 있도록 메서드와 속성을 제공합니다.
이를 통해 서블릿에서 클라이언트의 요청을 분석하고 처리할 수 있습니다.

HttpServletRequest 객체는 HTTP 요청에 관련된 여러 가지 정보를 제공합니다. 몇 가지 중요한 메서드와 속성은 다음과 같습니다:

getScheme() : HttpServletRequest 인터페이스의 메서드로, 클라이언트의 요청에 사용된 프로토콜 스키마를 반환합니다. 프로토콜 스키마는 요청 URL의 앞부분에 위치한 프로토콜의 이름을 나타냅니다. 보통 http를 출력해줍니다.

getMethod() : HTTP 요청의 메서드를 반환합니다. 예를 들어, "GET", "POST", "PUT", "DELETE" 등의 값을 반환할 수 있습니다.
getRequestURL() : 클라이언트가 보낸 요청의 URL을 반환합니다.
getProtocol() : HTTP 프로토콜의 버전을 반환합니다. 예를 들어, "HTTP/1.1"과 같은 값을 반환할 수 있습니다.
getParameter(String name) : 지정된 이름의 요청 매개변수 값을 반환합니다. HTTP 요청의 쿼리 문자열이나 폼 데이터에서 해당 이름을 가진 매개변수 값을 얻을 수 있습니다.
getHeader(String name) : 지정된 이름의 HTTP 헤더 값을 반환합니다. 예를 들어, "User-Agent", "Content-Type" 등의 값을 얻을 수 있습니다.
getSession() : 요청과 관련된 세션 객체를 반환합니다. 세션을 사용할 수 있는 경우 세션 객체를 반환하고, 세션을 사용할 수 없는 경우 null을 반환합니다.

 

getQueryString() 메서드는 HttpServletRequest 인터페이스의 메서드로, 요청 URL의 쿼리 문자열을 반환합니다. 쿼리 문자열은 URL의 물음표(?) 뒷부분에 위치하며, key=value 형식으로 매개변수와 값이 쌍으로 구성되어 있습니다.

예를 들어, http://example.com/search?q=keyword&page=1와 같은 URL이 있다면, getQueryString() 메서드는 "q=keyword&page=1"을 반환합니다. 이 메서드는 쿼리 문자열이 없는 경우 null을 반환합니다.

getQueryString() 메서드를 사용하여 클라이언트가 전송한 요청의 쿼리 문자열을 분석하고 필요한 매개변수를 추출하는 등의 작업을 수행할 수 있습니다.


 

 

💡 동적 리소스 vs 정적 리소스

 

서버가 제공하는 리소스에는 크게 2가지가 있습니다. 동적 리소스(Dynamic Resources)와 정적 리소스(Static Resources)는 웹 개발에서 사용되는 개념입니다.

  1. 동적 리소스(Dynamic Resources)
    동적 리소스는 클라이언트의 요청에 따라 서버에서 동적으로 생성되는 리소스를 말합니다. 서버 측 코드(예: PHP, Java, Python)를 사용하여 요청을 처리하고, 그에 따라 동적으로 HTML, 데이터베이스 쿼리 결과, API 응답 등을 생성합니다. 동적 리소스는 실행 할 때마다 결과가 변하며, 클라이언트의 요청에 따라 다양한 데이터를 가공하거나 개인화된 콘텐츠를 제공하는 데 사용됩니다. 예를 들어, 사용자가 로그인한 후에만 볼 수 있는 사용자 정보나 , 스트리밍 등이 동적으로 생성되는 웹 페이지가 동적 리소스에 해당합니다.

  2. 정적 리소스(Static Resources)
    정적 리소스는 서버에 이미 저장되어 있는 파일이며, 서버에서 그대로 클라이언트에게 제공됩니다. 정적 리소스는 서버 측 코드의 개입 없이 클라이언트에게 제공되므로 속도가 빠르고 캐싱이 가능합니다. 주로 HTML, CSS, JavaScript, 이미지, 비디오, 오디오 파일 등이 정적 리소스에 해당합니다. 정적 리소스는 서버에 저장된 그대로의 내용을 클라이언트에 전달하므로, 클라이언트의 요청에 따라 동적으로 변하지 않습니다.

    일반적으로 동적 리소스는 서버에서 동적으로 처리되어야 하는 데이터를 다루고, 정적 리소스는 클라이언트에게 제공되는 파일들을 나타냅니다. 동적 리소스는 서버의 로직에 의해 동적으로 생성되므로 처리 비용이 더 많이 소요되지만, 유연성과 개인화된 콘텐츠 제공이 가능합니다. 정적 리소스는 캐싱과 같은 기술을 활용하여 빠른 응답과 성능 향상을 도모할 수 있습니다.

  3. 클라이언트와 서버

클라이언트(Client)와 서버(Server)는 네트워크 환경에서 상호작용하는 컴퓨터 시스템입니다.

  1. 클라이언트(Client) 클라이언트는 사용자가 직접 사용하는 컴퓨터나 디바이스를 말합니다. 일반적으로 웹 브라우저(예: Chrome, Firefox)가 클라이언트의 역할을 수행합니다. 클라이언트는 사용자의 요청을 생성하고 서버로 전송합니다. 사용자는 클라이언트를 통해 서버에 요청을 보내고, 서버로부터 응답을 받아 화면에 결과를 표시합니다. 클라이언트는 사용자 인터페이스를 제공하며, 서버와의 통신을 위한 요청을 생성하고 응답을 받는 역할을 수행합니다.

  2. 서버(Server) 서버는 클라이언트의 요청을 받아 처리하고, 필요한 데이터나 리소스를 제공하는 컴퓨터나 시스템입니다. 서버는 클라이언트의 요청을 받아들여 해당 요청을 처리한 후, 클라이언트에게 응답을 보냅니다. 서버는 데이터베이스, 파일, 웹 페이지, 애플리케이션, API 등을 호스팅하고 관리하는 역할을 수행합니다. 서버는 네트워크를 통해 클라이언트와 통신하며, 클라이언트의 요청에 따라 동적으로 데이터를 생성하거나 처리할 수 있습니다.

  3. 클라이언트와 서버는 클라이언트-서버 아키텍처를 구성하는 두 가지 주요 구성 요소입니다. 클라이언트는 사용자와 상호작용하며 사용자 인터페이스를 통해 요청을 생성하고 응답을 표시합니다. 서버는 클라이언트의 요청을 받아 처리하고 필요한 데이터나 서비스를 제공하여 클라이언트에게 응답합니다. 이렇게 클라이언트와 서버가 함께 동작하여 웹 애플리케이션, 웹 서비스, 모바일 애플리케이션 등 다양한 네트워크 기반의 애플리케이션을 구현할 수 있습니다.
  • 서버의 종류 ( 어떤 서비스를 제공하냐에 따라 달라집니다. Email server / File server / Web server

  • 1대의 PC에 다양한 서버가 사용되고 있을 때, 클라이언트가 전송한 IP주소로는 어떤 서버에 요청한건 지 구분할 수 없기때문에 IP주소 뒤에 꼭 포트번호를 기입하여 구분합니다. 예를 들면 대표전화번호랑 비슷한 개념입니다. (ex) 1588-8888 → 내선번호(담당자 별로 내선번호를 갖고 있게 되는 경우) ip:port# / 웹 서버는 기본으로 : 80을 사용하기 때문에 기본적으로는 생략이 가능합니다.

  • 서버가 포트와 연결(바인딩) 되어 있어야만 소통이 가능합니다 . 서버가 포트를 리스닝하고 있다고도 표현합니다. 포트번호는 0 ~ 1023까지는 전부 예약되어 있기 때문에 쓸 수 없지만 그 이후의 번호는 그 이후의 6만개정도를 사용 가능합니다.

 

 

웹 애플리케이션 서버 (WAS) 란?

Web Application Server (WAS)웹 애플리케이션을 실행하고 관리하는 서버 소프트웨어입니다. 웹 애플리케이션은 클라이언트(웹 브라우저)에서 서버로 요청을 보내고, 서버에서는 해당 요청에 대한 처리를 수행하여 결과를 클라이언트에게 제공합니다. 이러한 웹 애플리케이션을 실행하기 위해 Web Application Server가 필요합니다.

  1. 웹 애플리케이션 실행 환경 제공: Web Application Server는 웹 애플리케이션을 실행하기 위한 실행 환경을 제공합니다. 이 환경에는 필요한 라이브러리, 컴포넌트, 설정 파일 등이 포함됩니다. 또한, 다양한 프로그래밍 언어와 기술을 지원하여 개발자가 웹 애플리케이션을 개발할 수 있도록 합니다.

  2. 요청 처리 및 분배: Web Application Server는 클라이언트로부터 받은 요청을 처리하고, 애플리케이션으로 전달합니다. 요청에 따라 적절한 애플리케이션 컴포넌트(서블릿, JSP 등)를 호출하고, 처리 결과를 다시 클라이언트에게 반환합니다. 또한, 부하 분산을 위해 여러 웹 애플리케이션 인스턴스 간에 요청을 분배할 수 있습니다.

  3. 세션 및 상태 관리: 웹 애플리케이션은 클라이언트와 상호작용하면서 세션과 같은 상태 정보를 유지해야 할 수 있습니다. Web Application Server는 세션 관리를 지원하여 세션 데이터를 저장하고 관리합니다. 이를 통해 클라이언트와의 상태 유지 및 다중 서버 환경에서의 세션 공유가 가능해집니다.

  4. 데이터베이스 연동: 대부분의 웹 애플리케이션은 데이터베이스와 연동하여 데이터를 저장하고 조회합니다. Web Application Server는 데이터베이스와의 연결 관리를 수행하고, SQL 문 실행 및 결과 처리를 지원합니다. 이를 통해 웹 애플리케이션은 데이터베이스와 손쉽게 상호작용할 수 있습니다.

  5. 보안 및 인증: 웹 애플리케이션은 보안과 인증이 중요한 요소입니다. Web Application Server는 사용자 인증, 권한 관리, 데이터 암호화 등의 보안 기능을 제공합니다. 또한, 웹 애플리케이션의 취약점을 탐지하고 방어하기 위한 보안 설정을 제공합니다.

  6. 확장성과 가용성: Web Application Server는 대량의 요청을 처리하고, 다중 서버 구성을 통해 확장성과 가용성을 제공합니다. 여러 대의 서버를 클러스터로 구성하거나 로드 밸런서를 사용하여 부하를 분산할 수 있습니다. 이를 통해 웹 애플리케이션의 성능과 신뢰성을 향상시킬 수 있습니다.

주요한 Web Application Server로는 Apache Tomcat, Jetty, IBM WebSphere, Oracle WebLogic, JBoss 등이 있습니다. 이러한 서버는 다양한 웹 애플리케이션 프레임워크(예: Java Servlet, JavaServer Pages, Ruby on Rails, Django 등)와 함께 사용되어 웹 애플리케이션을 실행하고 관리합니다.

728x90
반응형
728x90

1. Set<E>



자바 컬렉션 프레임워크(Collection Framework)에서 제공하는 인터페이스로, 중복되지 않는 요소들을 유일하게 저장하는 컬렉션입니다. 집합의 개념으로 인덱스 정보를 포함하고 있지 않고, 중복된 요소를 허용하지 않는 순서가 없는 데이터 그룹을 저장하는 인터페이스입니다. 인덱스 정보가 없기때문에 중복된 원소 중 특정 위치 값을 꺼낼 방법이 없습니다.

생성된 객체에서 데이터를 추가하고 출력할 때는 index 정보가 없기때문에 정렬 되어있는 상태가 아닌 주머니에 무작위하게 들어있기 때문에 순서가 보장되지 않습니다.(null 값 조차 한 개만 포함이 가능) 대표적인 클래스로는 HashSet, LinkedHashSet. TreeSet 등이 있습니다.

 

  • set<E> 컬렉션의 특징 : index 정보가 없기 때문에 중복저장이 불가능하기 때문에 중복저장이 가능한지 불가능한지를 판단하는 기준이 될 수 있습니다.

✅ 대표적인 Set<E> 컬렉션의 클래스


1. HashSet<E> : Set<E> 인터페이스를 구현한 클래스 중 하나로, 해시 테이블을 사용하여 요소들을 저장하는 자료구조입니다. 수집(collect)한 원소(Element)를 집합의 형태로 관리하며 저장용량( capacity - 16)을 동적 관리합니다. 입력의 순서와 출력의 순서는 동일하지 않을 수 있으며, HashSet<E>은 일반적으로 요소들의 집합을 관리하고 중복을 제거하는 데에 사용됩니다. 예를 들어, 어떤 항목들의 목록에서 중복된 항목을 제거하고 유일한 항목들을 가져와야 할 때 HashSet<E>을 사용할 수 있습니다. 또한 해시 테이블의 특성상 빠른 검색이 필요한 경우에도 유용하게 사용됩니다. ** Hash - 중복된 데이터들의 저장이 안되려면 다름과 같음을 비교해주기 때문에 중복의 확인이 필요합니다.

  • Iterator<E> (for-each로 대체 가능) : Set<E>은 index정보가 없기때문에 순서대로 요소출력이 어려운데 interator 메서드를 사용하면 set에서 컬렉션 내의 요소를 순차적으로 접근하고 삭제할 수 있는 방법을 제공합니다.
  • Set은 순서자체를 가지고 있지 않기 때문에 순서라는 것은 의미가 없지만 Iterator메서드는 모든 데이터요소에 한번씩 접근해 출력이 가능합니다. 컬렉션의 내부 구조에 상관없이 일관된 방식으로 요소에 접근할 수 있도록 합니다.
      1. hasNext() : Iterator가 다음 요소를 가지고 있는지 확인합니다. 다음 요소가 있을 경우 true를 반환하고, 없을 경우 false를 반환합니다.
      2. next() : Iterator의 현재 위치에서 다음 요소를 반환합니다. 반환된 요소는 컬렉션 내의 다음 요소로 이동합니다.
        • HashSet에서 요소의 중복 여부를 판단하기 위해 hashCode()와 equals() 메서드를 사용합니다.
          • hashCode() 메서드는 객체의 해시 코드를 반환하는 메서드입니다. HashSet은 요소를 저장할 때 요소의 hashCode() 값을 사용하여 해시 테이블에 저장하고 관리합니다.
            저장된 코드 값은 패키지명.클래스명@hashCode가 출력됩니다. 1단계로 동일한 hashCode() 값을 갖는 다른 요소들이 있다면 2단계로 equals() 메서드로 비교하여 중복 여부를 확인합니다.
            hashCode() 메서드는 객체의 내부 상태에 기반하여 정수 값을 반환하는데, 동일한 객체라면 항상 같은 hashCode() 값을 반환해야 합니다.
            HashSet에서 요소의 중복 여부를 판단하기 위해 hashCode() 값을 사용하는 이유는 해시 테이블의 성능을 향상시키기 위해서입니다. 따라서, HashSet을 사용하는 클래스에서는 hashCode() 메서드를 적절히 오버라이딩하여 동일한 객체에 대해 동일한 hashCode() 값을 반환하도록 구현해야 합니다.

          • equals() 메서드는 두 객체가 동등한지 비교하는 메서드입니다. HashSet은 요소의 중복 여부를 판단할 때 equals() 메서드를 사용합니다. HashSet에 이미 저장된 요소와 새로 추가하려는 요소를 equals() 메서드로 비교하여 중복 여부를 확인합니다. equals() 메서드가 true를 반환하는 경우 HashSet은 해당 요소를 중복된 요소로 간주하고 추가하지 않습니다.

          • Iterator<E> (for-each로 대체 가능) : Set<E>은 index정보가 없기때문에 순서대로 요소출력이 어려운데 interator 메서드를 사용하면 set에서 컬렉션 내의 요소를 순차적으로 접근하고 삭제할 수 있는 방법을 제공합니다.
             
            • Set은 순서자체를 가지고 있지 않기 때문에 순서라는 것은 의미가 없지만 Iterator메서드는 모든 데이터요소에 한번씩 접근해 출력이 가능합니다. 컬렉션의 내부 구조에 상관없이 일관된 방식으로 요소에 접근할 수 있도록 합니다.
              1. hasNext() : Iterator가 다음 요소를 가지고 있는지 확인합니다. 다음 요소가 있을 경우 true를 반환하고, 없을 경우 false를 반환합니다.
              2. next() : Iterator의 현재 위치에서 다음 요소를 반환합니다. 반환된 요소는 컬렉션 내의 다음 요소로 이동합니다.


2. TreeSet<E> : TreeSet<E>은 이진 검색 트리의 형태로 요소를 저장하며, 요소들은 정렬된 순서로 저장됩니다. 중복 요소를 허용하지 않으며, 요소들은 자동으로 정렬된 순서로 저장됩니다.
요소들은 기본적으로 오름차순으로 정렬되지만, 요소를 정렬하는 기준을 커스터마이즈할 수도 있습니다. 이진 검색 트리의 성질을 활용하여 요소의 삽입, 삭제, 검색 등의 작업을 빠르게 수행할 수 있습니다.
TreeSet은 Comparable 인터페이스를 구현하는 요소 클래스를 사용하거나, 생성자에 Comparator를 지정하여 요소들의 정렬 기준을 설정할 수 있습니다.
요소 클래스가 Comparable을 구현하지 않고, 별도의 정렬 기준을 지정하지 않으면 요소의 클래스는 **ClassCastException**을 발생시킬 수 있습니다.

  • TreeSet<E> = Set<E>의 기본기능 + 정렬/검색 기능 추가

         public class Ex1_TreeSetBasic {

                public static void main(String[] args) {

                    TreeSet<Integer> treeSet = new TreeSet<>();

                    for (int i = 50; i >0; i-=2) {  treeSet.add(i);      }
                    System.out.println(treeSet.toString());
                    //출력 : [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50]


                    //#1.first() : 정렬된 순서에서 가장 작은 객체를 반환
                    System.out.println(treeSet.first()); //출력 : 2


                    //#2.last() : 정렬된 순서에사 가장 큰 (마지막 순서) 객체를 반환
                    System.out.println(treeSet.last()); //출력 : 50 


                    //#3.lower(E element) : 매개변수로 입력된 원소보다 작은 수들 중 가장 큰 수를 리턴
                    System.out.println(treeSet.lower(26)); //출력 : 24


                    //#4.higher() : 매개변수로 입력된 원소보다 큰 수들 중 가자 작은 수를 리턴
                    System.out.println(treeSet.higher(26)); //출력 : 28


                    //#5.floor(E element) : 입력된 매개변수의 원소와 비교하여 같거나 작은 수들 중 가장 큰 수를 리턴
                    System.out.println(treeSet.floor(25)); //출력 : 24
                    System.out.println(treeSet.floor(26)); //출력 : 26


                    //#6.ceiling(E element) : 입격된 매개변수의 원소와 비교하여 같거나 큰 수들 중 가장 작은 수를 리턴
                    System.out.println(treeSet.ceiling(25)); //출력 : 26
                    System.out.println(treeSet.ceiling(26)); //출력 : 26

 

TreeSet에서 요소들의 크기 비교는 Comparable 인터페이스 또는 Comparator를 통해 이루어집니다.

  1. Comparable 인터페이스를 구현한 요소 클래스를 사용하는 경우 - java.lang.Comparable<T> interface 구현 Comparable 인터페이스를 구현한 클래스의 객체는 compareTo() 메서드를 오버라이드하여 구현해야 합니다. 이 메서드는 두 개의 객체를 비교하여 순서를 결정합니다. TreeSet은 이 메서드를 사용하여 요소들을 정렬하고 크기를 비교합니다. 예를 들어, **treeSet.add(obj)**로 TreeSet에 객체를 추가하면 TreeSet은 **obj.compareTo()**를 호출하여 요소의 크기를 비교하고 적절한 위치에 삽입합니다.

  2. Comparator를 사용하는 경우- java.util.Comparator<T> interface 객체 제공 Comparator는 Comparable을 구현하지 않은 클래스의 요소들을 정렬하기 위해 사용됩니다. Comparator는 compare() 메서드를 구현해야 합니다. TreeSet의 생성자나 **treeSet.add(obj)**와 같은 메서드에 Comparator를 전달하여 TreeSet의 정렬 기준을 지정할 수 있습니다. Comparator를 사용하면 Comparable을 구현하지 않은 클래스의 요소들도 TreeSet에 저장할 수 있습니다.

크기 비교의 결과는 다음과 같습니다:

  • compareTo() 또는 compare() 메서드가 0보다 작은 값 (-1) 을 반환하면, 첫 번째 객체는 두 번째 객체보다 작은 것으로 간주됩니다.
  • compareTo() 또는 compare() 메서드가 0을 반환하면, 두 객체는 동일한 크기로 간주됩니다.
  • compareTo() 또는 compare() 메서드가 0보다 큰 값(1)을 반환하면, 첫 번째 객체는 두 번째 객체보다 큰 것으로 간주됩니다.
           
           class MyClass {
                int data1;
                int data2;

                public MyClass(int data1 ,  int data2) {
                    this.data1 = data1;
                    this.data2 = data2;
                }
            }
            
            //상속과 다형성에 의해서 부모:Comparable 자식 : MyComparableClass 
            class MyComparableClass implements Comparable<MyComparableClass> {
                //MyComparableClass라는 타입을 <>에 작성해 주었고, 이 클래스는 비교에 관련된 내용을 포함시키는데,
                //Comparable이라는 인터페이스를 implements로 구현해주어야 합니다. 
                int data1;
                int data2;

                public MyComparableClass  (int data1 ,  int data2) {
                    this.data1 = data1;
                    this.data2 = data2;
                }
                
                
                //Comparable 인터페이스 안에는 미완성 메서드가 있는데 이 메서드는 compareTo이며, 
                //이 메서드 안에서 대소 크기비교의 기준을 설정 할 수 있습니다. (음수, 0, 양수)
                @Override
                public int compareTo(MyComparableClass o) {
                    if (this.data1 < o.data1) return -1;
                    else if (this.data1 == data2) return 0;
                    else return 1;
                }
            }
728x90
반응형
728x90

💡 JDBC란 




JDBC(Java Database Connectivity)자바 프로그램과 데이터베이스를 연결하여 데이터베이스와의 상호 작용을 가능하게 하는 자바 API(응용 프로그래밍 인터페이스)입니다. JDBC는 데이터베이스에 접속하고 쿼리를 실행하며, 데이터를 검색하고 수정하는 등의 작업을 수행할 수 있습니다.

자바는 기본적으로 DB와 연결지어지려면 JDBC가 필요합니다.

애플리케이션에서 DB에 연동을 해서 데이터를 기존처럼 메모리에 저장하는 것이 아니라 DB에 저장될 수 있도록 하는 것입니다.

Spring과 DB를 연결하기 위해 H2 데이터 베이스를 사용할 예정인데, 해당 DB 설치관련내용은 아래의 링크에서 확인하여 설치를 진행해주시면 됩니다.

우선 JDBC와 h2 database를 사용하려면 build.grale → dependencies 항목에 라이브러리를 추가해주어야 합니다. https://memory-dev.tistory.com/entry/KimSprInt13

implementation 'org.springframework.boot:spring-boot-starter-jdbc'runtimeOnly 'com.h2database:h2'

그리고 repository 폴더에 JdbcMemberRepository를 생성하여 각각의 코드를 작성해 줍니다. Service폴더에는 SpringConfig파일에 DataSource를 생성합니다.

 

 

 

더보기 버튼을 누르시면 JdbcMemberRepository 코드가 보여집니다. (너무 길어 접어서 작성해두었습니다.)

더보기
        package hello.hellospring.repository;

        import hello.hellospring.domain.Member;
        import org.springframework.jdbc.datasource.DataSourceUtils;

        import javax.sql.DataSource;
        import java.sql.*;
        import java.util.ArrayList;
        import java.util.List;
        import java.util.Optional;

        public class JdbcMemberRepository implements MemberRepository {

            private final DataSource dataSource;

            public JdbcMemberRepository(DataSource dataSource) {
                this.dataSource = dataSource;
            }


            @Override
            public Member save(Member member) {
                String sql = "insert into member(name) values(?)";

                Connection conn = null;
                PreparedStatement pstmt = null;
                ResultSet rs = null;

                try {
                    conn = getConnection();
                    pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);

                    pstmt.setString(1, member.getName());

                    pstmt.executeUpdate();
                    rs = pstmt.getGeneratedKeys();

                    if (rs.next()) {
                        member.setId(rs.getLong(1));
                    } else {
                        throw new SQLException("id 조회 실패");
                    }
                    return member;
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                } finally {
                    close(conn, pstmt, rs);
                }
            }

            @Override public Optional<Member> findById(Long id) {
                String sql = "select * from member where id = ?";

                Connection conn = null;
                PreparedStatement pstmt = null;
                ResultSet rs = null;

                try {
                    conn = getConnection();
                    pstmt = conn.prepareStatement(sql);
                    pstmt.setLong(1, id);

                    rs = pstmt.executeQuery();

                    if(rs.next()) {
                        Member member = new Member();
                        member.setId(rs.getLong("id"));
                        member.setName(rs.getString("name"));
                        return Optional.of(member);
                    } else {
                        return Optional.empty();
                    }
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                } finally {
                    close(conn, pstmt, rs);
                }
            }

            @Override
            public List<Member> findAll() {
                String sql = "select * from member";

                Connection conn = null;
                PreparedStatement pstmt = null;
                ResultSet rs = null;

                try {
                    conn = getConnection();
                    pstmt = conn.prepareStatement(sql);

                    rs = pstmt.executeQuery();
                    List<Member> members = new ArrayList<>();

                    while(rs.next()) {
                        Member member = new Member();
                        member.setId(rs.getLong("id"));
                        member.setName(rs.getString("name"));
                        members.add(member);
                    }

                    return members;
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                } finally {
                    close(conn, pstmt, rs);
                }
            }

            @Override
            public Optional<Member> findByName(String name) {
                String sql = "select * from member where name = ?";

                Connection conn = null;
                PreparedStatement pstmt = null;
                ResultSet rs = null;

                try {
                    conn = getConnection();
                    pstmt = conn.prepareStatement(sql);
                    pstmt.setString(1, name);

                    rs = pstmt.executeQuery();

                    if(rs.next()) {
                        Member member = new Member();
                        member.setId(rs.getLong("id"));
                        member.setName(rs.getString("name"));
                        return Optional.of(member);
                    }

                    return Optional.empty();
                } catch (Exception e) {
                    throw new IllegalStateException(e);
                } finally {
                    close(conn, pstmt, rs);
                }
            }

            private Connection getConnection() {
                return DataSourceUtils.getConnection(dataSource);
            }

            private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    if (pstmt != null) {
                        pstmt.close();
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }

                try {
                    if (conn != null) {
                        close(conn);
                    }
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }

            private void close(Connection conn) throws SQLException {
                DataSourceUtils.releaseConnection(conn, dataSource);
            }
        }

 

        package hello.hellospring.service;

        import hello.hellospring.repository.JdbcMemberRepository;
        import hello.hellospring.repository.MemberRepository;
        import hello.hellospring.repository.MemoryMemberRepository;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        import javax.sql.DataSource;

        @Configuration
        public class SpringConfig {
            //스프링이 실행될 때, Configuration을 읽고 하단의 코드를 스프링 빈에 등록하라고 인식하여 등록을 진행합니다.
            //멤버 서비스와 멤버 리포지토리를 스프링 빈에 등록해주고, 스프링빈에 등록되어있는 멤버 리포지토리를 멤버 서비스에 넣어줍니다.

            private DataSource dataSource;
            @Autowired
            public SpringConfig(DataSource dataSource) { this.dataSource = dataSource; }

            @Bean
            public MemberService memberService() {
                return new MemberService(memberRepository());

            }

            @Bean
            public MemberRepository memberRepository() {
                //return new MemoryMemberRepository();
                return new JdbcMemberRepository(dataSource);
            }


        }

 

 

 

h2 DB에서 id와 name을 insert하여 3개의 데이터를 만들어 두었습니다. H2 DB와 스프링이 잘 연결되었는지 확인하기 위해 , 스프링을 실행하여 member url에서 회원 목록을 확인해보겠습니다.

 

 

서버를 실행한 후 회원 목록을 클릭하면 H2 서버 데이터에서 입력해두었던 id, name 데이터가 저장되어 있는 걸 확인할 수 있습니다. 다시 home 브라우저로 돌아간 후 이름을 입력한 후 회원 목록을 확인하면 추가가 되어있고, h2 database에서 table을 조회하면 브라우저에서 등록된 이름 데이터가 저장되어 있는 걸 확인할 수 있습니다.

 

🎈 DB와 연동된 MemberService 이용하여 Test코드 작성해보기

 

👉 통합 TEST 진행해보기

데이터 베이스는 기본적으로 트랜잭션이라는 개념이 있는데 DB에 데이터를 insert를 하게되면 커밋을 진행해야합니다. 테스터가 끝난 후 만약 롤백을 하게된다면 .join 후 검증까지 완료되었는데 롤백하면 DB에서 데이터가 전부 지워지게 됩니다. 여기서 @Transactional을 테스터케이스에 작성해주면 테스트를 실행할 때 트랜잭션을 먼저 실행하고 DB에 데이터를 인서트 한 후 테스터를 마친 후 데이터를 깔끔하게 지워줍니다.

MemberServiceIntegrationTest 코드 (너무 길어 접어서 작성해두었습니다.)

더보기
        package hello.hellospring.service;

        import hello.hellospring.domain.Member;
        import hello.hellospring.repository.MemberRepository;
        import hello.hellospring.repository.MemoryMemberRepository;
        import org.junit.jupiter.api.AfterEach;
        import org.junit.jupiter.api.Test;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.transaction.annotation.Transactional;
        import static org.assertj.core.api.Assertions.*;
        import static org.junit.jupiter.api.Assertions.*;

        @SpringBootTest
        //@Transactional
        class MemberServiceIntegrationTest {

            @Autowired MemberService memberService;
            @Autowired MemberRepository memberRepository;



            //회원가입 서비스가 제대로 작동되는 지 확인해보기
            //test는 한글로 적어도 괜찮다
            @Test
            void 회원가입() {

                //given(어떤데이터) : 테스트 코드를 작성할 땐 어떠한 상황에 주어지는데
                Member member = new Member();
                member.setName("spring");

                //when : 해당 상황을 실행했을 때 (memberService의 join 메서드를 검증)
                Long saveId = memberService.join(member);

                //then(검증부) : 여기 결과가 주어지도록 코드를 작성한다.
                Member findMember = memberService.findOne(saveId).get();
                assertThat(member.getName()).isEqualTo(findMember.getName());

            }

            @Test
            public void 중복_회원_예외() {
                //given
                Member member1 = new Member();
                member1.setName("Spring");

                Member member2 = new Member();
                member2.setName("Spring");



                //when
                memberService.join(member1);
                IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));

                assertThat(e.getMessage()).isEqualTo("이미 존재하는 회원입니다.");


                //then
            }


        }


먼저 h2 데이터 베이스에 저장된 기존 데이터들을 DELETE FROM MEMBER로 지워줍니다. 그리고 상단의 테스트 코드에서 회원가입 기능을 테스트할 수 있는 회원가입 메서드를 실행하게되면 테스트가 진행되며 다시 H2 데이터베이스에 join 메서드가 실행되며 setName(”spring”)의 값이 저장됩니다.

 

 

 

이렇게 “spring”이라는 name이 저장된 후 다시 회원가입 테스트 코드를 실행하게 되면, 아래처럼 같은 아이디를 존재하는 회원으로 인식하여 테스트가 진행되지 않는 경우가 발생합니다. 이 얘기는 하나의 name에 대해 테스트를 진행하고나면 다시 테스트를 진행했을 때 다음 테스트의 실행을 할 수 없다는 의미입니다. 이런 상황에서는 delete문을 DB에다가 또 작성하여 Test 메서드가 실행되고 나면 리포지토리에 저장된 데이터들을 지워줘야하는 추가 코드를 작성해주어야 하는데, 스프링 부트에서는 @SpringBootTest와 @Transactional 애노테이션을 사용하여 테스트가 전부 진행된 후 롤백 될 수 있도록 해주는 기능을 사용할 수 있습니다.

📄 @SpringBootTest와 @Transactional은 스프링 프레임워크에서 사용되는 어노테이션입니다.

@SpringBootTest : 스프링 부트 애플리케이션의 통합 테스트를 위해 사용되는 어노테이션입니다. 테스트 클래스에서 @SpringBootTest를 사용하면 스프링 애플리케이션 컨텍스트를 로드하고, 애플리케이션의 빈(Bean)들을 주입받을 수 있습니다. 이를 통해 실제 환경과 유사한 환경에서 테스트를 수행할 수 있습니다.

@Transactional : 데이터베이스 트랜잭션 처리를 위해 사용되는 어노테이션입니다. @Transactional을 메서드나 클래스에 적용하면 해당 메서드 또는 클래스의 실행이 트랜잭션 내에서 수행됩니다. 트랜잭션은 ACID(원자성, 일관성, 고립성, 지속성) 속성을 보장하며, 데이터베이스 조작을 일관성있게 처리할 수 있습니다. 메서드가 성공적으로 완료되면 트랜잭션은 커밋되고, 예외가 발생하면 롤백됩니다.

롤백(Rollback)트랜잭션에서 이전 상태로 되돌리는 작업을 의미합니다. 트랜잭션이란 하나의 논리적인 작업 단위를 의미하며, 여러 개의 데이터베이스 조작(쿼리 실행, 데이터 변경 등)을 포함할 수 있습니다.

트랜잭션은 ACID 원칙을 따르며, 롤백은 이 중 "원자성(Atomicity)" 속성을 보장하기 위한 메커니즘입니다. 원자성은 트랜잭션 내의 모든 데이터베이스 조작이 "전부 성공"하거나 "전부 실패"하는 것을 보장하는 속성입니다. 즉, 트랜잭션 내의 어떤 작업이라도 실패하면 트랜잭션 전체가 실패로 판단되어 이전 상태로 롤백됩니다.

롤백은 트랜잭션 내의 데이터 변경을 취소하고, 이전 상태로 복구하는 작업을 수행합니다. 데이터베이스 조작이 실패하거나 예외가 발생한 경우에 주로 롤백이 발생하며, 데이터 일관성을 유지하고 데이터베이스의 무결성을 보장하기 위해 사용됩니다.

예를 들어, 트랜잭션 내에서 A와 B라는 두 개의 데이터베이스 조작이 수행되는 경우를 생각해보겠습니다. 만약 B 조작이 실패하여 예외가 발생하면, 롤백이 실행되어 A 조작 역시 취소되고 이전 상태로 복구됩니다. 따라서 트랜잭션은 일관된 상태를 유지하게 됩니다.

롤백은 데이터베이스 관리 시스템(DBMS)에 의해 제공되는 기능으로, 트랜잭션의 시작과 끝을 명시적으로 정의하거나, 프레임워크에서 자동으로 처리될 수 있습니다. 롤백은 데이터 일관성과 안전한 데이터 조작을 위해 중요한 개념으로 사용됩니다.

728x90
반응형
728x90

 

HTTP는 다양한 메서드를 제공하여 클라이언트가 서버에 요청을 보내고, 서버는 해당 요청에 대한 응답을 반환합니다. 가장 널리 사용되는 HTTP 메서드는 다음과 같습니다

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

API URI 설계 (URI : Uniform Resource Identifier)

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

ㅡㅡㅡㅡ 위의 리소스가 좋은 URI 설계일까?ㅡㅡㅡㅡ 가장 중요한 건 리소스 식별입니다.

 

리소스란 ❓

리소스(Resource)는 웹 애플리케이션에서 클라이언트가 요청하고 서버가 제공하는 데이터나 서비스를 의미합니다. 일반적으로 웹 애플리케이션에서 리소스는 웹 페이지, 이미지, 문서, 데이터베이스 레코드 등과 같은 것들을 포함합니다. 리소스는 고유한 식별자(예: URL)를 가지며, 클라이언트는 해당 식별자를 사용하여 리소스에 접근하고 요청할 수 있습니다.

리소스는 웹 애플리케이션의 중요한 구성 요소로, 클라이언트는 HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 리소스에 대한 요청을 보냅니다. 서버는 이러한 요청을 처리하고, 적절한 응답을 반환하여 클라이언트에게 전달합니다. 이를 통해 클라이언트는 웹 애플리케이션에서 필요한 데이터를 조회, 생성, 업데이트, 삭제할 수 있습니다.

리소스는 일반적으로 웹 애플리케이션의 상태를 나타내는 정보를 포함하고, 클라이언트는 이러한 리소스를 조작하여 웹 애플리케이션의 동작을 제어하거나 데이터를 처리합니다. RESTful 웹 서비스의 경우 리소스는 RESTful API의 핵심 개념이며, 클라이언트는 URI(Uniform Resource Identifier)를 사용하여 리소스를 식별하고 조작합니다.

 

URI(Uniform Resource Identifier) 계층 구조리소스를 계층적으로 구성하여 효율적이고 일관성 있게 관리할 수 있는 방법을 제공합니다. URI 계층 구조는 다음과 같은 방식으로 활용될 수 있습니다:

  1. 경로 구분: URI의 경로(Path)를 사용하여 리소스를 계층적으로 구분할 수 있습니다. 경로는 슬래시("/")로 구분되며, 각 경로 요소는 리소스의 상위 레벨을 의미합니다. 예를 들어, /users/john 경로는 "users"라는 상위 리소스 아래의 "john" 리소스를 식별합니다. 이러한 계층 구조를 활용하여 리소스를 조직화하고 접근할 수 있습니다.

  2. 부모-자식 관계: URI의 계층 구조를 활용하여 부모-자식 관계를 표현할 수 있습니다. 예를 들어, /categories/books와 /categories/electronics와 같은 URI는 "categories"라는 부모 리소스 아래에 "books"와 "electronics"라는 자식 리소스를 가지고 있음을 나타냅니다. 이러한 관계를 활용하여 리소스 간의 상호작용을 나타내거나 필요한 데이터를 추출할 수 있습니다.

  3. 컬렉션과 멤버: URI 계층 구조를 활용하여 컬렉션(Collection)과 멤버(Member)를 표현할 수 있습니다. 컬렉션은 여러 개의 리소스를 포함하는 상위 개념이고, 멤버는 개별 리소스를 나타냅니다. 예를 들어, /users는 사용자 컬렉션을, /users/{id}는 특정 사용자를 나타냅니다. 이러한 구조를 활용하여 컬렉션과 멤버 간의 관계를 표현하고 조작할 수 있습니다.

  4. 상태 전이(State Transition): URI 계층 구조는 상태 전이(State Transition)를 표현하는 데에도 활용될 수 있습니다. RESTful API에서는 리소스 간의 상태 전이를 URI와 HTTP 메서드를 조합하여 표현합니다. 예를 들어, /orders/{id}/cancel은 특정 주문의 상태를 취소로 변경하는 상태 전이를 의미합니다. 이러한 URI 계층 구조를 통해 리소스의 상태 전이를 명확하게 표현하고 제어할 수 있습니다.

URI 계층 구조를 활용하여 리소스를 적절하게 조직하고 식별하는 것은 RESTful 아키텍처에서 중요한 개념입니다. 계층 구조를 일관성 있게 설계하고 사용하여 클라이언트가 리소스를 쉽게 찾고 조작할 수 있도록 지원할 수 있습니다.

 

URI(Uniform Resource Identifier) 설계 시 리소스 식별은 URI를 통해 고유한 리소스를 식별하는 것을 의미합니다. URI는 웹에서 리소스에 접근할 수 있는 주소로, 리소스를 고유하게 식별하기 위해 사용됩니다.

리소스 식별은 다음과 같은 원칙을 따라야 합니다:

  1. 유일성: 각 리소스는 고유한 식별자를 가져야 합니다. 다른 리소스와 중복되지 않는 유니크한 식별자를 할당해야 합니다. 이를 통해 각각의 리소스가 독립적으로 식별되고 접근될 수 있습니다.
  2. 명확성: 리소스 식별자는 클라이언트가 어떤 리소스에 접근하고 있는지 명확하게 알 수 있어야 합니다. 의미를 파악하기 쉬운 명시적인 식별자를 사용하는 것이 좋습니다.

  3. 지속성: 리소스 식별자는 변하지 않는 것이 좋습니다. 리소스의 위치나 이름이 변경되지 않는 한, 동일한 식별자로 접근할 수 있어야 합니다. 이는 클라이언트가 항상 유효한 식별자를 사용하여 리소스에 접근할 수 있도록 보장합니다.

리소스 식별은 RESTful 아키텍처의 핵심 원칙 중 하나이며, 웹 애플리케이션에서 리소스를 효과적으로 관리하고 조작하기 위해 중요합니다. 클라이언트는 URI를 사용하여 원하는 리소스에 접근하고 RESTful API를 통해 해당 리소스를 조작할 수 있습니다. 따라서 URI 설계 시 리소스 식별은 명확하고 일관성 있게 이루어져야 합니다.

📕 HTTP 메서드

가장 널리 사용되는 HTTP 메서드는 다음과 같습니다:

  1. GET : 서버로부터 리소스(데이터)를 가져오기 위해 사용되는 메서드입니다. 주로 데이터 조회나 검색에 사용됩니다. GET 요청은 주소 표시줄에 쿼리 매개변수로 데이터를 전달할 수 있습니다.
  2. POST : 서버에 새로운 데이터를 전송하기 위해 사용되는 메서드입니다. 주로 데이터 생성이나 업데이트에 사용됩니다. POST 요청은 요청 본문에 데이터를 담아 서버로 전송합니다. 데이터를 담아서 클라이언트가 서버에 보내야 합니다.
  3. PUT : 서버에 새로운 데이터를 전송하거나 기존 데이터를 업데이트하기 위해 사용되는 메서드입니다. PUT 요청은 요청 본문에 데이터를 담아 지정된 리소스를 대체합니다. 파일을 폴더에 넣는 것과 비슷합니다. 없으면 생성, 있으면 덮어씌어집니다.
  4. DELETE : 서버에서 데이터를 삭제하기 위해 사용되는 메서드입니다. DELETE 요청은 지정된 리소스를 삭제합니다.
  5. PATCH : 서버에 새로운 데이터를 부분적으로 업데이트하기 위해 사용되는 메서드입니다. PATCH 요청은 요청 본문에 업데이트할 데이터만 포함하며, 해당 리소스의 일부만 수정합니다.
  6. HEAD : GET 요청과 유사하지만, 실제 데이터를 가져오지 않고 응답 헤더만 가져옵니다. 주로 리소스의 메타데이터를 확인할 때 사용됩니다.
  7. OPTIONS : 서버가 지원하는 HTTP 메서드 목록을 요청합니다. 주로 CORS (Cross-Origin Resource Sharing) 정책을 확인하기 위해 사용됩니다.

 

✅ GET, POST

👉 GET 메서드 👈

HTTP 메서드 중 GET은 서버로부터 리소스를 요청하기 위해 사용되는 메서드입니다. GET 요청은 서버로부터 데이터를 가져오는 역할을 합니다. 일반적으로 GET 메서드는 조회에서만 사용하며 , 요청한 리소스를 가져와서 응답으로 반환하며, 서버의 상태나 데이터를 변경하지 않습니다.

GET 메서드는 주로 브라우저에서 웹 페이지나 이미지, 동영상 등을 요청할 때 사용됩니다. GET 요청은 URL에 데이터를 포함하여 전송할 수 있으며, 이러한 데이터는 쿼리 파라미터(Query Parameter)의 형태로 전달됩니다. 예를 들어, https://example.com/products?id=123와 같은 URL에서 id=123은 GET 요청에 대한 쿼리 파라미터입니다.

GET 메서드의 주요 특징은 다음과 같습니다:

  • 요청한 리소스를 가져오기 위해 사용됨
  • 데이터를 URL에 쿼리 파라미터로 전달
  • 캐싱이 가능하므로 동일한 요청에 대한 반복적인 요청 시 서버 부하를 줄일 수 있음
  • 요청에 대한 응답으로 데이터를 반환 (HTML, JSON, XML 등)

GET 메서드는 보안이나 데이터 변경과 관련된 작업에는 적합하지 않습니다. 데이터의 생성, 수정, 삭제 등을 요청하기 위해서는 다른 HTTP 메서드 (POST, PUT, DELETE 등)를 사용해야 합니다.

GET 메서드는 브라우저에서 주로 사용되지만, HTTP 클라이언트 라이브러리나 API 호출에서도 GET 메서드를 사용하여 서버로부터 데이터를 요청할 수 있습니다.

👉 POST메서드 👈

HTTP 메서드 중 POST는 클라이언트에서 서버로 데이터를 전송하기 위해 사용되는 메서드입니다. POST 요청은 서버에 새로운 리소스를 생성하거나 기존 리소스를 수정하기 위해 데이터를 전달합니다.

POST 메서드는 일반적으로 HTML 폼(form)을 통해 사용자로부터 입력 받은 데이터를 서버로 전송할 때 사용됩니다. 예를 들어, 회원 가입 폼이나 댓글 작성 폼에서 사용자가 입력한 데이터를 서버로 전송할 때 POST 요청을 사용합니다. POST 요청은 요청 본문(Request Body)에 데이터를 담아 전송하며, URL에는 데이터가 노출되지 않습니다.

POST 메서드의 주요 특징은 다음과 같습니다:

  • 서버에 새로운 리소스를 생성하거나 기존 리소스를 수정하기 위해 사용 - 서버가 아직 식별하지 않은 새 리소스를 생성
  • 요청에 대한 응답으로 생성된 리소스의 정보나 상태를 반환 - 단순히 데이터를 생성하거나, 변경하는 것을 넘어서 프로세스를 처리해야 하는 경우 (ex) 상품 주문에서 → 결제 완료 → 배달 시작(배달시작 버튼을 누르는 것도 POST를 사용) → 배달완료 처럼 단순히 값 변경을 넘어 프로세스의 상태가 변경되는 경우 POST의 결과로 새로운 리소스가 생성되지 않을 수도 있습니다.
  • 다른 메서드로 처리하기 어려운 경우 (ex) JSON으로 조회 데이터를 넘겨야 하는데, GET 메서드를 사용하기 어려운 경우, 애매하면 POST를 사용

POST 메서드는 클라이언트가 서버에게 데이터를 전송하고, 서버는 해당 데이터를 처리하여 새로운 리소스를 생성하거나 기존 리소스를 변경하는 데 사용됩니다.
중요한 점은 POST 요청은 서버의 상태를 변경시키는 작업에 사용되므로, 이러한 작업을 수행할 때에는 적절한 권한과 보안 메커니즘이 필요합니다.

 

✅ PUT, PATCH, DELETE

👉 PUT 메서드 👈

PUT 메서드는 웹 서버에 새로운 리소스를 생성하거나 기존 리소스를 업데이트하는 데 사용됩니다. 클라이언트는 PUT 요청을 통해 서버에 데이터를 전송하고, 해당 데이터를 서버에 저장하거나 갱신할 수 있습니다. 쉽게 이야기하면 리소스를 대체하여 덮어씌워 버립니다.

PUT 요청은 일반적으로 RESTful API에서 자원을 생성하거나 업데이트하는 데 사용됩니다. 클라이언트는 PUT 요청을 보내면 원하는 자원의 위치(URI)와 업데이트할 데이터를 함께 전송합니다. 서버는 해당 위치에 자원을 생성하거나 업데이트하고, 작업 결과를 클라이언트에 응답합니다.

PUT 요청은 클라이언트가 전체 엔티티(리소스를 지정/식별)를 서버에 제공하는 것을 의미합니다. 따라서 클라이언트가 전체 리소스의 위치를 알고 URI를 지정하는 점이 POST와의 큰 차이점입니다. 전체 리소스의 내용을 PUT 요청 본문에 포함하여 서버에 전송해야 합니다. 이미 존재하는 리소스를 업데이트하는 경우에도 클라이언트는 전체 리소스를 제공해야 합니다. 그리고 요청한 리소스의 데이터로 기존 리소스들이 대체되어 데이터가 바뀌어버립니다.

PUT 메서드는 안전한 메서드로 간주되지 않습니다. 이는 서버의 상태나 데이터를 변경할 수 있기 때문입니다. 따라서 PUT 요청은 서버에 영향을 줄 수 있는 작업을 수행하기 때문에 주의해서 사용해야 합니다.

👉 PATCH 메서드 👈

리소스의 업데이트를 가능하게 해주는 메서드 입니다. PATCH 메서드는 서버에게 리소스의 부분적인 변경을 요청하는 데 사용됩니다. PUT 메서드를 사용하게되면 기존에 남아있던 리소스의 일부 변경이 아닌 전체가 대체되는데 이와 달리 PATCH메서드를 사용하게 되면 클라이언트는 전체 리소스를 보내지 않고도 리소스의 일부를 수정할 수 있습니다. 클라이언트는 PATCH 요청을 통해 변경하고자 하는 리소스의 일부를 지정하고, 서버는 해당 부분만 수정합니다. 이는 리소스의 일부를 효율적으로 업데이트할 수 있는 장점을 가지고 있습니다.

👉 DELETE 메서드 👈

DELETE 메서드는 서버에게 리소스의 삭제를 요청하는 데 사용됩니다. 클라이언트가 DELETE 요청을 보내면 서버는 해당 리소스를 삭제하고, 삭제 작업의 결과를 클라이언트에게 응답합니다. DELETE 요청을 통해 리소스를 삭제하면 해당 리소스에 대한 접근이 불가능해지며, 서버에서 완전히 제거됩니다.

728x90
반응형

'[HTTP]' 카테고리의 다른 글

[HTTP] HTTP 헤더  (0) 2023.07.27
[HTTP] HTTP 상태 코드  (0) 2023.07.26
[HTTP] HTTP 메서드의 속성  (0) 2023.07.15
[HTTP] URI와 웹 브라우저 요청 흐름 (URL, HTTP)  (0) 2023.06.28
[HTTP] 인터넷 통신  (0) 2023.06.25

+ Recent posts