기술용어

IT 기술 용어를 쉽고 간단하게 배워봅시다

스프링 프레임워크는 자바 플랫폼을 위한 오픈 소스 애플리케이션 프레임워크로서 간단히 스프링이라고도 합니다.
동적인 웹 사이트를 개발하기 위한 여러 가지 핵심 기술을 제공합니다.

스프링 컨테이너는 스프링 프레임워크에서 IoC (Inversion of Control)를 구현한 컨테이너로서, 애플리케이션에서 사용되는 객체(빈, Bean)의 생성, 관리, 배치 등을 담당하는 역할을 합니다.

IoC는 객체의 생성과 생명주기를 개발자가 아닌 프레임워크나 컨테이너가 관리하는 개념입니다.

스프링 컨테이너는 애플리케이션의 빈들을 관리하고, 이들 간의 의존성을 해결하여 객체의 생명주기를 관리합니다.

📌 스프링 컨테이너의 주요 역할과 특징
■ 빈 관리 (Bean Management)
스프링 컨테이너는 애플리케이션에서 사용되는 빈 객체들을 생성하고 관리하며, 필요한 시점에 소멸시킵니다. 이를 통해 객체의 라이프사이클을 관리할 수 있습니다.

■ 의존성 주입 (Dependency Injection)
스프링은 빈 간의 의존성을 주입해주는 IoC의 핵심 기능을 제공합니다. 이를 통해 객체 간의 결합도를 낮추고 유지보수성을 향상시킵니다.

■ AOP (Aspect-Oriented Programming)
스프링은 관점 지향 프로그래밍을 지원하여, 횡단 관심사(예: 로깅, 트랜잭션 처리)를 분리하여 모듈화할 수 있습니다.

■ 이벤트 처리 (Event Handling)
스프링은 이벤트와 리스너를 통한 이벤트 처리 메커니즘을 제공합니다.

■ 트랜잭션 관리 (Transaction Management)
스프링은 선언적 트랜잭션 관리를 제공하여 개발자가 트랜잭션을 쉽게 관리할 수 있도록 합니다.

스프링 컨테이너는 크게 두 가지 주요 타입으로 나눌 수 있습니다.
ApplicationContext. BeanFactory는 가장 기본적인 컨테이너이며, ApplicationContext는 BeanFactory를 확장한 인터페이스로 다양한 기능을 추가로 제공합니다.

스프링 AOP는 핵심 로직과 부가적인 관심사를 분리하여 모듈화하는 프로그래밍 패러다임 중 하나입니다.

AOP는 주로 횡단 관심사(Cross-Cutting Concerns)를 다루기 위해 사용됩니다.

횡단 관심사는 여러 모듈이나 객체에 공통으로 적용되는 관심사로서, 예를 들면 로깅, 트랜잭션 관리, 보안 등이 있습니다.

📌 스프링 AOP는 프록시 기반의 AOP를 구현하며 주요 기능은 아래와 같습니다.
■ Aspect(어드바이스) : 횡단 관심사를 정의하는 모듈입니다. Aspect는 어드바이스와 포인트컷을 결합하여 특정 지점에서 실행될 코드를 정의합니다.

■ Advice(조언) : Aspect가 특정 지점에서 실행하는 코드 블록입니다. 다섯 가지 종류의 어드바이스가 있습니다.
- Before Advice: 대상 메서드 실행 전에 실행되는 어드바이스
- After Returning Advice: 대상 메서드가 예외 없이 정상적으로 실행된 후에 실행되는 어드바이스
- After Throwing Advice: 대상 메서드가 예외를 던진 후에 실행되는 어드바이스
- After Advice: 대상 메서드 실행 후에 실행되는 어드바이스 (예외 발생 여부와 상관없이 실행)
- Around Advice: 대상 메서드를 감싸서 전/후에 실행되는 어드바이스. 가장 강력한 형태로, 메서드의 실행을 완전히 제어할 수 있음

■ Pointcut(포인트컷) : 어드바이스를 적용할 대상 메서드를 선택하는 규칙입니다. 정규표현식이나 패턴을 사용하여 메서드를 선택할 수 있습니다.

■ Join Point(조인 포인트) : 프로그램 실행 중에 어드바이스가 적용될 수 있는 특정 지점, 예를 들면 메서드 호출이나 예외 발생 등이 조인 포인트가 될 수 있습니다.

■ Weaving(위빙) : 어드바이스를 대상 메서드에 적용하여 새로운 프록시 객체를 생성하는 과정을 의미합니다. Weaving은 컴파일 시점, 로드 시점, 런타임 시점 중 하나에서 수행될 수 있습니다.

스프링 AOP를 사용하면 핵심 비즈니스 로직과 횡단 관심사를 분리함으로써 코드의 모듈화와 재사용성을 높일 수 있습니다. 이를 통해 코드의 가독성과 유지보수성도 향상됩니다.

서비스의 기능에 접근하는 방식을 일관되게 유지하면서 기술 자체를 유연하게 사용할 수 있도록 하는 것을 PSA(일관된 서비스 추상화)라고 한다.
즉, PSA가 적용된 코드는 나의 코드가 바뀌지 않고, 다른 기술로 바꿀 수 있도록 확장성이 좋고, 어떤 기술에 특화되어 있지 않는 코드를 의미한다.

대표적인 예로 Spring의 MVC, Transactional, Cacheable가 있다.

■ Transactional
@Transactional을 예로 들면, 어떤 데이터베이스를 선택하더라도 트랜잭션 기능을 사용할 수 있는것처럼, 이러한 기능을 가능하게 해주는 개념이 PSA(일관된 서비스 추상화)라고 한다.

이게 가능한 이유는 PlatformTransactionManager이라는 최상위 Manager를 사용하고, 각각 사용자의 선언에 따라서 JPATransactionManger, DatasourceTransactionManager, HibernateTransactionManger 등을 상황에 맞게 의존성 주입을 받아 사용하게 만들어졌기 때문이다.

■ Spring MVC
기본적으로 Servlet 기반의 Application을 활용하지만, 개발자는 Spring framework를 사용하면 Servlet을 직접적으로 코딩할 일이 거의 없다.

@Controller, @GET(url) 등의 어노테이션만 활용하게 되면 보이지 않는 곳에서 Spring이 처리해 준다.

즉, 개발자는 HttpServlet을 구현하여 모든 Mapping에 대해 직접적으로 구현할 일이 없게 Service Abstraction을 통해 편리하게 사용할 수 있는것이다.

스프링 JDBC는 스프링 프레임워크에서 제공하는 JDBC 기반의 데이터베이스 액세스 기술을 지원하는 모듈입니다.

JDBC는 자바를 사용하여 데이터베이스에 접근하고 SQL 쿼리를 실행할 수 있도록 하는 자바 API입니다.
스프링 JDBC는 이러한 JDBC의 기능을 좀 더 쉽게 사용할 수 있도록 지원하며 몇 가지 부가적인 기능을 추가합니다.

📌 스프링 JDBC의 주요 특징과 기능은 다음과 같습니다.
■ 간편한 데이터베이스 액세스
- 스프링 JDBC는 일반적인 JDBC 코드보다 더 간결하고 간편한 방식으로 데이터베이스에 액세스할 수 있도록 도와줍니다.
- 예를 들어, JdbcTemplate 클래스를 이용하여 반복적인 JDBC 작업을 줄일 수 있습니다.

■ 예외 처리 및 리소스 관리
- 스프링 JDBC는 데이터베이스 액세스 중 발생할 수 있는 예외를 일관된 방식으로 처리합니다.
- 리소스 관리를 자동으로 수행하여 메모리 누수를 방지합니다.

■ 트랜잭션 관리
- 스프링은 선언적 트랜잭션 관리를 지원합니다.
- @Transactional 어노테이션을 통해 트랜잭션을 선언적으로 설정할 수 있습니다.

■ DAO 패턴 지원
- 스프링은 DAO(Data Access Object) 패턴을 쉽게 구현할 수 있도록 도와주는 다양한 기능을 제공합니다.
- DAO는 데이터베이스 액세스 코드를 캡슐화하여 유지보수성을 높이는데 사용됩니다.

NamedParameterJdbcTemplate: 위치 기반 파라미터 대신 이름 기반 파라미터를 사용할 수 있는 NamedParameterJdbcTemplate 클래스를 제공하여 가독성을 향상시킵니다.

Model-View-Controller 아키텍처를 기반으로 하는 웹 프레임워크입니다.

MVC 아키텍처는 소프트웨어를 모델(데이터 및 비즈니스 로직), 뷰(사용자 인터페이스 및 표현) 및 컨트롤러(입력 및 비즈니스 로직 처리)로 나누어 개발하는 방법을 제공합니다.

📌 스프링 MVC의 주요 특징과 구성 요소
1. DispatcherServlet: 스프링 MVC의 핵심이 되는 서블릿으로 클라이언트의 요청을 받아 적절한 핸들러(Controller)로 전달하고 뷰를 렌더링하여 클라이언트에 응답합니다.
2. Controller: 클라이언트의 요청을 처리하고, 비즈니스 로직을 수행하는 부분입니다. @Controller` 어노테이션을 사용하여 해당 클래스를 컨트롤러로 선언할 수 있습니다.
@Controller
public class MyController {
  @RequestMapping("/myEndpoint")
  public String handleRequest() {
    // 비즈니스 로직 처리
    return "viewName"; // 뷰의 이름 반환
  }
}


3. Model: 비즈니스 로직에서 생성된 데이터를 뷰에 전달하기 위한 객체입니다. 컨트롤러는 모델을 이용하여 뷰에 데이터를 전달합니다.
@Controller
public class MyController {
  @RequestMapping("/myEndpoint")
  public String handleRequest(Model model) {
    // 비즈니스 로직 처리
    String data = "Hello, Spring MVC!";
    
    // 모델에 데이터 추가
    model.addAttribute("message", data);
    
    return "viewName";
  }
}


4. View: 컨트롤러가 처리한 결과를 클라이언트에 표시하기 위한 사용자 인터페이스를 나타냅니다. 일반적으로 JSP, Thymeleaf, FreeMarker와 같은 템플릿 엔진을 사용하여 뷰를 작성합니다.
5. HandlerMapping: 클라이언트의 요청 URL을 어떤 컨트롤러가 처리할지 결정하는 매핑 정보를 관리하는 역할을 합니다.
6. ViewResolver: 컨트롤러에서 반환한 뷰의 이름을 실제 뷰 객체로 매핑하는 역할을 합니다.

□ 스프링 MVC는 다양한 애노테이션 기반의 설정을 지원하며, XML 기반의 설정도 가능합니다.
□ 이를 통해 빠르고 간편하게 웹 애플리케이션을 개발할 수 있습니다.
□ 스프링 MVC는 다양한 기능을 포함하고 있어서 유연하게 확장이 가능하며, 검증, 파일 업로드, 국제화와 같은 다양한 웹 개발 요구사항을 다룰 수 있습니다.
스프링 트랜잭션은 스프링 프레임워크에서 제공하는 트랜잭션 관리 기능으로, 데이터베이스와 같은 리소스에 대한 트랜잭션 처리를 보다 쉽고 안전하게 할 수 있도록 지원합니다.

트랜잭션은 여러 개의 연산이 하나의 논리적 작업 단위로 묶여 있는 상태를 말하며, ACID(Atomicity, Consistency, Isolation, Durability) 속성을 지켜 안전하게 데이터를 관리하는데 중요한 역할을 합니다.

데이터베이스 트랜잭션뿐만 아니라, 메시징 시스템이나 JTA(Java Transaction API)와 같은 다양한 트랜잭션 환경에서도 일관된 방식으로 트랜잭션을 다룰 수 있도록 해줍니다.

📌 스프링 트랜잭션의 특징과 기능
1. 선언적 트랜잭션 관리: 스프링은 트랜잭션을 선언적으로 설정할 수 있는 방법을 제공합니다. @Transactional` 어노테이션을 사용하여 메서드나 클래스에 트랜잭션을 적용할 수 있습니다.
@Transactional
public class MyService {
  // 트랜잭션 처리가 필요한 비즈니스 로직
}


2. 프로그래밍적 트랜잭션 관리: 선언적인 방법 외에도, 스프링은 프로그래밍적으로 트랜잭션을 제어할 수 있는 API도 제공합니다. `TransactionTemplate` 클래스를 사용하면 트랜잭션 경계를 정의하고 프로그래밍적으로 트랜잭션을 조작할 수 있습니다.
public class MyService {
  private TransactionTemplate transactionTemplate;

  public MyService(TransactionTemplate transactionTemplate) {
    this.transactionTemplate = transactionTemplate;
  }

  public void myTransactionalMethod() {
    transactionTemplate.execute(status -> {
      // 트랜잭션 내에서 수행할 비즈니스 로직
      return null; // 또는 반환값
    });
  }
}


3. 여러 데이터 소스 지원: 스프링은 다수의 데이터 소스에 대한 트랜잭션 관리를 지원합니다. 여러 데이터베이스나 JMS(Java Message Service) 등 여러 리소스 간의 트랜잭션을 조정할 수 있습니다.

4. 트랜잭션 속성 설정: `@Transactional` 어노테이션을 통해 트랜잭션의 속성을 설정할 수 있습니다. 예를 들면, 격리 수준, 전파 속성, 읽기 전용 여부 등을 지정할 수 있습니다.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, readOnly = true)
public void myTransactionalMethod() {
  // 비즈니스 로직
}


5. 예외 처리: 트랜잭션 내에서 예외가 발생할 경우 롤백 여부를 설정할 수 있습니다. `rollbackFor`나 `noRollbackFor` 등의 속성을 사용하여 롤백 조건을 설정할 수 있습니다.
@Transactional(rollbackFor = Exception.class)
public void myTransactionalMethod() throws Exception {
  // 비즈니스 로직
  if (someCondition) {
    throw new Exception("Rollback condition");
  }
}

스프링 시큐리티는 다양한 인증(Authentication)과 인가(Authorization) 기능을 제공하여 애플리케이션의 보안을 향상시키는 데 사용됩니다.
주로 웹 애플리케이션에서의 보안 처리를 위해 사용되며, 특히 웹 보안에 대한 다양한 기능을 간편하게 구현할 수 있도록 도와줍니다.

📌 스프링 시큐리티의 주요 기능과 개념은 다음과 같습니다.
1. 인증(Authentication): 사용자의 신원 확인을 담당합니다. 사용자가 누구인지 확인하는 과정으로, 사용자의 아이디와 비밀번호를 검증하거나, 토큰 기반의 인증을 지원합니다.

2. 권한 부여(Authorization): 사용자에 대한 권한을 관리하고, 특정 리소스에 대한 접근 권한을 제어합니다. 권한을 기반으로 사용자에게 특정한 작업을 수행할 수 있는 권한을 부여합니다.

3. 필터 체인(Filter Chain): 스프링 시큐리티는 여러 개의 필터를 사용하여 보안 작업을 처리합니다. 각각의 필터는 특정한 보안 작업을 담당하며, 필터 체인을 통해 연결되어 작업을 순차적으로 수행합니다.

4. 세션 관리(Session Management): 사용자의 세션을 관리하고, 세션 타임아웃, 동시 로그인 제한과 같은 기능을 제공합니다.

5. CSRF(Cross-Site Request Forgery) 방어: 웹 어플리케이션에서 일반적으로 발생할 수 있는 CSRF 공격으로부터 보호합니다.

6. 로그인 및 로그아웃 처리: 사용자의 로그인과 로그아웃에 대한 처리를 간편하게 구현할 수 있도록 지원합니다.

7. Remember-Me 기능: 사용자의 로그인 상태를 기억하여, 다음에 접속할 때 자동으로 로그인되도록 하는 기능을 지원합니다.

□ 스프링 시큐리티는 XML 기반의 설정 뿐만 아니라, 애노테이션을 사용한 Java 기반의 설정도 지원하며, 다양한 커스터마이징이 가능합니다.
□ 보안 설정이 필요한 부분에서는 쉽게 스프링 시큐리티를 통합하여 사용할 수 있습니다.
□ 이를 통해 개발자는 보안 구현에 필요한 상세한 부분을 신경쓰지 않고도 안전하고 효과적인 보안 기능을 쉽게 도입할 수 있습니다.`

자바 프로그래밍 언어에서 함수형 프로그래밍을 지원하기 위해 Java 8에서 도입된 기능입니다.

익명 함수, 함수형 인터페이스 구현에 간결하게 사용할 수 있습니다.

추상 클래스는 공통 코드를 재사용하고 싶을 때 사용합니다.

인터페이스는 다중 상속이 필요하거나 클래스 계층 구조와 독립적으로 동작하는 기능을 정의할 때 주로 사용됩니다.

추상 클래스와 인터페이스를 함께 사용하여 다중 상속과 공통 구현을 동시에 사용하는 것도 가능합니다. (AbstractMap)

코드상의 특징으로는 생성자 사용 가능 유무 (추상클래스 가능), 다중 상속 가능 유무(인터페이스 가능)가 있습니다.

객체와 관계형 데이터베이스 간의 매핑을 자동화하는 프로그래밍 기술이나 도구를 가리킵니다.

객체 모델과 관계형 데이터베이스의 테이블과의 간격을 줄여주고, 데이터베이스 조작을 객체 지향적으로 다룰 수 있도록 도와줍니다.
주요 목적은 객체와 데이터베이스 간의 불일치를 해결하고 개발자가 더욱 편리하게 데이터베이스와 상호작용할 수 있도록 하는 것입니다.

여러 ORM 프레임워크들이 있으며, 대표적인 것으로는 Hibernate, JPA(Java Persistence API) 등이 있습니다.

JPA는 자바에서 관계형 데이터베이스를 사용하기 위한 API로, 자바 표준 명세(Java EE의 일부)입니다.

■ 인터페이스
JPA는 표준 인터페이스를 제공하며, 이 인터페이스를 구현한 구현체들이 실제로 데이터베이스와의 통신을 담당합니다.

■ ORM 표준
JPA는 ORM(객체-관계 매핑)을 위한 표준 명세를 제공하며, 개발자들이 ORM을 사용할 때 일관된 방식으로 코드를 작성할 수 있도록 합니다.

■ Vendor-Neutral
JPA는 벤더(데이터베이스 공급자)에 독립적인 특성을 가지므로, 다양한 데이터베이스에 대해 동일한 코드를 사용할 수 있습니다.

■ 영속성 컨텍스트
JPA에서는 영속성 컨텍스트(Persistence Context)라는 개념을 도입하여, 객체의 상태를 데이터베이스와 일치시키는데 사용됩니다.

Hibernate는 자바 기반의 ORM 프레임워크로 JPA를 구현한 구현체 입니다.

ORM은 데이터베이스의 테이블과 자바 객체 사이의 매핑을 자동으로 처리함으로써 개발자가 직접 SQL 쿼리를 작성하지 않아도 더 쉽게 데이터베이스를 다룰 수 있게 해줍니다.

📌 Hibernate는 다음과 같은 주요 기능과 특징을 제공합니다:

1. 객체-관계 매핑 (ORM): Hibernate는 자바 객체와 데이터베이스 테이블 간의 매핑을 지원합니다. 개발자는 객체를 통해 데이터베이스에 접근하고 조작할 수 있습니다.

2. 자동 테이블 생성: Hibernate는 객체 모델을 기반으로 데이터베이스 테이블을 자동으로 생성할 수 있습니다. 이를 통해 개발자는 테이블 생성에 대한 SQL을 직접 작성할 필요가 없습니다.

3. 다양한 데이터베이스 지원: Hibernate는 여러 데이터베이스와 연동할 수 있는 기능을 제공합니다. 지원하는 데이터베이스 종류에 제약이 적고, 여러 데이터베이스 간의 이식성을 향상시킵니다.

4. JPQL(Java Persistence Query Language): Hibernate는 객체 지향 쿼리 언어인 JPQL을 제공하여 객체를 대상으로 하는 쿼리를 작성할 수 있습니다. 이는 데이터베이스에 대한 종속성을 줄이고, 객체 지향적인 접근을 지원합니다.

5. 캐싱(Caching): Hibernate는 성능 향상을 위해 캐싱을 지원합니다. 쿼리 결과나 개체를 메모리에 캐시하여 반복적인 데이터베이스 액세스를 최소화합니다.

6. 트랜잭션 관리: Hibernate는 트랜잭션을 관리하고 롤백, 커밋 등을 처리하는 기능을 제공하여 데이터 일관성을 유지합니다.

7. 객체 상속과 다형성 지원: Hibernate는 객체 상속과 다형성을 지원하여 객체 모델을 더욱 유연하게 구성할 수 있도록 도와줍니다.

8. Spring과의 통합: Hibernate는 스프링 프레임워크와 잘 통합되어 사용됩니다. 스프링과 함께 사용되면서 트랜잭션 관리, 의존성 주입(DI), AOP(Aspect-Oriented Programming) 등의 스프링 기능을 효과적으로 활용할 수 있습니다.

Hibernate를 사용하면 데이터베이스 연동을 편리하게 처리할 수 있고, 객체 지향적인 코드를 유지하면서 데이터베이스와의 상호 작용을 단순화할 수 있습니다.

의존성 주입은 객체 간의 의존성을 외부에서 주입하는 디자인 패턴입니다.

IOC 컨테이너는 객체의 생명 주기와 의존성을 관리하며, 객체를 생성하고 필요한 의존성을 주입합니다.

스프링에서의 빈은 스프링 컨테이너에 의해 관리되는 객체를 의미합니다.

스프링 빈은 스프링 IoC(Inversion of Control) 컨테이너에 등록되어 라이프사이클을 관리하고, 의존성 주입(Dependency Injection)을 통해 다른 빈과의 관계를 설정합니다.

스프링 빈은 일반적으로 POJO(Plain Old Java Object)를 기반으로 하며, 스프링 컨테이너에 의해 생성, 관리, 소멸됩니다.

스프링 부트는 스프링 기반의 애플리케이션을 쉽게 개발하고 실행하기 위한 프레임워크입니다.

자동 구성(starter), 내장 서버, 간단한 빌드 설정 등을 제공하여 빠르게 프로젝트를 구축할 수 있습니다.

교착상태(Deadlock)는 프로세스나 스레드가 서로의 작업이 끝나기를 기다리면서 무한히 대기하게 되는 상태를 말합니다.

교착상태를 해결하기 위한 주요 방법들은 다음과 같습니다.
■ 예방 (Prevention)
- 상호배제: 하나의 프로세스가 자원을 점유한 상태에서는 다른 프로세스가 해당 자원을 사용할 수 없도록 하는 것을 방지합니다.
- 점유 대기: 프로세스가 자원을 점유한 상태에서 다른 자원을 요청하는 경우, 모든 자원을 동시에 할당하거나 하나도 할당하지 않는 것을 방지합니다.
- 비선점: 어떤 프로세스가 사용 중인 자원을 강제로 빼앗지 않는 것을 방지합니다.
- 환형(순환) 대기: 자원을 순환적으로 요청하는 것을 방지합니다.

■ 회피 (Avoidance)
은행원 알고리즘(Banker's Algorithm): 프로세스가 자원을 요청할 때 시스템이 여전히 안전한지를 사전에 확인하여 자원을 할당하는 방법입니다.
안전한 상태인 경우에만 자원을 할당합니다.

■ 검출 및 회복 (Detection and Recovery)
- 교착상태 검출: 주기적으로 시스템 상태를 검사하여 교착상태 여부를 확인합니다.
- 교착상태 회복: 교착상태가 감지되면, 교착 상태에 있는 프로세스 중 하나 이상을 중지하거나, 해당 프로세스가 점유한 자원을 강제로 해제하여 회복합니다.

■ 타임아웃 설정
프로세스가 자원을 요청할 때, 일정 시간 이내에 자원을 할당받지 못하면 해당 요청을 취소하고 다른 자원을 요청하는 방식입니다.
하지만 이 방법은 효율적인 자원 사용을 방해할 수 있습니다.

■ 자원 할당 순서 변경
시스템에서 사용되는 자원의 할당 순서를 변경하여 교착상태를 피할 수 있습니다.
모든 프로세스가 동일한 순서로 자원을 요청하도록 강제하는 방식이 있습니다.

■ 다른 자원 사용
교착상태의 가능성을 최소화하기 위해 더 적은 수의 자원을 사용하거나 다른 유형의 자원을 사용하는 방법을 고려할 수 있습니다.

1. 스레드 안전성 문제
문제: 병렬 스트림은 여러 스레드에서 동시에 작업을 수행하므로, 스레드 안전성 문제가 발생할 수 있습니다.
해결 방법: 스레드 안전한 컬렉션 또는 동기화 기법을 사용하여 공유 데이터에 대한 안전한 접근을 보장합니다.

2. 데드락 (Deadlock) 및 교착상태 (Starvation)
문제: 여러 스레드가 동시에 자원에 접근할 때 데드락이나 교착상태가 발생할 수 있습니다.
해결 방법: 안전한 자원 할당과 사용 패턴을 따르고, 적절한 동기화 메커니즘을 사용하여 데드락을 방지합니다.

3. 병렬화 오버헤드
문제: 작은 크기의 데이터나 간단한 연산에 대해서는 병렬화를 사용하는 것이 더 많은 오버헤드를 유발할 수 있습니다.
해결 방법: 병렬화가 실제로 성능을 향상시키는지 평가하고, 작은 데이터셋에는 병렬화를 적용하지 않는 등 세심한 고려가 필요합니다.

4. 순서 의존성 문제
문제: 병렬 스트림의 연산에서 순서에 의존하는 경우, 원하는 결과가 나오지 않을 수 있습니다.
해결 방법: 순서에 의존하지 않는 연산에 대해서만 병렬 스트림을 사용하거나, 연산의 결과를 정렬하여 순서를 보존합니다.

5. 성능 저하
문제: 병렬 스트림을 사용하면 모든 코어가 사용되지 않는 경우에도 성능이 저하될 수 있습니다.
해결 방법: 데이터 크기, 작업의 병렬화 가능성, 하드웨어 사양 등을 고려하여 병렬 스트림을 적절하게 사용합니다.

6. 메모리 사용량 증가
문제: 병렬 처리에 필요한 추가적인 메모리 사용으로 인해 힙 메모리 부족이나 GC(Garbage Collection) 오버헤드가 발생할 수 있습니다.
해결 방법: 메모리 사용을 최적화하기 위해 적절한 분할 크기를 선택하거나, 스트림을 순차적으로 처리하는 것을 고려합니다.

7. 병렬 스트림이 항상 성능을 향상시키지 않는 경우
문제: 모든 유형의 작업에 대해 병렬 스트림이 항상 성능 향상을 가져오지 않을 수 있습니다.
해결 방법: 작업의 특성에 따라서 병렬 스트림을 선택하는 것이 아니라, 각각의 상황에 맞게 평가하여 결정합니다.

이러한 문제들을 최소화하기 위해서는 세심한 고려와 테스트가 필요하며, 특히 병렬 처리가 성능을 향상시킬 수 있는 상황인지를 신중하게 판단해야 합니다.

동적으로 생성된 객체와 배열이 저장되는 영역입니다.

모든 객체는 힙에 저장됩니다.
힙은 프로그램이 실행되는 동안 동적으로 할당되고 해제되는 메모리를 담당합니다.

가비지 컬렉션에 의해 관리되며, 더 이상 사용되지 않는 객체들은 자동으로 수거됩니다.
힙은 크기가 동적으로 조절되므로, 프로그램이 실행되는 동안 계속해서 메모리가 할당 및 해제됩니다.

메소드 호출과 지역 변수가 저장되는 영역입니다.

각 스레드마다 별도의 스택이 생성되며, 스레드의 메소드 호출 및 지역 변수를 저장합니다.

메소드가 호출될 때마다 해당 메소드에 대한 지역 변수와 메소드 호출 정보가 스택에 쌓입니다.

메소드가 종료되면 해당 메소드에 대한 정보가 스택에서 제거됩니다.

스택은 크기가 정적으로 고정되어 있어, 메모리 할당과 해제가 빠르게 이루어집니다.

오직 하나의 스레드 또는 프로세스만이 공유 자원에 접근할 수 있도록 하는 동기화 기법입니다.

일반적으로 크리티컬 섹션(Critical Section)에 대한 잠금(lock)을 제공하며, 해당 섹션을 하나의 스레드만 실행할 수 있게 합니다.

여러 스레드 또는 프로세스가 공유 자원에 접근할 수 있도록 허용하는 동기화 기법입니다.

세마포어는 정수 변수로, 해당 변수가 나타내는 값에 따라 스레드가 진입을 허용하거나 차단합니다.

값이 양수이면 그만큼의 스레드가 동시에 진입 가능하며, 값이 음수이면 해당 수만큼의 스레드가 대기 상태로 들어갑니다.

둘 다 해시 기반의 맵(Map) 구현체이지만 몇 가지 중요한 차이점이 있습니다.

■ 동기화 (Synchronization)
- 가장 큰 차이 중 하나는 동기화 여부입니다.
- Hashtable은 모든 메소드에 대해 동기화가 되어 있어 멀티스레드 환경에서 안전합니다.
- 반면에 HashMap은 동기화를 제공하지 않기 때문에 단일 스레드 환경에서 사용할 때 더 효율적입니다.

자바 5 이후부터는 ConcurrentHashMap 등의 더 효율적인 동시성 컬렉션 클래스들이 등장하면서 HashMap이 멀티스레드 환경에서의 사용에 더 적합해졌습니다.

■ null 허용 여부
- Hashtable은 키나 값으로 null을 허용하지 않습니다.
- null을 사용하려하면 NullPointerException이 발생합니다.
- 반면에 HashMap은 키와 값 모두 null을 허용합니다.

■ 성능
- 일반적으로 HashMap이 Hashtable보다 높은 성능을 제공합니다.
- HashMap은 동기화를 제공하지 않기 때문에 단일 스레드 환경에서 더 빠릅니다.
- 또한, HashMap은 초기 용량(capacity)과 로드 팩터(load factor)를 설정할 수 있어서 더 효율적으로 메모리를 사용할 수 있습니다.

■ Iterator 및 Enumeration
Hashtable은 Enumeration을 사용하여 요소에 접근할 수 있습니다. 반면에 HashMap은 Iterator를 사용합니다. Enumeration은 Iterator보다 기능이 적고, Iterator는 보다 유연하며 안전합니다.

■ 상속
Hashtable은 Dictionary 클래스를 확장하고 있지만, HashMap은 AbstractMap 클래스를 확장하고 있습니다. Dictionary는 구식의 인터페이스로, 새로운 코드에서는 사용되지 않는 편입니다.

객체 또는 데이터를 외부 시스템에서도 사용 할수 있도록 바이트 스트림 형태로 연속적인(serial) 데이터로 변환하는 기술을 일컫는다.

직렬화된 데이터는 이후에 역직렬화(deserialization)를 통해 원래의 객체로 복원될 수 있습니다.

📌 주요 목적은 다음과 같습니다.
1. 객체의 저장
직렬화를 통해 객체의 상태를 파일이나 데이터베이스에 저장할 수 있습니다. 나중에 필요할 때 객체를 역직렬화하여 복원할 수 있습니다.

2. 네트워크 통신
직렬화를 사용하면 객체를 네트워크를 통해 전송할 수 있습니다. 객체를 바이트 스트림으로 변환하여 전송하고, 수신 측에서는 역직렬화를 통해 객체를 복원할 수 있습니다.

3. 분산 시스템에서의 객체 공유
여러 시스템 간에 객체를 주고받을 때 직렬화를 사용하여 객체를 전송하고 받을 수 있습니다.

자바에서는 java.io.Serializable` 인터페이스를 구현함으로써 객체를 직렬화할 수 있습니다. 해당 인터페이스를 구현한 클래스의 인스턴스는 객체의 상태를 바이트 스트림으로 변환할 수 있습니다.

// 직렬화 가능한 클래스
class MySerializableObject implements Serializable {
  private static final long serialVersionUID = 1L;
  
  private String name;
  private int age;

  // 생성자, getter, setter 등이 있다고 가정
}

public class SerializationExample {
  public static void main(String[] args) {
    MySerializableObject myObject = new MySerializableObject();
    myObject.setName("John");
    myObject.setAge(30);

    // 객체를 직렬화하여 파일에 저장
    try (ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("serializedObject.dat"))) {
      outputStream.writeObject(myObject);
    } catch (IOException e) {
      e.printStackTrace();
    }

    // 파일에서 객체를 역직렬화하여 복원
    try (ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("serializedObject.dat"))) {
      MySerializableObject restoredObject = (MySerializableObject) inputStream.readObject();
      System.out.println("Name: " + restoredObject.getName());
      System.out.println("Age: " + restoredObject.getAge());
    } catch (IOException | ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}


이 예제에서 `MySerializableObject` 클래스는 `Serializable` 인터페이스를 구현하고 있습니다. 객체를 직렬화하여 파일에 저장한 후, 파일에서 읽어와 역직렬화하여 복원합니다. 이를 통해 객체의 상태가 유지되며 데이터를 파일에 저장하거나 네트워크를 통해 전송할 수 있습니다.

■ Filter
- Servlet Specification에 따라 동작하는 기능
- DispatcherServlet 이전에 위치하며, 요청이 Controller에 도달하기 전에 가로챈다.
- HttpServletRequest, HttpServletResponse 객체를 인자로 받아 처리할 수 있다.
- 여러 개의 Filter를 등록하여 체인 형태로 동작할 수 있으며, 순서대로 실행된다.
- 전역적으로 적용되며, 모든 URL 패턴에 대해 동작한다.

■ Interceptor
- Spring MVC Framework에서 지원하는 기능
- DispatcherServlet에서 Controller로 가는 요청, Controller에서 DispatcherServlet으로 가는 응답, 그리고 Exception 발생시 동작한다.
- HandlerInterceptor 인터페이스를 구현하여 사용하며, preHandle(), postHandle(), afterCompletion() 메서드를 정의하여 처리한다.
- DispatcherServlet과 Controller 사이에서 동작하므로, Controller에서 처리하는 모든 RequestMapping에 적용된다.
- HandlerInterceptor를 구현한 클래스는 스프링 빈으로 등록하여 사용한다.

==' 연산은 참조 비교로, 두 객체가 같은 메모리 공간을 가리키는 지를 확인하는 연산입니다.

'equals' 연산은, 두 객체의 내부 값이 같은 지 내용을 비교합니다.
기본 타입에 대해서는 사용할 수 없고, 객체 비교시 override해서 원하는 방식으로 수정이 가능합니다.

== : 본질까지 같음. (같은 메모리 내의 데이터) / equals : 값만 같음.

public : 접근에 제한이 없음
private : 자기 자신 클래스 내에서만 접근 가능
default : 동일한 패키지 내에서만 접근 가능
protected : 동일한 패키지 내에서만 접근 가능 + 상속을 이용한 접근 가능

■ 원시자료형 타입(Primitive Type)
- 기본 타입의 크기가 작고 고정적이기 때문에 메모리 Stack 영역 에 저장됩니다.
- 정수형 : byte, short, int, long
- 실수형 : float, double
- 논리형 : boolean
- 문자형 : char

■ 참조 타입(Reference Type)
- 원시자료형을 제외하고는 모두 참조형이다.
- String과 박싱 타입인 Integer 등이 있습니다.
- 참조 타입은 데이터의 크기가 가변적이고, 동적이므로 Heap 영역 에서 관리됩니다.
- 데이터는 Heap 영역에서 관리되지만 메모리의 주소값은 Stack 영역에 담깁니다.
- new 키워드를 이용해 객체를 생성하여 데이터가 생성된 주소를 참조하는 타입
- String과 배열은 일반적인 참조 타입과 달리 new 없이 생성 가능하지만 참조타입입니다.
- 더 이상 참조하는 변수가 없을 때 GC에 의해 삭제됩니다.

두 객체가 동일한 객체인지 비교할 때 사용하고, Heap 영역에 저장된 객체의 메모리 주소를 반환합니다.

원시자료형 타입의 데이터를 객체로 취급해야 할 경우 사용하는 클래스로, 기본 타입의 데이터를 객체로 변환해주는 클래스입니다.

산술 연산을 위해 정의된 클래스가 아니므로, 인스턴스 내의 값을 변경할 수는 없습니다.

■ 원시자료형 ➝ 래퍼클래스
- byte ➝ Byte
- short ➝ Short
- int ➝ Integer
- long ➝ Long
- float ➝ Float
- double ➝ Double
- char ➝ Character
- boolean ➝ Boolean

■ 박싱
기본 타입의 변수를 래퍼클래스의 인스턴스로 변경하는 과정

■ 언박싱
래퍼클래스의 인스터스를 다시 기본 타입으로 꺼내는 과정을

❓ 래퍼 클래스 내의 값을 비교할 경우에는 ==을 사용해야 할까? equals를 사용해야 할까?

== 을 사용하면 참조 비교를 수행해 값을 비교하지 않습니다. equals를 사용하면 인스턴스 내의 값을 비교합니다.

public class Wrapper03 {
  public static void main(String[] args) {
   Integer num1 = new Integer(10);
   Integer num2 = new Integer(20);
   Integer num3 = new Integer(10);

   System.out.println(num1 < num2);    // true
   System.out.println(num1 == num3);   // false
   System.out.println(num1.equals(num3)); // true
   
   Integer num = new Integer(17); // 박싱
   int n = num.intValue();    // 언박싱
   System.out.println(n); // 출력 값: 17


   Character ch = 'X'; // Character ch = new Character('X'); : 오토박싱
   char c = ch;    // char c = ch.charValue();      : 오토언박싱
   System.out.println(c); // 출력 값: X
  }
}

클래스당 하나만 생성되고, 동일한 클래스의 모든 객체들에 의해 공유됩니다.

다른 객체들이 생기기 전에 미리 생성되고, 프로그램 종료시에 사라집니다.

static 멤버는 프로그램 시작 시, 클래스 로더에 의해 메모리에 로드되어 인스턴스를 생성하지 않아도 호출이 가능하기 때문입니다.

Runtime Data Area에서 Method Area(Static Area)라고 불리는 영역에 static 키워드를 가진 변수, 메소드가 생성됩니다.
따라서 JVM은 별도로 인스턴스를 생성하지 않아도, Method Area에 로드된 main()을 실행하게 됩니다.

■ final 키워드
- 변수, 메서드 클래스가 변경 불가능 하도록 만든다.
- 기본 타입 변수에 적용 시 해당 변수의 값 변경 불가능하다.
- 참조 변수에 적용 시 참조 변수가 힙 내의 다른 객체를 가리키도록 변경할 수 없다.
- 메서드에 적용 시 해당 메서드를 오버라이드할 수 없다.(오버로딩은 가능)
- 클래스에 적용 시 해당 클래스를 상속 받아서 사용할 수 없다.

■ finally 키워드
- try catch 블록 뒤에서 항상 실행될 코드 블록을 정의하기 위해 사용됩니다..

■ finalize 메서드
- 가비지 컬렉터가 더 이상의 참조가 존재하지 않는 객체를 메모리에서 삭제하려고 할 때 호출됩니다.

try-catch-finally 구문에서 리소스를 생성하게 되면, 생성은 try에서 하고 반납은 finally에서 하다보니 실수의 발생 여지가 있었습니다.

그래서 나온 구문으로 try-with-resources가 있습니다.

try 옆에 괄호로 리소스를 생성해주면, 따로 반납 코드를 작성하지 않아도 자동으로 리소스를 반납합니다.

제네릭(Generic)은 클래스 내부에서 타입을 지정하는 것이 아닌, 외부에서 사용자에 의해 지정되는 것을 의미합니다.

1. 잘못된 타입이 들어오는 것을 컴파일 단계에서 방지할 수 있습니다.
2. 클래스 외부에서 타입을 지정하기 때문에 따로 타입을 체크하고 변환할 필요가 없어 관리하기 용이합니다.
3. 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아집니다.

■ 직렬화
- 자바 시스템 내부에서 사용되는 객체 또는 데이터를 외부의 자바 시스템에서도 사용할 수 있도록 바이트 형태로 데이터를 변환하는 기술
- 조건: 자바 기본 타입, Serializable 인터페이스 상속받은 객체
- ObjectOutputStream 객체를 이용

■ 역직렬화
- 바이트로 변환된 데이터를 다시 객체로 변환하는 기술
- ObjectInputStream 객체를 이용

■ 추상클래스
abstract 키워드를 사용해 선언한 클래스로, 추상 메소드를 최소 한 개 이상 포함한 클래스입니다.

■ 추상메소드
abstract 키워드를 사용해 원형만 선언되고 내부 코드는 작성하지 않은 메서드를 뜻합니다.

추상클래스 내부에 추상메소드 외의 다른 것들도 추가가 가능하다는 것이 특징이고, 추상클래스의 사용 주 목적은 관련성이 높은 클래스 간의 코드를 공유하고 확장하고 싶은 목적입니다.

■ 인터페이스
- interface 키워드를 사용해 선언하며 default와 static을 제외하고는 추상 메소드와 상수만을 포함합니다.
- interface 내부의 모든 메소드는 추상 메소드로, abstract public이 생략되어 있는 상태입니다.
- 상수 필드는 public static final이 생략되어 있습니다.

인터페이스는 다중 상속이 가능하며 관련성이 없는 클래스들의 논리적으로 같은 기능을 자신에 맞게 구현을 강제하는데에 목적을 갖습니다.

Image 0



Error와 Exception은 둘 다 Throwable 클래스의 하위 클래스이지만 차이가 있습니다.

■ Error
- Error는 프로그램이 심각한 문제에 직면했을 때 발생합니다.
- 프로그래머가 미리 예측하거나 처리할 수 없는 문제를 나타냅니다.
- 일반적으로 시스템 레벨의 문제로, 프로그램에서 복구할 수 없는 상황을 나타냅니다.
- 예를 들면 OutOfMemoryError(메모리가 부족한 경우)나 StackOverflowError(스택 오버플로우) 등이 있습니다.
- Error는 주로 시스템 환경의 문제 또는 프로그램 실행에 필요한 리소스의 부족과 같은 예상치 못한 상황에서 발생합니다.

■ Exception
- Exception은 프로그램 실행 중 발생하는 예외적인 상황을 나타냅니다.
- 예외는 프로그램이 예상할 수 있는 상황에서 발생하며, 프로그래머가 예외를 처리할 수 있도록 예외 처리 코드를 작성할 수 있습니다.

■ Exception은 크게 두 가지로 나뉩니다: checked exception과 unchecked exception
▶ Checked Exception
반드시 처리해야 하는 예외로, 컴파일러가 확인하고 처리 코드를 요구합니다.
▶ Unchecked Exception
예외 처리 코드가 필요하지 않으며, 프로그래머의 실수 또는 논리 오류에 의해 발생하는 예외입니다.
RuntimeException 클래스를 상속받습니다.

이러한 차이로 인해, 프로그래머는 예외를 처리하여 프로그램의 안정성을 높일 수 있습니다.
하지만 Error는 일반적으로 처리할 수 없는 치명적인 상황을 나타내므로, 대부분의 경우 프로그램이 이러한 오류에 대한 처리를 시도하면 안 됩니다.

■ String
새로운 값을 할당할 때마다 새로운 동적 변수(인스턴스)가 생성됩니다. String에 저장된 문자열은, 내부적으로 private final char[] 형태이므로 변경이 불가능합니다. String에 대한 연결 연산으로 '+'를 사용하면, 두 String을 연결한 새로운 객체가 생성됩니다. 가비지 컬렉터가 호출되기 전까지 생성된 String 객체들은 Heap에 계속해서 머물기 때문에 메모리 관리 측면에서 치명적입니다.

■ StringBuilder
String과 다르게 가변성을 가지는 클래스입니다. append(), delete()등의 method를 사용해 동일 객체 내에서 문자열을 변경하는 것이 가능합니다. 동기화를 지원하지 않기 때문에, 멀티 쓰레드 환경에서 사용하는 것은 적합하지 않지만, 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer보다 뛰어납니다.

■ StringBuffer
String과 다르게 가변성을 가지는 클래스입니다. append(), delete()등의 method를 사용해 동일 객체 내에서 문자열을 변경하는 것이 가능합니다. 동기화를 지원하기 때문에 멀티 쓰레드 환경에서 thread-safe합니다.

""으로 문자열을 초기화 하게 되면, Java Heap 메모리의 String Pool에 저장이 됩니다.

String msg1 = "hello";
String msg2 = "hello";

위와 같은 코드를 작성하면, "hello"는 Java Heap 메모리의 String Pool에 저장되고, msg1과 msg2는 같은 String pool 내의 "hello"를 가리키게 됩니다.

❗️반면에, new String("")은 조금 다릅니다.

String msg3 = new String("hello");
String msg4 = new String("hello");

new 키워드를 사용해 새로이 생성했기 때문에, String Pool이 아닌, 그냥 Heap 영역에 각각 생성됩니다. 따라서 msg3와 msg4는 서로 다른 주소를 가리키고 있는 상태입니다. msg3.intern() 메소드를 활용하면 string pool에 등록할 수 있습니다.

굳이 객체로 만들어 GC 대상이 되는 것보다, String Pool로 만들어 활용하는 것이 메모리 효율 측면에서 더욱 유리할 것으로 보입니다.

컴파일 시간이 아닌, 런타임 시간에 메모리에 올라간 클래스나 메소드의 정의를 동적으로 찾아서 조작할 수 있는 기술을 의미합니다.

생성자 찾기getDeclaredConstructor(), 메소드 찾기getDeclaredMethods(), 필드 찾고 필드 변경(getDeclaredFields, set) 등 런타임 시간에 동적으로 해당 명령을 수행할 수 있도록 도와줍니다.

□ 런타임 시점에 사용할 instance를 선택하고 동작시킬 수 있어 유연합니다.
□ 컴파일 시점이 아니라 런타임 시점에서 오류를 잡기 떄문에 컴파일 타임에 오류를 확인할 수 없습니다.
□ 접근 제어자로 캡슐화된 필드나 메서드에 접근 가능하므로 기존 동작방식을 무시하고 깨뜨릴 수 있어 위험요소가 있습니다.
□ 런타임 시점에 다른 클래스를 동적으로 로딩하여 접근할 때 사용
□ 클래스와 멤버 필드, 메서드 등에 관한 정보를 얻어야 할 때 사용

Annotation 개념과 함께 나오는 경우가 많습니다.
Spring 컨테이너에서 객체가 호출되어 객체 인스턴스를 생성할 경우 필요하게 됩니다.

Collections Type의 데이터를 Stream 메소드로 내부 반복을 통해 정렬 혹은 필터링을 지원해주는 API 입니다.

□ parallel 메서드 제공을 통해 병렬처리 가능
- 각 스레드가 개별 큐를 가지고 있으며, 놀고 있는 스레드가 있으면 일하는 스레드의 작업을 가져와 수행
□ Immutable
- 원본데이터로부터 데이터를 읽기만 할 뿐, 원본데이터 자체를 변경하지 않습니다.
□ 작업을 내부 반복으로 처리하므로 불필요한 코딩을 줄일 수 있습니다.
□ 최종 연산 후 stream이 닫히므로 일회용입니다.

연산의 구조는 Stream 생성 → 중간연산 → 최종연산 순서로 이어집니다.

중간연산은 데이터를 가공하는 과정으로 필터링, 변환, 제한, 정렬, 연산 중간 결과 확인 등이 있습니다.

최종 연산은 Stream 안의 데이터를 모아서 반환하는 역할로써, 출력, 소모, 검색, 검사, 통계, 연산, 수집 등을 지원합니다.

중간 연산 작업은 바로 실행되는 것이 아니라, 종결처리의 실행이 필요할 때 비로소 중간 처리가 실행됩니다. (Lazy Evaluation)

Image 0Image 1

Task(업무)를 Fork(분할)을 통해 작은 업무로 나눠 배분해서 일을 한 후에 다시 Join(취합)하는 형태의 쓰레드 풀입니다.

https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbxBDO%2FbtrP3nqiOFC%2FNon1qKTBtrxeM8mkUUIpi0%2Fimg.png

Fork-Join Pool도 내부에 inbound queue가 있는데, 이 inbound queue는 각 업무(Task)가 저장되고, 각 쓰레드가 가져가 업무를 하게 됩니다.

왼쪽에서 업무를 제출하면, 하나의 inbound queue에 누적되고, A와 B 쓰레드는 해당 업무를 가져다가 일을 시작합니다.

A와 B는 각자 업무를 관리하는 queue가 있으며, 자신의 큐에 업무가 없으면 상대방의 큐에서 업무를 가져와서(steal) 처리합니다.

최대한 놀고 있는 Thread를 줄이는 방법으로 설계되었습니다.

Optional은 Java 8부터 도입된 클래스로, 값이 존재할 수도 있고 아닐 수도 있는 컨테이너 객체를 나타냅니다.
Optional을 사용하면 명시적으로 값이 존재하지 않을 수 있는 상황을 다룰 수 있으며, NullPointerException을 방지하고 코드의 안정성을 향상시킬 수 있습니다

■ 스레드 생성 방법
- 방법 1: Thread 클래스를 상속받아서 run을 오버라이드해서 정의한다.
- 방법 2: Runnable 인터페이스를 구현하여 Thread 생성자에 인자로 넘긴다.
- 방법 3: Callable 인터페이스를 구현하여 FutureTask에 한번 감싸서 Thread의 인자로 넘긴다.
- Runnable은 Exception이 없고 리턴값도 없으나 Callable은 리턴값이 있고 Exception을 발생시킬 수 있다.

■ 스레드 실행 방법
- 보통 start() 메서드를 사용해서 호출하는데 start 한다고 해서 바로 실행되는 것은 아니고 실행 대기열에 저장된 후 차례가 오면 실행된다.
- 정확하게 말하자면, start는 새로운 스레드가 작업을 실행하는데 필요한 새로운 호출 스택을 생성해서 그곳에 run 메서드를 올려둔다. 이후 그곳에서 run 메서드를 호출하고 스레드가 별개의 작업을 수행하게 된다.

운영 시에는 ExecutorService와 Executors를 이용해 스레드풀을 생성 하여 병렬처리합니다.

자바에서 동시성 문제를 해결하는데 3가지 방법이 있습니다.

■ synchronized
안전하게 동시성을 보장할 수 있습니다. 하지만 비용이 가장 큽니다.

■ volatile
- 키워드가 붙은 자원은 하나의 thread 만이 write 하고 나머지는 스레드는 read만 한다는 전제하에만 동시성을 보장합니다.
- volatile 키워드를 붙인 자원은 read, write 작업이 CPU Cache Memory가 아닌 Main Memory에서 이뤄집니다.
- 즉, 자원을 저장하는 메모리는 하나가 되기 때문에 같은 공유자원에 대해 각각 메모리별로 다른 값을 가지는 경우가 없습니다.
- 하지만 여러 스레드에서 Main Memory에 있는 공유자원에 동시에 접근할 수 있으므로 여러 스레드에서 수정하게 되면, 계산값이 덮어씌워지게 되므로 동시 접근 문제를 해결할 수 없습니다.
- 정리하면, 가시성 문제는 해결할 수 있지만, 동시성은 해결할 수 없습니다.

■ Atomic 클래스
- CAS(compare-and-swap)를 이용하여 동시성을 하므로 여러 쓰레드에서 데이터를 write해도 문제가 없습니다. synchronized 보다 적은 비용으로 동시성을 보장할 수 있습니다.
- CAS 알고리즘이란 현재 스레드가 존재하는 CPU의 CacheMemory와 MainMemory에 저장된 값을 비교하여, 일치하는 경우 새로운 값으로 교체하고, 일치하지 않을 경우 기존 교체가 실패되고, 이에 대해 계속 재시도하는 방식입니다.
- CPU가 MainMemory의 자원을 CPU Cache Memory로 가져와 연산을 수행하는 동안 다른 스레드에서 연산이 수행되어 MainMemory의 자원 값이 바뀌었을 경우 기존 연산을 실패 처리하고, 새로 바뀐 MainMemory 값으로 재수행하는 방식입니다.

DI는 의존 관계 주입으로, 의존 객체를 직접 생성하는 것이 아니라, 수정자, 생성자, 필드를 통해 의존 관계를 주입하는 것을 의미합니다.

■ 수정자 주입
- 대부분 의존 관계 주입은 한번 일어나면 종료시점까지 변경할 일이 거의 없다.
- setter를 통해 주입하게 되면 변경될 위험이 존재
- setter을 public으로 열어야함

■ 생성자 주입
- 생성자 주입을 권장
- 생성자 호출 시점에 딱 1번만 호출되는 것을 보장
- final 키워드를 통해 불변하게 설계 가능
- 의존성 주입이 누락되는 것을 방지할 수 있음 (IDE에서 컴파일 오류로 알려줌)

■ 필드 주입
주로 클래스의 필드에 @Autowired 어노테이션을 사용하여 의존성을 주입하는 방식입니다.
하지만 필드 주입은 몇 가지 단점이 있어서 권장되지 않는 경우도 있습니다.

1. 테스트 어려움: 필드 주입은 주로 private 필드에 사용되므로, 테스트 클래스에서는 해당 필드에 직접 접근하기 어려워 테스트가 어려울 수 있습니다.

2. 의존성 숨김: 필드 주입을 사용하면 의존성이 외부에서 주입되는지 코드에서 직접 확인할 수 없어, 클래스의 의존성이 어디서 주입되는지 명시적으로 확인하기 어렵습니다.

3. 유연성 감소: 필드 주입은 의존성을 변경하기 어렵게 만들 수 있습니다. 생성자나 세터 주입을 사용하면 더 많은 유연성을 가질 수 있습니다.

Bean: Spring은 IoC 컨테이너에서 관리되는 객체를 Bean이라고 합니다. Bean은 Spring이 생성, 조립 및 관리하는 객체로서, Spring IoC 컨테이너에 의해 인스턴스화, 구성 및 관리됩니다.

ApplicationContext: ApplicationContext는 Spring IoC 컨테이너의 핵심 인터페이스입니다. ApplicationContext는 Bean의 생성, 설정, 관리 및 조회를 담당합니다. 주로 XML 파일이나 Java 설정 클래스를 사용하여 ApplicationContext를 구성합니다.

의존성 주입(Dependency Injection): Spring은 의존성 주입을 통해 Bean이 의존하는 다른 Bean을 자동으로 주입할 수 있습니다. 이는 XML, 어노테이션 또는 Java 설정 클래스를 사용하여 수행됩니다. 주입 방법에는 생성자 주입, setter 주입, 필드 주입 등이 있습니다.

Bean 설정: Spring은 XML 파일, Java 설정 클래스 또는 어노테이션을 사용하여 Bean을 설정합니다. 이를 통해 Spring IoC 컨테이너는 애플리케이션의 Bean을 관리하고 필요한 의존성을 주입할 수 있습니다.

Bean 스코프: Spring은 Bean의 라이프사이클을 관리하기 위해 다양한 스코프를 제공합니다. Singleton, Prototype, Request, Session 등의 스코프를 지원하여 Bean의 생성과 소멸을 제어할 수 있습니다.

■ 자바빈(JavaBean)
- 자바빈은 일반적으로 자바 객체를 가리키며, 특정 규약을 따라야 합니다.
- 자바빈은 주로 데이터를 캡슐화하고, getter 및 setter 메서드를 통해 속성에 접근하는 일반적인 형태의 클래스를 말합니다.
- 자바빈은 시리얼라이즈(Serialize)할 수 있고, GUI 디자이너 도구 등에서 손쉽게 사용할 수 있는 특징을 가지고 있습니다.

■ 스프링 빈(Spring Bean)
- 스프링에서의 빈은 스프링 컨테이너에 의해 관리되는 객체를 의미합니다.
- 스프링 빈은 스프링 IoC(Inversion of Control) 컨테이너에 등록되어 라이프사이클을 관리하고, 의존성 주입(Dependency Injection)을 통해 다른 빈과의 관계를 설정합니다.
- 스프링 빈은 일반적으로 POJO(Plain Old Java Object)를 기반으로 하며, 스프링 컨테이너에 의해 생성, 관리, 소멸됩니다.

따라서, 맥락에 따라 "빈"이라는 용어는 자바빈과 스프링 빈으로 나뉠 수 있습니다.
자바빈은 단순히 자바 객체를 가리키는 일반적인 용어이며, 스프링 빈은 스프링 프레임워크에서 관리되는 객체를 의미합니다.

📌 스프링 Bean의 생명 주기
스프링 IoC 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 메소드 호출 → 사용 → 소멸 전 콜백 메소드 호출 → 스프링 종료

프로그램에게 추가적인 정보를 제공하는 메타데이터입니다.

annotation을 정의하고 원하는 위치에 배치한 후 코드가 실행되는 도중 자바 리플렉션을 이용해 추가 정보를 획득해 기능을 실시하는 방식으로 동작됩니다.

Spring 컨테이너에서 객체가 호출되면, 객체의 인스턴스를 생성하게 되는데 이때 자바 리플렉션이 필요하게 됩니다.

■ @ComponentScan
- @Component, @Service, @Repository, @Controller, @Configuration이 붙은 클래스 Bean들을 찾아서 Context에 bean을 등록해주는 애노테이션
- 전부 다 @Component를 사용하지 않고 @Repository 등으로 분리해서 사용하는 이유는, 예를 들어 @Repository는 DAO에서 발생할 수 있는 unchecked exception들을 스프링의 DataAccessException으로 처리할 수 있기 때문이다.
- 또한 가독성에서도 해당 애노테이션을 갖는 클래스가 무엇을 하는지 단 번에 알 수 있다.

■ @EnableAutoConfiguration
- autoConfiguration도 Configuration중 하나에 해당한다.
- spring.factories 내부에 여러 Configuration들이 있고 조건에 따라 Bean이 등록되게 되는데 메인 클래스 @SpringBootApplication을 실행하면 @EnableAutoConfiguration에 의해 spring.factories 안에 있는 수많은 자동 설정들이 조건에 따라 적용되어 수 많은 Bean들이 생성된다.
- 간단하게 정리하면, Application Context를 만들 때 자동으로 빈설정이 되도록 하는 기능이다.

■ @Component
개발자가 직접 작성한 class를 Bean으로 등록하기 위한 애노테이션

■ @Bean
개발자가 직접 제어가 불가능한 외부 라이브러리등을 bean으로 만들려할 때 사용되는 애노테이션

■ @Configuration
@Configuration을 클래스에 적용하고 @Bean을 해당 class의 메서드에 적용하면 @autowired로 Bean을 부를 수 있다.

■ @Autowired
- 스프링이 Type에 따라 알아서 Bean을 주입해준다.
- Type을 먼저 확인한 후 못 찾으면 Name에 따라 주입한다.
- 강제로 주입하고자 하는 경우 @Qulifier을 같이 명시

■ @Qualifier
- 같은 타입의 빈이 두 개 이상 존재하는 경우 스프링이 어떤 빈을 주입해야할 지 알 수 없어서 스프링 컨테이너를 초기화하는 과정에서 예외가 발생한다.
- @Qualifier는 @Autowired와 함께 사용하여 정확히 어떤 bean을 사용할지 지정하여 특정 의존 객체를 주입할 수 있다.

■ @Resource
- @Autowired와 마찬가지로 Bean 객체를 주입해주는데 차이점은 Autowired는 타입으로, Resource는 이름으로 연결해준다.
- 애노테이션 사용으로 인해 특정 Framework에 종속적인 애플리케이션을 구성하지 않기 위해서 @Resource 사용을 권장한다.

■ @Controller
- API와 view를 동시에 사용하는 경우에 사용
- 보통 view 화면 return을 목적으로 사용한다.

■ @RestController
view가 필요 없이 API만 지원하는 서비스에서 사용

■ @SpringBootApplication
@Configuration, @EnableAutoConfiguration, @ComponentScan 3가지를 하나로 합친 애노테이션

Image 0



웹 서버(Web Server)와 웹 애플리케이션 서버(Web Application Server, WAS)는 웹 환경에서 다른 역할을 수행하는 서버 유형입니다.

■ 웹 서버(Web Server):
- 웹 서버는 클라이언트로부터 HTTP 요청을 받아 정적인 콘텐츠(HTML, 이미지, CSS 등)를 제공하는 서버입니다.
- 주로 정적인 콘텐츠를 제공하며, 동적인 처리가 필요한 경우에는 웹 애플리케이션 서버에 요청을 전달하고 응답을 클라이언트에게 반환합니다.
- 예시로는 Apache, Nginx 등이 있습니다.
- 기본적으로 HTTP 프로토콜을 다루며, HTTPS를 지원할 수도 있습니다.

■ 웹 애플리케이션 서버(WAS):
- 웹 애플리케이션 서버는 동적인 웹 페이지를 생성하고 비즈니스 로직을 수행하는 서버입니다.
- 정적인 콘텐츠 뿐만 아니라, 서버 측에서 동적으로 생성되는 페이지, 데이터베이스 연동, 사용자 인증, 트랜잭션 처리 등을 담당합니다.
- 주로 Java EE, Java Servlet, JSP, 등과 같은 웹 애플리케이션 프레임워크와 함께 사용됩니다.

▶ 일반적으로 웹 서버와 웹 애플리케이션 서버는 협력하여 동작합니다.

▶ 클라이언트의 요청이 들어오면 웹 서버가 정적인 콘텐츠를 처리하고, 동적인 처리가 필요한 경우에는 해당 요청을 웹 애플리케이션 서버로 전달하여 처리하고 결과를 클라이언트에게 반환합니다.

▶ 이런 협력 구조를 통해 웹 애플리케이션은 동적인 기능을 제공하면서도 정적인 콘텐츠를 효율적으로 처리할 수 있습니다.

Image 0Image 1

https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVImM%2FbtrPVDhM8Iv%2FKpQclT8wmI2gZrsfmL1Vk0%2Fimg.png

클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술입니다.

웹 요청과 응답의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해줍니다.

서블릿이 존재하기 전에는 HTTP 요청 메시지를 파싱하는 작업 등 여러 부가적인 작업을 개발자가 해야 하나, 서블릿의 등장이후로는 이러한 부가 작업을 서블릿이 대신 하고, 개발자는 실질적인 메인 로직만 집중할 수 있게 되었습니다.



📌 서블릿 컨테이너는 서블릿의 생명주기를 관리합니다.
1. 사용자(Client)가 URL을 입력하면 HTTP Request가 Servlet Container로 전송됩니다.

2. 요청 받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성합니다.

3. web.xml을 기반으로 사용자가 요청한 URL이 어느 서블릿에 대한 요청인지 찾습니다.

4. 해당 서블릿에서 service메소드를 호출한 후 GET, POST여부에 따라 doGet() 또는 doPost()를 호출합니다.

5. doGet() or doPost() 메소드는 동적 페이지를 생성한 후 HttpServletResponse 객체에 응답을 보냅니다.

6. 응답이 끝나면 HttpServletRequest, HttpServletResponse 두 객체를 소멸시킵니다.

Image 0

Spring에서 제공하는 웹 모듈로, Model / View / Controller 세 가지 구성요소를 사용해 사용자의 다양한 HTTP Request를 처리하고 단순한 텍스트 형식의 응답부터 REST 형식의 응답은 물론, View를 표시하는 HTML을 Retrun하는 응답까지 다양한 응답을 할 수 있도록 도와주는 Framework입니다.



■ 핸들러 조회
핸들러 매핑을 통해 요청 URL에 매핑된 핸들러(컨트롤러)를 조회합니다.

■ 핸들러 어댑터 조회
핸들러를 실행할 수 있는 핸들러 어댑터를 조회합니다.

■ 핸들러 어댑터 실행
조회한 핸들러(컨트롤러)를 인자로 핸들러 어댑터에 넘겨서 핸들러를 실행시킵니다.

■ ModelAndView 반환
핸들러(컨트롤러)가 로직을 수행하고 반환하는 정보로 ModelAndView로 변환해서 반환합니다.

■ viewResolver 호출
- 적절한 viewResolver를 찾고 해당 viewResolver를 호출합니다..
- RestController라면 이 과정과 이후 과정 없이 컨버터를 이용해 바로 결과값을 리턴합니다.

■ View 반환
viewResolver는 뷰의 논리 이름을 물리 이름으로 바꾸고, 랜더링 역할을 담당하는 뷰 객체를 반환합니다.

■ 뷰 랜더링
뷰를 통해서 뷰를 랜더링합니다.

■ MVC 패턴
Model, View, Controller로 분리하는 아키텍처

□ 장점
- 과거에는 Controller에 다 담아두고 처리
- 기능 별로 코드를 분리하여, 가독성을 높이고 재사용성 증가

□ 단점
- view와 model 사이에 의존성이 높아서 애플리케이션이 커질수록 복잡해지고 유지보수가 어렵습니다.
- 대규모의 프로그램에서 Controller에 다수의 Model과 View가 복잡하게 연결되어 코드 분석과 테스트가 어려워 질 수 있습니다.
- 이런 의존성 문제를 해결하기 위해 MVVM, MVP 구조가 등장했습니다.

컨트롤러는 컴포넌트 스캔을 통해 스프링 빈 컨테이너에 생성됩니다.

이렇게 생성된 스프링 빈 객체는 싱글톤으로 생성되기 때문에 여러 쓰레드의 요청이 들어와도 하나의 컨트롤러 객체를 공유하면서 처리합니다.
즉, 여러 쓰레드가 메서드에 대해 공유 자원으로써 접근해 사용합니다.

주의할 점은, 싱글톤 패턴으로 구현되어있다는 점은, Thread-Safe하지 않다는 의미이므로, 상태를 공유하거나 저장하는 코드가 없도록 Stateless하게 정의해야 합니다.

싱글톤 패턴(Singleton Pattern)은 디자인 패턴 중 하나로, 특정 클래스의 인스턴스가 오직 하나만 생성되고, 그 인스턴스에 대한 전역적인 접근 지점을 제공하는 패턴입니다.

이를 통해 애플리케이션 전역에서 하나의 인스턴스만을 사용하여 공유하고, 여러 곳에서 동시에 이 인스턴스에 접근할 수 있습니다.

📌 싱글톤 패턴을 구현하는 방법은 여러 가지가 있지만, 주로 다음의 특징을 가지고 있습니다:

1. Private Constructor (비공개 생성자): 해당 클래스의 생성자를 외부에서 호출할 수 없도록 만듭니다.

2. Private Static Instance Variable (비공개 정적 인스턴스 변수): 클래스 내부에 유일한 인스턴스를 담을 정적(private static) 변수를 선언합니다.

3. Public Static Method (공개 정적 메소드): 유일한 인스턴스를 반환하는 메소드를 정의하고, 이 메소드를 통해 외부에서 해당 인스턴스에 접근할 수 있도록 합니다.

아래는 싱글톤 패턴을 구현한 간단한 예제입니다.

public class Singleton {
  // 1. 비공개 생성자
  private Singleton() {}

  // 2. 비공개 정적 인스턴스 변수
  private static Singleton instance;

  // 3. 공개 정적 메소드를 통한 인스턴스 반환
  public static Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}


이렇게 구현된 싱글톤 패턴은 다수의 스레드에서 안전하지 않습니다.
여러 스레드가 동시에 `getInstance()` 메소드를 호출하면 인스턴스가 여러 번 생성될 수 있습니다.
이를 해결하기 위해 동기화(synchronization)를 적용하거나, 더 효율적으로 인스턴스를 생성하는 방법도 고려될 수 있습니다. 또한, Java에서는 `enum`을 사용하여 싱글톤을 구현하는 방법도 있습니다.
싱글톤 패턴은 다수의 스레드에서 안전하지 않습니다.

왜냐하면, 여러 스레드가 동시에 getInstance() 메소드를 호출하면 인스턴스가 여러 번 생성될 수 있습니다.

이를 해결하기 위해 동기화를 적용하거나, 더 효율적으로 인스턴스를 생성하는 방법도 고려될 수 있습니다. 하지만 동기화를 적용하면 비용이 크게 증가합니다.

❗️따라서, Java에서는 enum을 사용하여 싱글톤을 구현하는 방법도 있습니다.

enum`은 JVM에 의해 스레드 안전하게 초기화되기 때문에 명시적인 동기화가 필요하지 않습니다.

📌 `enum`을 사용한 싱글톤 패턴의 예제
public enum SingletonEnum {
  INSTANCE;

  int value;

  public int getValue() {
    return value;
  }

  public void setValue(int value) {
    this.value = value;
  }
}

public class EnumDemo {
  public static void main(String[] args) {
    SingletonEnum singleton = SingletonEnum.INSTANCE;

    System.out.println(singleton.getValue());
    singleton.setValue(2);
    System.out.println(singleton.getValue());
  }
}


▶ 이렇게 `enum`을 사용하면 JVM이 알아서 스레드 안전한 방식으로 싱글톤 인스턴스를 생성합니다.
▶ `INSTANCE`는 `SingletonEnum` 타입의 유일한 인스턴스이며, 다른 코드에서 `SingletonEnum.INSTANCE`로 접근할 수 있습니다.
▶ 이 방법은 직렬화와 리플렉션에 대해서도 자동으로 방어되어 있어서 추가적인 코드 작성 없이도 이러한 공격에 대응할 수 있습니다.
▶ 이러한 이점들 때문에 Java에서는 `enum`을 사용한 싱글톤 패턴을 권장합니다.

Plain Old Java Object의 약어로, 평범한 구식 자바 객체를 의미합니다.

단순한 클래스를 의미하는 것으로, 타 프레임워크에 종속되지 않아 코드가 간결하고 테스트 자동화에 유리합니다.

Spring에서는 도메인과 비즈니스 로직을 수행하는 대상이 POJO 대상이 될 수 있습니다.

■ DAO (Data Access Object)
- DB 데이터를 조회하거나 조작하는 기능을 전담하는 객체
- DB 접근 로직과 비즈니스 로직을 분리하기 위해서 사용

■ DTO (Data Transfer Object)
- 계층간의 데이터 교환을 위한 객체
- 로직을 갖지 않는 순수 데이터 객체로 getter, setter만 포함

■ VO (Value Object)
- DTO와 동일한 개념
- Read Only로 수정 불가
- getter, setter 이외의 추가 로직 포함 가능

■ BO (Business Object)
- 비즈니스 로직을 포함하는 오브젝트로, 여러 DAO를 사용해 데이터를 처리

Image 0



📌 Spring은 레이어드 아키텍처로 이루어져 있습니다.
- 하나의 레이어는 자신의 고유 역할을 수행하고, 인접한 다른 레이어에 무언가를 요청하거나 응답합니다.
- 그 밖의 다른 레이어는 신경 쓸 필요가 없기 때문에 각 레이어는 자신의 역할에 충실할 수 있습니다.
- 따라서 시스템 전체를 수정하지 않고 특정한 레이어의 기능을 개선하거나 교체할 수 있기 때문에 재사용성이 좋고 유지 보수에도 유리합니다.
- 또한, 레이어별로 테스트 구현이 편해지고 코드 가독성도 높아집니다.

■ Presentation Layer
view를 담당하는 부분으로, 클라이언트와 직접적으로 맞닿는 부분

■ Application Layer
비즈니스 핵심 로직을 처리하는 부분
Service 객체라는 것은 하나의 트랜잭션으로 구성되어 작동

■ Persistence Layer
데이터 관련 처리를 담당하는 부분

어플리케이션이 데이터베이스를 사용하기 위해서는 Connection이 필요한데, 생성 및 소멸 비용이 크기 때문에 커넥션 풀을 미리 생성하고 애플리케이션이 시작하는 시점에 커넥션을 미리 다 생성하고 이것을 재활용하며 사용하게 됩니다.

▶ SpringBoot 2.0 이전에는 tomcat-jdbc
▶ Springboot 2.0 이후 부터는 hikariCP를 기본 CP로 사용합니다

데이터베이스와의 연결을 관리하기 위한 인터페이스입니다.
DataSource는 데이터베이스 연결 풀링(pooling)을 지원하며, 데이터베이스와의 연결을 효율적으로 관리하고 재사용할 수 있도록 도와줍니다.

실질적인 로직을 DataSource에 의존하도록 하고, 구현 기술이 바뀌면 DataSource의 구현체만 바꾸면 되므로, 재사용성과 확장성을 높일 수 있습니다.

다양한 데이터 접근 기술이 등장하면서, 코드레벨에서는 다르지만 논리적으로는 같은 기능(벤더는 다르지만 트랜잭션 의미는 같은)을 수행하기 때문에 트랜잭션을 추상화하여 사용 가능하도록 해야 했기 때문입니다.

다양한 접근 기술로는 JDBC, JPA, Hibernate 등이 있습니다.

보통 서비스 단에서 트랜잭션을 시작하고 끝내게 됩니다.

📌 하나의 트랜잭션 내에서는 같은 커넥션을 사용 해야 하는데 과정이 다음과 같습니다.
1. 서비스단에서 트랜잭션이 시작하면 트랜잭션 매니저가 커넥션을 생성하고(풀을 사용하면 풀에서 가져오고) autoCommit을 false로 세팅한 뒤 트랜잭션 동기화 매니저의 스레드 로컬에 커넥션을 보관

2. 이후 리포지토리 계층에서는 트랜잭션 동기화 매니저의 스레드 로컬에서 해당 커넥션을 가져와서 사용

3. 서비스 단에서 트랜잭션을 종료할 때는 트랜잭션 동기화 매니저에서 해당 커넥션을 가져와 커밋 또는 롤백을 수행하고 리소스를 정리하고 커넥션을 커넥션 풀에 반환

▶ 하나의 트랜잭션에서 같은 커넥션을 사용하도록 도움을 주는 기능을 제공합니다.

■ 선언적 트랜잭션
- @Transactional 어노테이션을 사용함으로써 트랜잭션을 적용시킬 수 있습니다. 추가적인 코드 작성이 없어 대부분 사용하는 방식입니다.
- @Transactional 어노테이션은 AOP로 구성되어 있습니다. AOP는 프록시 패턴으로 동작하기 때문에, 오버라이딩 개념으로 동작합니다. 메서드에 @Transactional을 붙이면, 해당 클래스가 빈으로 등록될 때, @Transactional이 붙은 메소드만 트랜잭션 처리되는 메소드로 오버라이딩 한 프록시 객체가 빈으로 등록되게 됩니다.

■ 프로그래밍 방식 트랜잭션
트랜잭션 매니저나 트랜잭션 템플릿을 사용해 프로그래밍 코드로 작성해 적용하는 방식입니다.

@Service
public class MyService {

  private final PlatformTransactionManager transactionManager;

  @Autowired
  public MyService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }

  public void performTransactionalOperation() {
    DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
    TransactionStatus status = transactionManager.getTransaction(transactionDefinition);

    try {
      // 트랜잭션 처리 로직

      transactionManager.commit(status);
    } catch (Exception e) {
      transactionManager.rollback(status);
      throw e;
    }
  }
}

■ JDBC (Java Database Connectivity)
설명: JDBC는 Java에서 데이터베이스에 접근하고 SQL 쿼리를 실행하기 위한 API(인터페이스)입니다.
동작 원리: JDBC는 데이터베이스 연결, 쿼리 실행, 결과 처리 등을 담당합니다. 개발자는 JDBC API를 사용하여 데이터베이스와 연동하는 코드를 작성해야 합니다.

■ Spring JDBC
설명: Spring JDBC는 JDBC를 간편하게 사용할 수 있도록 Spring 프레임워크에서 제공하는 모듈입니다.
동작 원리: Spring JDBC는 JDBC 기능을 감싸고 있는데, JdbcTemplate이라는 클래스를 사용하여 JDBC 코드를 더 단순하게 작성할 수 있도록 지원합니다.

■ MyBatis
설명: MyBatis는 SQL 매핑 프레임워크로, SQL 쿼리와 Java 객체 간의 매핑을 XML 또는 어노테이션을 통해 정의합니다.
동작 원리: MyBatis는 개발자가 SQL을 직접 작성하고, 이를 매핑 파일에 정의하여 데이터베이스와의 상호작용을 간소화합니다.

■ ORM (Object-Relational Mapping)
설명: ORM은 객체와 관계형 데이터베이스 간의 매핑을 자동화하는 기술을 의미합니다. 객체 지향 프로그래밍 언어에서의 객체와 데이터베이스의 레코드 간의 변환을 관리합니다.

■ JPA (Java Persistence API)
설명: JPA는 자바에서 객체와 관계형 데이터베이스 간의 매핑을 위한 API로, ORM을 구현하기 위한 표준 인터페이스를 제공합니다.

■ Hibernate
설명: Hibernate는 JPA의 구현체 중 하나로, 객체와 데이터베이스 간의 매핑을 위한 강력하고 유연한 ORM 프레임워크입니다.

■ Spring Data JPA
설명: Spring Data JPA는 Spring에서 JPA를 더 편리하게 사용할 수 있도록 지원하는 모듈입니다.
동작 원리: Spring Data JPA는 JpaRepository 인터페이스를 제공하여, 개발자가 기본적인 CRUD 작업을 쉽게 수행할 수 있도록 도와줍니다. 또한, 쿼리 메소드를 통해 개발자가 쉽게 쿼리를 정의할 수 있도록 지원합니다.

https://github.com/conf312/concept-description/blob/master/concept/JPA/JPA.png

영속성 컨텍스트(Persistence Context)는 데이터베이스와 애플리케이션의 상호작용에서 중요한 개념 중 하나로, 주로 ORM(Object-Relational Mapping) 프레임워크에서 사용됩니다.

영속성 컨텍스트는 데이터베이스로부터 가져온 엔티티(객체)들을 관리하고, 데이터베이스에 엔티티 변경사항을 반영하는 역할을 수행합니다.

📌 주요 특징과 동작
■ 엔티티의 관리
영속성 컨텍스트는 애플리케이션에서 사용되는 엔티티들을 관리합니다. 이를 통해 엔티티의 생명주기(Lifecycle)를 추적하고 엔티티의 상태를 파악할 수 있습니다.

■ 캐싱(Caching)
영속성 컨텍스트는 데이터베이스에서 읽어온 엔티티들을 캐시에 저장하여, 동일한 엔티티에 대한 반복적인 조회를 방지하고 성능을 향상시킵니다.

■ 지연 로딩(Lazy Loading)
영속성 컨텍스트는 지연 로딩을 지원하여, 엔티티의 연관 관계를 필요할 때 로딩하게끔 할 수 있습니다. 이는 성능 최적화에 도움을 줍니다.

■ 변경 감지(Dirty Checking)
영속성 컨텍스트는 트랜잭션 내에서 엔티티의 상태 변경사항을 감지하고, 이를 데이터베이스에 자동으로 반영합니다.

■ 트랜잭션 범위
영속성 컨텍스트는 일반적으로 트랜잭션 범위 내에서 동작합니다. 트랜잭션이 커밋될 때, 영속성 컨텍스트는 변경된 엔티티들을 데이터베이스에 반영합니다.

■ 클린업(Cleanup)
트랜잭션이 종료되면 영속성 컨텍스트는 관리하고 있던 영속 상태의 엔티티들을 정리하고, 캐시를 비우는 등의 클린업 작업을 수행합니다.

▶ ORM 프레임워크들 중에서는 Hibernate, EclipseLink 등이 영속성 컨텍스트를 지원하며, JPA(Java Persistence API) 역시 영속성 컨텍스트를 정의하고 사용하는 표준 인터페이스를 제공합니다.

JPA에서 발생하는 N+1 문제는 지연 로딩(Lazy Loading)을 사용하는 경우에 나타날 수 있는 성능 문제입니다.

N+1 문제는 부모 엔티티를 조회할 때마다 추가적인 N개의 쿼리가 발생하는 상황을 말합니다.

📌 예를 들어, 다음과 같은 관계가 있다고 가정해봅시다.

@Entity
public class Parent {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  // 일대다 관계
  @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
  private List children;
}

@Entity
public class Child {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  // 다대일 관계
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "parent_id")
  private Parent parent;
}


여기서 `Parent` 엔티티를 조회할 때, 각각의 자식 엔티티를 가져오는 데 필요한 추가적인 쿼리가 N번 발생하게 됩니다.
이는 데이터베이스의 부하를 유발하고 성능을 저하시킬 수 있습니다.

📌 해결 방법
■ 페치 전략 수정
`FetchType.LAZY`로 설정된 연관 관계의 경우, `FetchType.EAGER`로 수정하여 즉시 로딩을 사용하는 방법이 있습니다.
하지만 즉시 로딩은 항상 좋은 선택이 아니며, 성능 이슈를 유발할 수 있습니다.

@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
private List children;


■ 1. 페치 조인 사용
JPQL(Java Persistence Query Language)이나 Criteria API를 사용하여 페치 조인을 수행하는 방법입니다.
페치 조인을 사용하면 한 번의 쿼리로 필요한 모든 데이터를 한번에 가져올 수 있습니다.

SELECT p FROM Parent p JOIN FETCH p.children


■ 2. @BatchSize 어노테이션 사용
`@BatchSize` 어노테이션을 사용하여 한 번에 가져올 엔티티의 개수를 지정할 수 있습니다.
이는 특정 범위의 엔티티들을 한 번에 로딩하여 N+1 문제를 완화할 수 있습니다.

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
@BatchSize(size = 10)
private List children;


■ 3. @Fetch(FetchMode.SUBSELECT) 사용
`@Fetch(FetchMode.SUBSELECT)` 어노테이션을 사용하여 부모 엔티티와 자식 엔티티들을 서브쿼리를 사용하여 한 번에 로딩할 수 있습니다.

@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
@Fetch(FetchMode.SUBSELECT)
private List children;


▶ 이러한 방법들을 적절히 선택하여 사용하면 N+1 문제를 효과적으로 해결하고 성능을 최적화할 수 있습니다.
▶ 선택하는 방법은 상황과 요구사항에 따라 다를 수 있습니다.
객체 간의 상속 관계를 관계형 데이터베이스에 매핑하기 위해서는 상속 전략을 선택하고, 선택한 전략에 따라 매핑 설정을 해주어야 합니다.

📌 주로 사용되는 상속 전략은 세 가지가 있습니다.
단일 테이블 전략, 각 테이블 전략, 구현 클래스마다 테이블 전략입니다.

■ 단일 테이블 전략 (Single Table Strategy)
□ 설명: 모든 클래스의 속성을 하나의 테이블에 모두 저장합니다. 서브클래스의 속성이 사용되지 않는 경우에는 NULL로 저장됩니다.
□ 장점: 단일 테이블을 사용하므로 간단하며 성능이 좋을 수 있습니다.
□ 단점: 테이블이 커질 수 있고, 서브클래스마다 모든 컬럼이 포함되어야 하므로 스키마가 복잡해질 수 있습니다.

■ 각 테이블 전략 (Table Per Class Strategy)
□ 설명: 각 클래스마다 별도의 테이블을 생성합니다. 부모 클래스와 서브클래스의 속성이 각각의 테이블에 저장됩니다.
□ 장점: 테이블이 서브클래스마다 분리되어 있어 스키마가 깔끔하게 유지됩니다.
□ 단점: JOIN을 사용해야 하므로 성능에 영향을 줄 수 있습니다.

■ 구현 클래스마다 테이블 전략 (Table Per Concrete Class Strategy)
□ 설명: 모든 클래스의 속성이 서브클래스마다 별도의 테이블에 저장됩니다. 부모 클래스와 서브클래스의 테이블 간에는 어떤 관계도 없습니다.
□ 장점: 테이블이 각 구현 클래스마다 분리되어 있어 스키마가 깔끔하게 유지됩니다.
□ 단점: 상속 관계를 쿼리하기 위해 UNION을 사용하므로 성능에 영향을 줄 수 있습니다.

각 전략에 따라 JPA(Java Persistence API)에서는 다양한 어노테이션을 사용하여 매핑을 지원합니다.

📌 각 전략에 대한 간단한 예제
■ 단일 테이블 전략
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
public abstract class Vehicle {
  // 공통 속성
}

@Entity
@DiscriminatorValue("CAR")
public class Car extends Vehicle {
  // Car에 특화된 속성
}

@Entity
@DiscriminatorValue("BIKE")
public class Bike extends Vehicle {
  // Bike에 특화된 속성
}


■ 각 테이블 전략
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle {
  // 공통 속성
}

@Entity
public class Car extends Vehicle {
  // Car에 특화된 속성
}

@Entity
public class Bike extends Vehicle {
  // Bike에 특화된 속성
}


■ 구현 클래스마다 테이블 전략
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Vehicle {
  // 공통 속성
}

@Entity
@Table(name = "car")
public class Car extends Vehicle {
  // Car에 특화된 속성
}

@Entity
@Table(name = "bike")
public class Bike extends Vehicle {
  // Bike에 특화된 속성
}


▶ 선택한 전략은 데이터베이스 스키마의 복잡성, 쿼리 성능 등을 고려하여 결정해야 하며, 각 전략마다 장단점이 있습니다.

Spring Batch는 대용량의 데이터를 처리하고 복잡한 비즈니스 로직을 포함한 일괄 처리 작업(batch processing)을 간단하게 만들어주는 프레임워크입니다.

주로 대량의 데이터를 처리하고, 통계 생성, 정산, 데이터 마이그레이션 등의 비즈니스 프로세스를 자동화하는데 사용됩니다.

Spring Batch는 강력한 트랜잭션 관리, 실패한 작업의 복구, 병렬 처리, 통계 및 리포팅 등의 기능을 제공합니다.

📌 주요 구성 요소와 개념
■ Job(작업)
일괄 처리 작업의 최상위 개념입니다. 일련의 Step으로 이루어져 있으며, 하나의 Job은 여러 개의 Step을 포함할 수 있습니다.

■ Step(단계)
Job을 구성하는 각각의 처리 단위입니다. Step은 각각의 Reader, Processor, Writer를 갖고 있으며, 이들이 일련의 데이터 처리를 수행합니다.

□ ItemReader
데이터를 읽어오는 역할을 담당합니다. 파일, 데이터베이스, 메시지 큐 등 다양한 소스에서 데이터를 읽어와 Spring Batch에 전달합니다.

□ ItemProcessor
읽어온 데이터를 가공하거나 필터링하는 역할을 담당합니다. 선택적으로 사용할 수 있습니다.

□ ItemWriter
가공된 데이터를 저장하거나 외부 시스템에 전달하는 역할을 담당합니다. 파일에 쓰기, 데이터베이스에 저장하기, 메시지 큐로 전송하기 등이 가능합니다.

■ JobRepository
Spring Batch의 메타데이터와 실행 상태를 저장하는 데 사용되는 저장소입니다. 기본적으로는 데이터베이스를 사용합니다.

■ JobLauncher
Job을 실행하는 역할을 수행합니다. Job을 수동으로 또는 스케줄러를 통해 실행할 때 사용됩니다.

■ JobParameters
Job을 실행할 때 전달되는 파라미터를 나타냅니다. 실행 시간, 파일 경로 등과 같은 정보를 전달할 수 있습니다.

▶ Spring Batch는 XML 또는 JavaConfig를 사용하여 Batch Job을 설정하고 실행할 수 있습니다.
▶ 대용량 데이터 처리 및 일괄 처리 작업을 간편하게 구현하고 관리할 수 있는 기능들을 제공하여, 엔터프라이즈 수준의 일괄 처리를 지원합니다.

Spring Batch의 Chunk 기반 방식은 대용량 데이터를 처리할 때 사용되는 효율적인 방식 중 하나입니다.

이 방식은 데이터를 작은 덩어리로 나누어(chunk) 처리하고, 각각의 덩어리를 별도의 트랜잭션에서 처리하는 방식입니다.

📌 Chunk 기반 방식의 특징
■ Reader, Processor, Writer
Spring Batch의 일괄 처리 작업은 주로 ItemReader, ItemProcessor, ItemWriter를 사용하여 구성됩니다.

□ ItemReader: 대용량 데이터를 덩어리(chunk) 단위로 읽어옵니다.
□ ItemProcessor: 각각의 덩어리에 대해 가공 및 필터링 작업을 수행합니다.
□ ItemWriter: 가공된 덩어리를 외부 시스템에 저장하거나 처리 결과를 기록합니다.

■ Chunk 설정
Chunk 기반 방식에서는 작업의 크기를 나타내는 "chunk size"를 설정합니다.

이는 한 번의 트랜잭션 내에서 처리되는 데이터의 양을 나타냅니다.

예를 들어, chunk size가 100이면, 100개의 항목을 읽어와서 처리하고, 그 후에 다음 100개의 항목을 처리하는 식입니다.

□ 트랜잭션 관리
Chunk 기반 방식에서는 각각의 덩어리(chunk)가 별도의 트랜잭션 내에서 처리됩니다. 이는 실패한 덩어리에 대해 롤백이 발생할 때, 해당 덩어리만 다시 처리할 수 있도록 합니다.

□ 성능 향상
대용량 데이터를 적은 양의 데이터로 나누어 처리하므로, 메모리 사용량이 줄어들고 성능이 향상될 수 있습니다.

📌 Chunk 기반 방식의 구현 예제
@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

  @Autowired
  private JobBuilderFactory jobBuilderFactory;

  @Autowired
  private StepBuilderFactory stepBuilderFactory;

  @Autowired
  private ItemReader itemReader;

  @Autowired
  private ItemProcessor itemProcessor;

  @Autowired
  private ItemWriter itemWriter;

  @Bean
  public Job myJob() {
    return jobBuilderFactory.get("myJob")
        .start(myStep())
        .build();
  }

  @Bean
  public Step myStep() {
    return stepBuilderFactory.get("myStep")
        .chunk(100) // Chunk size 설정
        .reader(itemReader)
        .processor(itemProcessor)
        .writer(itemWriter)
        .build();
  }
}


▶ 위의 예제에서 `chunk(100)`은 Chunk의 크기를 나타내는 부분으로, 한 번의 트랜잭션 내에서 100개의 항목을 처리하는 것을 의미합니다.
▶ 이러한 방식으로 작업을 세분화하여 처리하게 되면, 대용량 데이터를 안정적이고 효율적으로 처리할 수 있습니다.
Spring Batch의 구조는 크게 Job과 Step으로 나뉩니다.

이들은 대용량 데이터를 처리하는 일괄 처리(Batch Processing) 작업을 정의하고 실행하기 위한 핵심 구성 요소입니다.

■ Job
하나의 완전한 일괄 처리 작업을 나타냅니다. 이 작업은 하나 이상의 단계(Step)로 구성되며, 일련의 처리 흐름을 정의합니다.

□ JobBuilderFactory
Job을 생성하기 위한 빌더를 제공하는 팩토리 클래스입니다. get(String name)` 메소드를 사용하여 Job의 이름을 지정하고, 해당 Job에 속하는 Step을 추가할 수 있습니다.

□ JobParameters
Job 실행 시에 전달되는 파라미터를 나타냅니다. 예를 들어, 배치 작업이 실행되는 날짜나 시간과 같은 정보를 포함할 수 있습니다.

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

  @Autowired
  private JobBuilderFactory jobBuilderFactory;

  @Autowired
  private Step myStep;

  @Bean
  public Job myJob() {
    return jobBuilderFactory.get("myJob")
        .start(myStep)
        .build();
  }
}


■ Step
Job을 이루는 각각의 처리 단위를 나타냅니다. 각 Step은 ItemReader, ItemProcessor, ItemWriter를 포함하며, 덩어리(chunk) 단위로 데이터를 처리합니다.

□ StepBuilderFactory
Step을 생성하기 위한 빌더를 제공하는 팩토리 클래스입니다. `get(String name)` 메소드를 사용하여 Step의 이름을 지정하고, 해당 Step에 속하는 ItemReader, ItemProcessor, ItemWriter를 추가할 수 있습니다.

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

  @Autowired
  private StepBuilderFactory stepBuilderFactory;

  @Autowired
  private ItemReader itemReader;

  @Autowired
  private ItemProcessor itemProcessor;

  @Autowired
  private ItemWriter itemWriter;

  @Bean
  public Step myStep() {
    return stepBuilderFactory.get("myStep")
        .chunk(100)
        .reader(itemReader)
        .processor(itemProcessor)
        .writer(itemWriter)
        .build();
  }
}


▶ 이러한 구조를 통해 하나의 Job이 여러 개의 Step으로 이루어져 있고, 각 Step은 특정한 처리를 담당합니다.
▶ 각 Step은 별도의 트랜잭션에서 실행되며, 실패한 Step에 대해서는 롤백이 발생할 수 있습니다.
▶ Job과 Step은 Spring Batch의 핵심 구성 요소로, 일괄 처리 작업을 효율적으로 정의하고 실행하는 데 사용됩니다.

- 스프링 배치에서는 각각의 Step이 별도의 트랜잭션 내에서 실행됩니다.
- 이는 실패한 덩어리(chunk)에 대해 롤백이 발생할 때 해당 덩어리만 다시 처리할 수 있도록 합니다.
- 트랜잭션 관리는 스프링의 트랜잭션 매니저를 사용하여 수행됩니다

- JobParameters는 Job을 실행할 때 전달되는 파라미터를 나타냅니다.
- 예를 들어, 배치 작업이 실행되는 날짜, 시간 등과 같은 정보를 전달할 수 있습니다.
- 이는 Job 실행 시에 동적으로 변하는 값을 전달하는 데 사용됩니다.

■ ItemReader
데이터를 읽어오는 역할을 합니다. 파일, 데이터베이스, 메시지 큐 등 다양한 소스에서 데이터를 읽어와 스프링 배치에 전달합니다.

■ ItemWriter
가공된 데이터를 저장하거나 외부 시스템에 전달하는 역할을 합니다. 파일에 쓰기, 데이터베이스에 저장하기, 메시지 큐로 전송하기 등이 가능합니다.

- 스프링 배치에서는 일반적으로 스프링의 PlatformTransactionManager를 사용하여 트랜잭션을 관리합니다.
- 이는 각각의 Step이 별도의 트랜잭션 내에서 실행될 수 있도록 지원합니다.

스프링 배치에서의 실패 복구는 다양한 메커니즘을 활용하여 실패한 상황에 대처하는 것을 의미합니다.

📌 실패 복구 메커니즘
■ Skip 처리
- 일부 데이터가 실패해도 전체 작업이 중단되지 않고, 실패한 데이터를 건너뛰고 나머지 데이터를 계속 처리할 수 있도록 하는 방법입니다.
- skip` 메커니즘을 사용하여 특정 예외가 발생했을 때 해당 데이터를 스킵하고 계속 처리하도록 설정할 수 있습니다.

■ Retry 처리:
- 실패한 작업을 지정된 횟수만큼 반복해서 시도하는 방법입니다. 일정 횟수 동안 성공하지 못하면 해당 데이터를 스킵하거나 실패 상태로 처리할 수 있습니다.
- `RetryTemplate`과 같은 스프링의 리트라이 메커니즘을 사용하여 설정할 수 있습니다.

■ Rollback 처리:
- 실패한 데이터에 대한 롤백을 수행하여 이전 상태로 복구하는 방법입니다. 이는 트랜잭션을 롤백하여 데이터를 이전 상태로 돌리는 방식으로 동작합니다.
- 이 방법은 트랜잭션 관리를 통해 구현되며, 실패한 스텝 전체가 롤백됩니다.

■ Listener 활용:
스프링 배치의 이벤트 리스너를 활용하여 실패 시 발생하는 이벤트에 대한 리스너를 등록하여 특정 작업을 수행할 수 있습니다. 예를 들어, 실패한 데이터에 대한 로그를 남기거나 특정 조치를 취할 수 있습니다.

■ 별도의 실패 처리 로직 구현:
실패한 데이터에 대한 특별한 처리 로직을 별도로 구현하여 적용할 수 있습니다. 이는 비즈니스 로직에 따라 실패한 데이터를 어떻게 처리할지 정의하는 방식입니다.

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {

  @Autowired
  private StepBuilderFactory stepBuilderFactory;

  @Autowired
  private ItemReader itemReader;

  @Autowired
  private ItemProcessor itemProcessor;

  @Autowired
  private ItemWriter itemWriter;

  @Bean
  public Step myStep() {
    return stepBuilderFactory.get("myStep")
        .chunk(100)
        .reader(itemReader)
        .processor(itemProcessor)
        .writer(itemWriter)
        .faultTolerant() // 실패 복구 메커니즘 활성화
        .retryLimit(3)  // 최대 3번 재시도
        .retry(Exception.class) // 특정 예외에 대해서만 재시도
        .skipLimit(10)  // 최대 10번 스킵
        .skip(Exception.class) // 특정 예외에 대해서만 스킵
        .listener(new MySkipListener()) // 실패 시 리스너 등록
        .build();
  }
}


▶ 위의 예제에서는 `faultTolerant()`를 사용하여 실패 복구 메커니즘을 활성화하고, `retryLimit`, `retry`, `skipLimit`, `skip` 등의 메소드를 사용하여 재시도 및 스킵에 대한 설정을 지정하였습니다.
▶ 이러한 설정을 통해 실패한 데이터에 대한 처리를 세밀하게 제어할 수 있습니다.

Gradle과 Maven은 둘 다 프로젝트 빌드 및 종속성 관리 도구로 널리 사용되고 있습니다.

📌 주요 차이점
■ DSL (Domain-Specific Language)
Gradle: Groovy 언어를 사용하는 DSL을 제공합니다. Groovy는 간결하고 동적인 언어로, Gradle 스크립트 작성을 더 유연하게 만듭니다.
Maven: XML 기반의 설정을 사용합니다. XML은 간결성 측면에서는 부족하지만, Maven의 설정은 더 구조화되어 있을 수 있습니다.

■ 성능
Gradle: 빌드 캐싱 및 증분 빌드 등을 통해 성능이 향상되었습니다. 특히 큰 프로젝트에서 Gradle이 Maven보다 빌드 속도가 빠를 수 있습니다.
Maven: 이전에는 Maven이 간결한 구조로 인해 더 빨랐지만, Gradle의 성능 향상으로 인해 이 차이가 줄어들었습니다.

■ 설정 방식
Gradle: Groovy 스크립트를 사용하며, 각 태스크에 대한 메소드 호출을 통해 빌드 스크립트를 작성합니다. 이는 빌드 스크립트의 가독성을 향상시킵니다.
Maven: XML 기반의 설정 파일 (pom.xml)을 사용합니다. XML은 간결하지 않고 가독성이 떨어질 수 있지만, 일부 사용자에게는 표준화된 형식이 강점일 수 있습니다.

■ 종속성 관리
Gradle: 라이브러리의 다양한 버전을 사용하거나 특정 라이브러리만 가져오는 등의 세밀한 종속성 관리가 가능합니다.
Maven: 버전 충돌이 발생하지 않도록 의존성 트리를 평평하게 유지하는 경향이 있습니다.

■ 플러그인 시스템
Gradle: 플러그인 시스템이 강력하며, 다양한 플러그인을 사용하여 다양한 작업을 지원합니다.
Maven: 일부 작업에 대한 플러그인이 내장되어 있고, 다른 플러그인을 추가하여 기능을 확장할 수 있습니다.

■ 학습 곡선
Gradle: Groovy 언어와 DSL에 익숙하지 않은 경우 초기 학습 곡선이 존재할 수 있습니다.
Maven: XML을 사용하므로 문법이 간단하고 명확하며, Maven의 표준 프로젝트 구조 등을 이해하는 데 상대적으로 덜 복잡할 수 있습니다.

검색 및 분석 엔진으로, 대용량 데이터를 실시간으로 저장, 검색 및 분석할 수 있도록 설계된 오픈 소스 분산 검색 엔진입니다.

Apache Lucene 기반으로 만들어졌으며, RESTful API를 통해 사용자가 데이터를 색인하고 검색하는 데 사용됩니다.

📌 Elasticsearch 특징
■ 분산 아키텍처
Elasticsearch는 데이터를 여러 노드에 분산하여 저장하고 처리함으로써 성능 및 확장성을 제공합니다. 노드 간 데이터의 분산 저장은 데이터의 안정성과 가용성을 높입니다.

■ 실시간 검색 및 분석
Elasticsearch는 데이터를 실시간으로 색인하고 검색할 수 있습니다. 대용량 데이터에 대한 빠른 응답 시간을 제공하며, 실시간 분석을 통해 데이터의 트렌드 및 통계를 추적할 수 있습니다.

■ 다양한 데이터 형식 지원
Elasticsearch는 다양한 데이터 형식을 지원하며, JSON 형식의 문서로 데이터를 색인합니다. 이는 구조화된 데이터뿐만 아니라 비정형 데이터도 처리할 수 있게 해줍니다.

■ 강력한 검색 기능
Elasticsearch는 강력한 검색 엔진으로, 풍부한 쿼리 언어를 제공하여 사용자가 원하는 정확한 결과를 얻을 수 있습니다. 또한, 텍스트 검색에서 일치 단어의 중요도를 스코어링하여 결과를 반환합니다.

■ 오픈 소스 및 확장성
Elasticsearch는 오픈 소스로 제공되며, 다양한 플러그인과 함께 사용자의 요구에 따라 확장이 가능합니다. 다양한 생태계와 커뮤니티가 존재하여 지속적인 개발과 지원이 이루어지고 있습니다.

▶ 주로 로그 및 이벤트 데이터의 실시간 검색, 분석, 모니터링, 비즈니스 인텔리전스 등 다양한 분야에서 Elasticsearch가 활용되고 있습니다.
▶ Elasticsearch는 ELK 스택(ELK Stack)의 일부로 사용되기도 하는데, Logstash(데이터 수집), Kibana(시각화 및 대시보드)와 함께 사용되어 로그 및 이벤트 데이터의 효율적인 관리 및 시각화를 지원합니다.

GraphQL은 데이터를 쿼리하고 변이(mutations)를 수행하기 위한 쿼리 언어입니다.

Facebook에서 개발되었으며, RESTful API의 일부 한계와 복잡성을 극복하기 위해 설계되었습니다.

GraphQL은 클라이언트가 필요한 데이터의 구조를 명시적으로 정의할 수 있도록 하는 유연하고 강력한 API 쿼리 언어입니다.

📌 GraphQL 특징
■ 선택적인 데이터 검색
클라이언트가 필요로 하는 데이터를 명시적으로 지정할 수 있습니다. 이는 과도한 데이터 전송을 방지하고, 필요한 정보만을 효율적으로 가져올 수 있게 해줍니다.

■ 단일 요청으로 다중 리소스 요청
여러 REST 엔드포인트에서 데이터를 가져오는 대신, GraphQL은 단일 요청으로 여러 리소스를 동시에 요청할 수 있습니다. 이로 인해 네트워크 오버헤드를 최소화할 수 있습니다.

■ 유연한 타입 시스템
GraphQL은 강력한 타입 시스템을 가지고 있습니다. 서버는 사용 가능한 데이터 및 쿼리의 유효성을 검사할 수 있으며, 클라이언트는 명확한 타입 정보를 받아 타입 변환 및 검증을 수행할 수 있습니다.

■ 실시간 데이터 업데이트
GraphQL은 실시간 데이터 업데이트를 지원하며, 서버에서 클라이언트로 데이터를 자동으로 푸시할 수 있는 구독(subscription) 메커니즘을 제공합니다.

■ 단일 엔드포인트
여러 REST 엔드포인트 대신 단일 GraphQL 엔드포인트를 사용합니다. 이로써 클라이언트는 필요한 데이터를 단일 엔드포인트로 효율적으로 요청할 수 있습니다.

GraphQL 쿼리는 다음과 같은 형태를 가질 수 있습니다:

이 쿼리는 "user"와 그와 연관된 "posts"에 대한 데이터를 요청합니다. 서버는 이러한 쿼리를 해석하고 필요한 데이터를 제공합니다.

GraphQL은 다양한 언어와 프레임워크에서 지원되고 있으며, 클라이언트 측 라이브러리와 서버 측 구현이 다양하게 개발되어 있습니다.

가장 널리 사용되는 서버 측 구현으로는 Apollo Server, Express GraphQL, Django GraphQL 등이 있습니다.

클라이언트 측으로는 Apollo Client, Relay 등이 사용됩니다.

동적 스키마 설계는 유연성과 안정성 사이의 균형을 맞추며 데이터 일관성을 고려해야 합니다.
추가로 동적으로 변경될 수 있기 때문에 보안에 대한 부분도 신경써야 합니다.

■ 어떻게 균형을 맞출수 있나요?
데이터 모델의 동적인 특성을 적절히 제한하고 필요한 경우에만 동적인 변경을 허용 하도록 해서 제한된 변경의 일관성을 유지하면서 안정성을 높일 수 있습니다.

■ 보안은 어떻게 신경쓸 수 있죠?
데이터의 유효성을 검사하여 악성 데이터 삽입을 방지하고, 데이터 접근에 대한 제어와 권한 관리 등을 고려해볼 수 있고, 민감한 정보는 암호화나 마스킹 처리하는 등의 방법이 있을 수 있습니다.

■ 절차지향
프로시저에 중점을 두어 순차적인 처리와 프로그램이 유기적으로 연결되는 프로그래밍 언어입니다.

■ 객체지향
객체에 중점을 두고 데이터 캡슐화와 상속 등을 활용하여 유연하고 모듈화된 설계를 지향합니다.

■ 아파치
정적인 데이터를 처리하는 웹 서버로서 기본적으로 멀티 프로세스(하나의 프로그램을 여러 프로세스로 구성하여 각 프로세스가 하나의 작업을 처리하는 환경)로 구현 되지만 설정에 따라 멀티 쓰레드를 같이 운용할 수 있다.

■ 톰캣
WAS로서 요청을 처리하기 위한 Thread Pool 내 쓰레드를 제공하는 멀티 쓰레드다.

1. 사용자가 로그인 정보와 함께 인증 요청(HttpRequest)을 하면 AuthenticationFilter가 요청을 가로챕니다.
2. 가로챈 정보를 통해 UsernamePasswordAuthenticationToken(인증용 객체)를 만들고 이를 이용해 AuthenticationManager의 인증 메서드를 호출합니다.
3. 이후 UserDetailsService 구현체를 통해 DB에 저장된 정보와 비교해 일치하면 UserDetails 구현 객체를 반환해 SecurityContext에 저장합니다.

즉시로딩은 연관관계에 있는 모든 엔티티를 한 번에 조회합니다.
지연 로딩은 연관관계가 있는 엔티티가 실제로 사용욀 때 조회합니다.

📌 배열
- 동일한 자료형을 갖는 정적인 구조입니다.
- 미리 크기를 정해놓아야 하고, 수정이 불가능합니다.
- 연속된 메모리 주소를 할당받기 때문에 인덱스를 가지게 되어 검색 속도가 O(1)으로 빨라 연결리스트 보다 빠르며 접근, 탐색이 중요하면 배열을 쓰는것이 좋다.

📌 연결 리스트
- 제네릭 타입을 갖는 동적인 구조입니다.
- 크기를 미리 저장할 필요가 없고, 데이터가 추가됨에 따라 크기가 늘어나고 이 데이터들은 노드(값 + 다음 데이터 메모리 주소)를 통해 연결되고 추가 삭제가 용이합니다.
- 검색 속도는 O(n)의 시간복잡도를 가져 배열보다 느립니다.

JTA는 Java Transaction API의 약자로, 자바에서 트랜잭션 관리를 위한 API입니다.

Java EE(Enterprise Edition) 및 Jakarta EE(Java EE의 후속 프로젝트)에서 사용되는 API 중 하나로, 분산 환경에서 여러 리소스 간의 트랜잭션을 관리하는 데 사용됩니다.

■ JTA는 자바에서 트랜잭션 관리를 위한 표준 인터페이스와 클래스를 제공하며, 이를 사용하여 데이터베이스, 메시징 시스템, JMS(Java Message Service) 등과 같은 다양한 리소스 간에 일관된 트랜잭션을 처리할 수 있습니다.

■ 분산 트랜잭션 관리를 위한 여러 기능을 포함하고 있습니다.

일반적인 상황에서는 for-loop가 더 빠릅니다.

Effective Java의 공저자인 `Angelika Langer`는 JIT Compiler가 for-loop을 워낙 40년 이상 다뤄왔다 보니까 for-loop에 대한 internal optimization이 이미 잘 되어 있다고 말했습니다.

primitive type이 아니라 wrapped type의 loop에서는 for-loop가 약 1.27배 정도 빠른데 ArrayList를 순회하는 비용 자체가 워낙 크기 때문에, for-loop와 stream 간의 성능 차이를 압도해버리는 것입니다.

따라서, for-loop의 성능을 무시할정도로 크기카 커진다면 그 간격의 차이는 더 줄어든다고 볼 수 있습니다.

GC는 자바 메모리 관리 기법중 하나로 Heap 영역에서 동적으로 할당했던 메모리 중 필요 없게 된 메모리 객체를 주기적으로 제거하는 방식입니다.
가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생되는 문제점이 있고 이를 `Stop-The-World`라 합니다.

■ Mark And Sweep
- 다양한 GC에서 사용되는 객체를 솎아내는 내부 알고리즘
- 가비지 컬렉션이 동작하는 아주 기초적인 청소 과정
- 가비지 컬렉션이 될 대상 객체를 식별(Mark)하고 제거(Sweep)하며 객체가 제거되어 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)을 수행하게 된다.

📌 Heap은 Young과 Old 2가지 영역으로 설계
■ Young 영역(Young Generation)
- 새롭게 생성된 객체가 할당(Allocation)되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되기 때문에, 많은 객체가 Young 영역에 생성되었다가 사라집니다.
- Young 영역에 대한 가비지 컬렉션(Garbage Collection)을 Minor GC라고 부릅니다.

■ Old 영역(Old Generation)
- Young영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- Young 영역보다 크게 할당되며, 영역의 크기가 큰 만큼 가비지는 적게 발생합니다.
- Old 영역에 대한 가비지 컬렉션(Garbage Collection)을 Major GC 또는 Full GC라고 부릅니다.

또 다시 힙 영역은 더욱 효율적인 GC를 위해 Young 영역을 3가지 영역(Eden, survivor 0, survivor 1) 으로 나눕니다.

■ Eden
- new를 통해 새로 생성된 객체가 위치.
- 정기적인 쓰레기 수집 후 살아남은 객체들은 Survivor 영역으로 보냄

■ Survivor 0 / Survivor 1
- 최소 1번의 GC 이상 살아남은 객체가 존재하는 영역
- Survivor 영역에는 특별한 규칙이 있는데, Survivor 0 또는 Survivor 1 둘 중 하나에는 꼭 비어 있어야 하는 것입니다. (0,1을 이동하여 age를 증가 시킴)

이렇게 하나의 힙 영역을 세부적으로 쪼갬으로서 객체의 생존 기간을 면밀하게 제어하여 가비지 컬렉터(GC)를 보다 정확하게 불필요한 객체를 제거하는 프로세스를 실행하도록 합니다.

■ Minor GC
- Young Generation 영역은 짧게 살아남는 메모리들이 존재하는 공간
- 모든 객체는 처음에는 Young Generation에 생성
- Young Generation의 공간은 Old Generation에 비해 상대적으로 작기 때문에 메모리 상의 객체를 찾아 제거하는데 적은 시간이 걸립니다. (작은 공간에서 데이터를 찾으니까)
- 이 때문에 Young Generation 영역에서 발생되는 GC를 Minor GC라 불립니다.

■ Major GC (Full GC)
- Old Generation은 길게 살아남는 메모리들이 존재하는 공간
- Old Generation의 객체들은 거슬러 올라가면 처음에는 Young Generation에 의해 시작되었으나, GC 과정 중에 제거되지 않은 경우 age 임계값이 차게되어 이동된 녀석들이다.
- 그리고 Major GC는 객체들이 계속 Promotion되어 Old 영역의 메모리가 부족해지면 발생하게 됩니다.

Major GC가 일어나면 Thread가 멈추고 Mark and Sweep 작업을 해야 해서 CPU에 부하를 주기 때문에 멈추거나 버벅이는 현상이 일어나기 때문입니다.
따라서 자바 개발진들은 끊임 없이 가비지 컬렉션 알고리즘을 발전 시켜왔습니다.

■ Parallel GC
- Java 8의 디폴트 GC
- Serial GC와 기본적인 알고리즘은 같지만, Young 영역의 Minor GC를 멀티 쓰레드로 수행 (Old 영역은 여전히 싱글 쓰레드)
- Serial GC에 비해 stop-the-world 시간 감소

Apache와 Nginx는 모두 웹 서버 소프트웨어로, 클라이언트로부터 요청을 받아 정적 및 동적 콘텐츠를 제공하는 역할을 합니다.
그러나 각각의 특징과 사용 사례에 따라 다음과 같은 차이점이 있습니다.

■아키텍처
Apache는 `멀티 프로세스/스레드 아키텍처`를 사용합니다. 각 요청에 대해 새로운 프로세스 또는 스레드를 생성하여 처리합니다.
Nginx는 `이벤트 기반 아키텍처`를 사용합니다. 단일 프로세스 또는 스레드에서 `비동기 방식`으로 다수의 연결을 처리합니다.

■ 리소스 사용
Nginx는 Apache보다 적은 리소스를 사용합니다. 이는 더 많은 동시 연결을 처리할 수 있고, 메모리 사용량이 낮고 효율적인 CPU 사용을 보장합니다.
Apache는 각 요청에 대해 새로운 프로세스 또는 스레드를 생성하기 때문에 더 많은 메모리와 CPU를 사용합니다.

■ 성능
Nginx는 정적 파일 서비스 및 프록시 기능에 특히 탁월한 성능을 보입니다. 많은 정적 파일 요청이 있는 경우에 효율적으로 처리할 수 있습니다.
Apache는 동적 콘텐츠 및 모듈 지원에 더 많은 중점을 두고 있습니다.

■ 확장성
Nginx는 고성능 웹 서버로서 대규모 트래픽을 처리하는 데 매우 효과적입니다. 수천 개의 동시 연결을 처리할 수 있습니다.
Apache는 확장성이 떨어지는 경우가 있습니다. 많은 요청이 동시에 발생할 때 프로세스 또는 스레드 생성에 따른 오버헤드가 있을 수 있습니다.

■ 모듈 지원
Apache는 다양한 모듈을 통해 유연한 기능을 제공합니다. 이 모듈은 웹 서버, 프록시, 보안, 로깅 등 다양한 영역에 걸쳐 있습니다.
Nginx도 모듈 시스템을 가지고 있지만, Apache보다는 기능이 적습니다. 그러나 필요한 모듈은 손쉽게 추가할 수 있습니다.

결론적으로, Apache와 Nginx는 각각의 특징에 따라 다른 용도나 상황에 더 적합할 수 있습니다. 정적 파일 서비스나 프록시 역할에 최적화된 Nginx는 대규모 트래픽이 있는 웹 서버 환경에서 더 인기가 있습니다. 반면에 Apache는 모듈 지원과 유연성을 통해 다양한 요구 사항을 충족시키는 데 더 적합할 수 있습니다.

3계층 아키텍처(3-tier Architecture)는 소프트웨어 설계 및 개발에서 사용되는 일반적인 아키텍처 패턴입니다. 이 패턴은 응용 프로그램을 세 가지 주요 부분 또는 계층으로 나눕니다. 각 계층은 특정한 역할과 책임을 갖고 있으며, 서로 분리되어 있습니다.

📌 Presentation Tier (표현 계층 또는 사용자 인터페이스 계층)
- Presentation Tier는 사용자와 상호 작용하고 사용자에게 정보를 표시하는 역할을 합니다.
- 사용자 인터페이스(UI)를 포함하며, 사용자의 입력을 처리하고 적절한 응답을 생성합니다.
- 주로 웹 애플리케이션에서는 웹 브라우저나 모바일 앱과 같은 클라이언트 측 기술이 이 계층에 해당합니다.

📌 Logic Tier (비즈니스 계층 또는 애플리케이션 서버 계층)
- Logic Tier는 비즈니스 로직을 처리하고 응용 프로그램의 핵심 기능을 제공합니다.
- 클라이언트의 요청을 받아들이고 처리하는 역할을 합니다. 비즈니스 로직은 이 계층에 구현됩니다.
- 데이터 처리, 유효성 검사, 규칙 처리 등의 작업이 이루어집니다.
- 일반적으로 웹 애플리케이션에서는 서버 측 스크립트, 애플리케이션 서버, 미들웨어가 이 계층에 해당합니다.

📌 Data Tier (데이터 계층 또는 데이터베이스 계층)
- Data Tier는 데이터 저장, 검색 및 관리 기능을 담당합니다.
- 주로 데이터베이스 시스템을 포함하며, 응용 프로그램이 사용하는 데이터를 영구적으로 저장합니다.
- 데이터베이스 서버, 파일 시스템, 데이터 저장소 등이 이 계층에 해당합니다.

3계층 아키텍처는 각 계층이 독립적으로 관리되고 유지보수되며, 변경 사항이 다른 계층에 영향을 미치지 않도록 설계됩니다. 이는 응용 프로그램의 확장성, 유지보수성 및 재사용성을 향상시키는 데 도움이 됩니다. 또한 클라이언트와 데이터베이스 사이에 중간 계층으로 Logic Tier를 두어, 응용 프로그램의 보안 및 성능을 최적화할 수 있습니다.

데이터 동기화는 여러 마이크로서비스 간에 공유되는 데이터가 서로 일관성을 유지하는 것을 의미합니다.
데이터 동기화를 위해 일반적으로 다음과 같은 방법을 사용합니다.

이벤트 기반 통신: 서비스에서 발생하는 이벤트를 다른 서비스에게 알리고, 해당 이벤트를 수신한 서비스에서 데이터를 갱신합니다.
변경 로그: 데이터베이스 변경 사항을 로그에 기록하고, 다른 서비스가 해당 변경 사항을 읽어와 자신의 데이터를 갱신합니다.
분산 트랜잭션: 여러 서비스 간에 분산 트랜잭션을 사용하여 데이터 일관성을 유지합니다. 그러나 이는 복잡하고 비용이 많이 드는 접근 방식입니다.

서비스 간 동기화는 다른 마이크로서비스 간에 요청 및 응답을 통해 정보를 공유하는 것을 의미합니다.
서비스 간 동기화를 위해 일반적으로 다음과 같은 방법을 사용합니다.

REST API 호출: 서비스 간에 HTTP 기반의 RESTful API를 사용하여 데이터를 요청하고 응답합니다. 이는 가장 일반적이고 간단한 방법입니다.
gRPC: Protobuf와 같은 바이너리 프로토콜 버퍼를 사용하여 서비스 간 효율적인 통신을 제공하는 RPC(Remote Procedure Call) 기반의 프레임워크입니다.
메시지 큐: 비동기적으로 서비스 간에 메시지를 교환하여 통신하는 방법으로, 대표적으로 Kafka나 RabbitMQ와 같은 메시지 브로커를 사용합니다.

📌 스택 오버플로우(Stack Overflow)
원인: 스택 메모리의 한계를 초과하여 메서드 호출 스택에 과도한 재귀 호출이 쌓일 때 발생합니다. 예를 들어, 재귀 함수가 종료 조건을 충분히 만족하지 않고 반복 호출되면 스택 프레임이 계속 쌓여서 스택의 한계를 초과하게 됩니다.
결과: 일반적으로 스택 오버플로우가 발생하면 프로그램이 비정상적으로 종료됩니다. 이는 스택이 메모리를 넘어선 상태에서 더 이상 메서드 호출을 처리할 수 없기 때문입니다. 오류 메시지가 표시되거나, 무한 루프가 발생하여 프로그램이 멈출 수도 있습니다.

📌 메모리 부족(Out of Memory)
원인: 프로그램이 사용하려는 메모리 양이 시스템에서 사용 가능한 메모리 양을 초과할 때 발생합니다. 이는 일반적으로 메모리 누수(memory leak) 또는 프로그램이 할당된 메모리를 효율적으로 관리하지 못할 때 발생합니다.
결과: 메모리 부족이 발생하면 프로그램이 다양한 방식으로 영향을 받습니다. 예를 들어, 프로그램이 느려지거나 반응하지 않을 수 있으며, 더 나아가 시스템 전체가 느려지거나 다운될 수 있습니다. Out of Memory 오류가 발생하면 보통 프로그램이 강제 종료됩니다. 이때 보통은 예외가 발생하거나, 운영 체제에서 프로그램을 종료시킵니다.

📌 정리
스택 오버플로우는 프로그램의 재귀 호출이 스택 메모리 한계를 초과할 때 발생 (JVM의 실행 스택 영역)
메모리 부족은 프로그램이 시스템에서 사용 가능한 메모리 양을 초과할 때 발생 (JVM의 힙(Heap) 영역)

StringBuilder의 append 메서드의 시간복잡도는 O(1) 입니다.
이는 StringBuilder가 내부적으로 가변 크기의 버퍼를 사용하기 때문에 발생합니다.
일반적으로 append 메서드는 상수 시간 내에 작동하며, 문자열을 버퍼에 추가하고 필요에 따라 버퍼 크기를 조정합니다.
따라서 문자열을 추가하는 데 필요한 시간은 현재 버퍼의 크기와 관계없이 일정합니다.
이는 문자열 길이에 비례하지 않고, 빠른 문자열 연결이 가능하도록 합니다.

HashMap과 TreeMap은 모두 자바 컬렉션 프레임워크의 맵(Map) 인터페이스를 구현한 클래스입니다. 하지만 그들 사이에는 몇 가지 중요한 차이가 있습니다.

1. 내부 구현 방식
HashMap: 해시맵은 해시 테이블을 기반으로 합니다. 이는 키-값 쌍을 저장할 때 각 키의 해시 코드를 계산하여 해시 버킷에 매핑합니다. 이로써 빠른 검색, 삽입, 삭제를 지원합니다. 해시맵은 순서를 보장하지 않습니다.
TreeMap: 트리맵은 레드-블랙 트리(Red-Black Tree)를 기반으로 합니다. 키-값 쌍을 키의 자연 순서 또는 커스텀된 정렬 순서로 유지합니다. 이로써 항상 정렬된 순서로 요소에 접근할 수 있습니다.

2. 정렬
HashMap: 요소들의 순서는 해시 함수에 의존하며, 순서가 보장되지 않습니다.
TreeMap: 요소들은 키의 정렬 순서에 따라 정렬되어 저장되므로 항상 정렬된 순서로 요소에 접근할 수 있습니다.

3. 속도
HashMap: 일반적으로 해시맵은 상수 시간 복잡도(O(1))를 가지지만, 해시 충돌이 발생할 경우 성능이 저하될 수 있습니다. 그러나 해시 충돌을 최소화하기 위한 적절한 해시 함수를 선택하면 이러한 상황을 피할 수 있습니다.
TreeMap: 트리맵은 이진 검색 트리의 속성을 이용하여 항상 O(log n)의 시간 복잡도를 가집니다. 이는 키의 범위가 큰 경우에도 일정한 성능을 보장합니다.

4. 메모리 사용
HashMap: 해시맵은 해시 버킷을 사용하여 요소를 저장하기 때문에 일반적으로 메모리 사용량이 적습니다.
TreeMap: 트리맵은 레드-블랙 트리를 사용하여 요소를 저장하기 때문에 해시맵보다 더 많은 메모리를 사용할 수 있습니다.

public class MySqrt {
  public int mySqrt(int x) {
    if (x == 0 || x == 1) return x;

    int left = 1;
    int right = x;
    int result = 0;

    while (left <= right) {
      int mid = left + (right - left) / 2;
      if (mid <= x / mid) {
        left = mid + 1;
        result = mid;
      } else {
        right = mid - 1;
      }
    }
    
    return result;
  }
}

Java에서 GC는 메모리 관리를 자동으로 처리하는 기술입니다. Java에서는 new 연산자를 사용하여 객체를 생성하면, JVM은 객체를 저장할 공간을 자동으로 할당하고, 객체의 참조 카운트(Reference Count)를 증가시킵니다.
참조 카운트가 0이 되면 GC가 해당 객체를 메모리에서 제거합니다.
GC는 메모리 사용량이 많아지면, 메모리 누수(Memory Leak)를 방지하고, 메모리 사용을 최적화하기 위해 주기적으로 실행됩니다

Call by value와 Call by reference는 함수 호출 방식에 따라 인수(Parameter)를 전달하는 방식을 나타내는 용어입니다.
Call by value는 값에 의한 호출 방식으로, 함수의 인수를 복사하여 전달합니다.
함수 내에서 인수 값을 변경해도, 호출한 측에는 영향을 주지 않습니다.
반면, Call by reference는 참조에 의한 호출 방식으로, 함수의 인수로 변수의 주소를 전달합니다.
따라서 함수 내에서 인수 값을 변경하면, 호출한 측에서도 영향을 받습니다.

다형성은 객체지향 프로그래밍에서 사용되는 개념으로, 동일한 이름의 메서드나 연산자가 다양한 객체에서 다양하게 동작할 수 있도록 하는 것입니다.
예를 들어, 부모 클래스의 메서드를 자식 클래스에서 오버라이딩하여 사용할 수 있으며, 각 클래스에서 메서드의 내용이 다르게 구현될 수 있습니다.
이렇게 같은 이름의 메서드를 다른 방식으로 동작하도록 하는 것을 메서드 오버라이딩(Method Overriding)이라고 합니다.
또한, 다양한 타입의 객체를 같은 타입으로 처리할 수 있는 것을 다형성이라고 합니다.
이를 이용하여 객체의 유연한 처리가 가능합니다.