인터넷은 인터넷 프로토콜 TCP/IP를 기반으로 전 세계적으로 연결되어있는 컴퓨터 네트워트 통신망으로 흔히 웹이라고 부르는 월드 와이드 웹(www)을 포함해 동영상 스트리밍, 온라인 게임 등의 다양한 서비스를 지원한다.

 

월드 와이드 웹의 주요 기능은 URL을 통한 통일된 웹 자원의 위치 지정 방법, 웹의 자원에 접근하는 프로토콜(HTTP), HTML 언어로 문서들을 체계화하여 정보를 신속하게 교환할 수 있도록 데이터베이스를 구축하고 이를 전문 열람 소프트웨어로 열람하는 방식으로 탄생하였다.

 

인터넷 네트워크의 수 많은 노드를 거쳐서 데이터를 주고 받기 위해서 각각의 노드를 구분할 수 있는 IP 주소를 지정하고 패킷이라는 통신 단위로 데이터를 전송하는 인터넷 프로토콜(IP)이 등장했다. IP 패킷은 출발지 IP, 목적지 IP, 전송 데이터를 포함하는데 비연결성, 비신뢰성의  문제로 TCP 프로토콜이 나오게 된다. (IP 주소는 구분하기도 어렵고 변경 될 수 있기 때문에 DNS 방식을 통해 IP 주소를 특정 이름의 도메인 명으로 바꾸어 사용, google.com, naver.com)

 

웹 브라우저에서 SOCKET 라이브러리를 통해 HTTP 데이터를 전송하면 TCP 정보가 생성이 되고 이를 IP 패킷이 감싸게 된다. 

TCP3 way handshake라는 논리적인 가상 연결 방식과 PORT 정보와 전송 제어, 순서, 검증 정보 등으로 높은 신뢰성과 연결성을 가지는 프로토콜로 현재 대부분 TCP 프로토콜을 사용 (PORT : 같은 IP 내에서 프로세스를 구분하기 위한 번호, Http-80, Https-443)

 

URI인터넷에 있는 자원(식별할 수 있는 모든 것, Resource)을 나타내는 유일한 주소로 리소스의 위치를 지정하는 URL, 이름을 지정하는 URN 방식이 있는데 URN 방식은 실제 리소스를 찾기가 어려워서 거의 URL을 사용한다. (URI = URL)

URL은 프로토콜(https)과 호스트명(www.google.com), 포트 번호, 패스(/path) 경로, 쿼리 파라미터로 이루어져 있다.

 

 

[정리]

 

1. 웹 브라우저(애플리케이션 계층)에서 HTTP 메시지를 SOCKET 라이브러리를 통해 전송

2. 전송 계층은 HTTP 메시지에 TCP 헤더를 추가한 TCP 세그먼트를 생성해 인터넷 계층으로 전송

3. 인터넷 계층은 TCP 세그먼트에 IP 헤더를 추가한 IP 패킷을 생성하여 네트워크 계층으로 전송

4. 네트워크 계층은 TCP/IP 패킷을 이더넷 헤더를 추가해 이더넷 프레임을 생성하고 이를 전기 신호로 변환하여 인터넷으로 전송

5. 인터넷의 중간 노드(라우터)를 거쳐 목적지에 도착을 하면 네트워크 계층에서 이더넷 헤더의 MAC 주소를 읽어 자신의 데이터인지 확인

6. 이더넷 프레임에서 이더넷 헤더를 삭제한  IP 패킷을 인터넷 계층으로 전송

7. 인터넷 계층은 IP 헤더의 IP 주소를 확인하고 IP 헤더를 제거한 TCP 세그먼트를 전송 계층으로 전송

8. 전송 계층은 TCP 헤더를 검증하고 포트 번호를 확인하여 해당 포트의 애플리케이션에 HTTP 메시지를 전송

9. 애플리케이션 계층은 HTTP 헤더를 읽고 데이터를 처리

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

[참고]https://better-together.tistory.com/93

 

 

[Java] 자바 예외 (Exception)

자바 예외는 최상위 예외 계층인 Throwable, 그 하위로 Exception과 Error가 있는데 Error는 메모리 부족 같이 애플리케이션에서 복구 불가능한 시스템 예외인데 catch로 예외를 잡으면 하위 예외(Error)까

treecode.tistory.com

 

[DB 접근 기술1] 스프링 트랜잭션

일반적인 웹 애플리케이션 구조는 다음과 같다. 프레젠테이션 계층 (@Controller) 서비스 계층 (@Service) 데이터 접근 계층 (@Repository) 여기서 가장 중요한 곳은 핵심 비즈니스 로직이 있는 서비스 계

treecode.tistory.com

 

스프링 트랜잭션 AOP를 사용하면 트랜잭션 로직과 비즈니스 로직을 깔끔하게 분리할 수 있는데 예외 처리에 대한 의존까지는 해결할 수 없다. 리포지토리에서 서비스가 처리할 수 없는 체크 예외를 던지면 서비스 계층에 불필요한 예외 의존 관계가 생기는데 체크 예외를 런타임 예외로 전환해서 던지면 서비스 계층을 순수하게 유지할 수 있다. (체크 예외의 경우 인터페이스에서도 throws를 선언해야 한다.)

 

try {
    ...
} catch (SQLException e) {
    // RuntimeException을 상속 받은 예외 생성
    // 예외 e를 파라미터로 보내줘야 나중에 예외 출력 시 기존 예외를 확인할 수 있다.
    throw new MyException(e);
} finally {
    ...
}

 

데이터베이스에서 발생한 예외중 특정 상황에는 예외를 잡아서 복구하고 싶은 경우 예외의 ErrorCode를 확인해서 새로운 예외로 변환하면 되는데 데이터베이스마다 똑같은 오류라도 ErrorCode가 다 다르기 때문에 직접 ErrorCode를 확인해서 처리하기는 어렵다.

 

스프링은 데이터 접근과 관련된 예외를 추상화해서 제공하는데 각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있고 심지어 스프링이 제공하는 예외로 변환하는 예외 변환기가 ErrorCode를 확인해서 적절한 예외로 변환까지 해준다.

 

스프링이 제공하는 데이터 접근 계층 예외는 RuntimeException을 상속 받은 DataAccessException로 크게 두가지로 나뉜다.

  • Transient : 일시적인 오류로 동일한 SQL로 다시 시도하면 성공할 가능성이 있는 경우 (쿼리 타임아웃, 락 관련 오류 등)
  • NonTransient : SQL 문법 오류, 데이터베이스 제약 조건 위배 등 일시적이지 않은 오류

 

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

 

자바 예외는 최상위 예외 계층인 Throwable, 그 하위로 Exception과 Error가 있는데 Error는 메모리 부족 같이 애플리케이션에서 복구 불가능한 시스템 예외인데 catch로 예외를 잡으면 하위 예외(Error)까지 함께 잡기 때문에 Exception 예외를 실질적인 최상위 예외로 생각하면 된다. 

 

Exception과 그 하위 예외는 RuntimeException을 제외하고 전부 컴파일러가 체크하는 체크 예외(잡아서 처리하거나 밖으로 던지지 않을 경우 컴파일 오류 발생)인데 RuntimeException은 컴파일러가 체크 하지 않는 언체크 예외(하위 예외 포함)로 런타임 예외라고 부른다.

 

예외는 잡아서 처리하거나 호출한 곳으로 던지게 되는데 잡거나(catch) 던질(throws) 경우 지정한 예외의 하위 예외들도 함께 처리가 된다. 예외를 처리하지 못할 경우 자바 main() 쓰레드는 예외 로그를 출력하면서 시스템이 종료되는데 웹 애플리케이션은 시스템이 종료되지 않는 대신 WAS가 해당 예외를 받아서 오류 페이지를 보여주는 식으로 처리를 한다.

 

체크 예외는 예외를 잡아서 처리하지 않는 경우 예외를 던지는 throws를 무조건 선언해야 하는데 예외를 누락하지 않도록 컴파일 시점에서 확인을 해준다는 장점이 있지만 실제로는 모든 예외를 전부 처리해야 되고 의존관계에 문제가 생기는 등의 단점이 있다.

언체크 예외는 말 그대로 컴파일러가 예외를 체크하지 않는 예외로 체크 예외와 달리 throws를 생략해도 되는데 중요한 예외의 경우에는 throws를 선언해서 코드를 호출하는 개발자에게 정보를 줄 수 있다.

 

기본적으로는 런타임 예외를 사용하고 체크 예외는 비즈니스 로직상 의도적으로 던지는, 반드시 처리해야 하는 문제에만 사용을 하는 것이 좋다. 예를 들어 JDBC를 사용하다가 JPA 같은 기술로 변경하는 경우 throws SQLException 코드를 전부 throws JPAException으로 바꿔야 하는 등 OCP, DI에 큰 문제가 생기는데 대부분의 예외는 서비스, 컨트롤러에서 복구 불가능한 예외라서 불필요한 의존관계 문제가 생기는 것이다.

 

복구 불가능한 예외의 경우 서블릿 필터나 스프링 인터셉터, 스프링 ControllerAdvice를 통해 일관성 있게 공통으로 처리해서 오류를 빠르게 인지하는 것이 중요한데 체크 예외를 런타임 예외로 변환하는 경우 예외를 일일히 throws 하지 않아도 되고 공통으로 처리를 하게 되면 변경시에도 영향 범위가 최소화 된다.

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

일반적인 웹 애플리케이션 구조는 다음과 같다.

  • 프레젠테이션 계층 (@Controller)
  • 서비스 계층 (@Service)
  • 데이터 접근 계층 (@Repository)

 

여기서 가장 중요한 곳은 핵심 비즈니스 로직이 있는 서비스 계층으로 비즈니스 로직은 최대한 변경 없이 순수하게 유지되도록 특정 기술에 종속적이지 않게 개발하는 것이 좋다. 서비스 계층을 특정 기술에 종속적이지 않게 개발하기 위해 다른 계층에서 기술에 종속적인 부분을 가지고 가는데 프레젠테이션 계층UI와 관련된 기술인 웹, 서블릿, HTTP와 관련된 부분을 담당하고 데이터 접근 계층JDBC, JPA와 같은 데이터 접근 기술을 담당한다. 서비스 계층은 데이터 접근 계층에 직접 접근하지 않고 인터페이스를 통해 접근하면 비즈니스 로직을 유지보수, 테스트 하기 쉽게 관리할 수 있다.

 

JDBC로 서비스 계층에서 트랜젝션을 시작하면 DataSource, Connection 같은 JDBC 기술에 의존하게 되는데 JDBC 코드를 데이터 계층으로 옮기고 인터페이스로 제공한다 해도 데이터 계층에서 발생한 JDBC 예외가 서비스 계층으로 전파되고  try, catch 코드가 반복되는 등 여러 문제가 발생한다.

 


 

스프링 트랜잭션

 

스프링은 서비스 계층을 순수하게 유지하면서 위의 문제들을 해결할 수 있는 기술을 제공한다.

 

트랜잭션 매니저(TransactionManager)

 

스프링은 PlatformTransactionManager 라는 인터페이스를 제공하는데 이는 트랜잭션 추상화를 통해 데이터 접근 기술이 바뀌면 트랜젝션 코드를 수정해야 하는 등의 문제를 해결해주고 각각의 기술에 맞는 구현체(JpaTransactionManage..)도 직접 만들어 제공한다.

 

트랜잭션 매니저는 내부적으로 트랜잭션 동기화 매니저를 사용하는데 이는 쓰레드 로컬을 사용해서 트랜젝션의 시작부터 끝까지 같은 커넥션을 유지할 수 있도록 커넥션을 동기화 해주고 쓰레드 로컬을 사용하기 때문에 멀티 쓰레드 상황에서 안전하게 커넥션을 동기화 할 수 있다.

 

트랜젝션 매니저는 트랜젝션을 시작하면 트랜젝션 동기화 매니저를 통해 쓰레드 로컬에 커넥션을 보관하는데 리포지토리는 트랜젝션 동기화 매니저를 통해 동기화된 커넥션을 꺼내서 획득한 커넥션으로 SQL을 데이터베이스에 전달해서 실행한다.

트랜잭션은 커밋하거나 롤백하면 종료가 되는데 트랜잭션 동기화 매니저를 통해 동기화된 커넥션을 획득하고 데이터베이스에 트랜잭션을 커밋하거나 롤백한 뒤에 전체 리소스를 정리한다. 트랜잭션 동기화 매니저는 쓰레드 로컬을 사용하기 때문에 사용 후 반드시 리소스를 정리해야 한다.

 

추가로 스프링은 TransactionTemplate이라는 템플릿 클래스를 제공하는데 트랜잭션 템플릿을 사용하면 트랜잭션을 시작하고, 커밋하거나 롤백하는 코드를 전부 제거할 수 있다. (비즈니스 로직이 정상 수행되거나 체크 예외가 발생하면 커밋하고 언체크 예외는 발생 시 롤백)

 

하지만 트랜잭션 템플릿을 사용해도 비즈니스 로직과 트랜잭션 로직을 완전히 분리할 수는 없는데 스프링 AOP를 통해 프록시를 도입하면 해당 문제를 깔끔하게 해결할 수 있다.

 


 

스프링 트랜잭션 AOP

 

스프링 AOP를 통해 트랜잭션 프록시를 사용하면 서비스 계층에서 트랜잭션을 생성했던 것과 달리 트랜잭션 프록시 객체에서 트랜잭션을 생성해서 비즈니스 로직을 호출을 하는 방식으로 바뀌어 트랜잭션과 비즈니스 로직을 처리하는 객체를 명확하게 분리할 수 있다.

스프링이 제공하는 트랜잭션 AOP를 사용하면 프록시를 매우 편리하게 적용할 수 있는데 스프링 부트는 트랜잭션 AOP를 처리하기 위해 필요한 스프링 빈들(어드바이저, 포인트컷, 어드바이스)을 자동으로 등록해준다. 

 

스프링이 제공하는 트랜잭션 AOP를 사용하려면 메서드나 클래스에 @Transactional 애노테이션을 추가하면 되는데 클래스에 붙이면 외부에서 호출 가능한 public 메서드에 AOP 적용이 된다.

 

 

간단한 흐름

 

1. 트랜잭션 AOP 프록시 트랜잭션 시작

2. 스프링 컨테이너에 등록된 트랜잭션 매니저 획득

3. transactionManager.getTransaction()

4. DataSource를 통해 커넥션 조회, 없으면 생성

5. setAutoCommit(false)

6. 트랜잭션 동기화 매니저에 커넥션 보관

7. 트랜잭션 프록시에서 실제 객체의 서비스 로직 호출

8. 리포지토리에서 트랜잭션 동기화 매니저로부터 커넥션 획득

 

스프링 부트는 커넥션 풀을 제공하는 HikariDataSource를 생성해서 스프링 빈으로 등록하고 현재 등록된 라이브러리를 확인하여 데이터 접근 기술에 맞는 PlatformTransactionManager 구현체를 스프링 빈으로 등록(빈 이름 : transactionManager)한다.

+ Recent posts