Skip to content

Latest commit

 

History

History
329 lines (215 loc) · 14 KB

TIL220414.md

File metadata and controls

329 lines (215 loc) · 14 KB

Daily to do list

Java


Spring


CS


알고리즘


오늘의 회고

220413 Spring DB

트랜잭션 문제점들

  • 애플리케이션 구조 가장 단순하고 많이 사용하는 방법은  사용 방법에 따라 역할을 3가지 계층으로 나누는 것이다.

프레젠테이션 계층 : UI와 관련된 처리 담당 웹 요청과 응답 사용자 요청을 검증 주 사용 기술: 서블릿과 HTTP 같은 웹 기술, 스프링 MVC

서비스 계층 : 비즈니스 로직을 담당 주 사용 기술: 가급적 특정 기술에 의존하지 않고, 순수 자바 코드로 작성

데이터 접근 계층 : 실제 데이터베이스에 접근하는 코드 주 사용 기술: JDBC, JPA, File, Redis, Mongo

  • 순수한 서비스 계층 이 세 계층에서 가장 중요한 곳은?.. 바로 핵심 비즈니스 로직이 있는 서비스 계층이다. 시간이 흘러 UI나 데이터 저장 기술이 바뀌어도 비즈니스 로직은 최대한 변경 없이 유지되어야 한다. 이렇게 하려면 서비스 계층을 특정 기술에 종속적이지 않고 개발해야 한다. 예를 들어서 HTTP API를 사용하다가 GRPC같은 기술로 변경해도 프레젠테이션 계층만 바꾸면 된다. 데이터 접근 계층은 데이터를 저장하고 관리하는 기술을 담당해준다. 그래서 JDBC, JPA와 같은 구체적인 데이터 접근 기술로부터 서비스 계층을 보호해준다. 예를 들어서 JDBC를 사용하다가 JPA 로 변경해도 서비스 계층은 변경하지 않아도 된다. 물론 서비스 계층에서 데이터 접근 계층을 직접 접근하는 것이 아니라, 인터페이스를 제공하고 서비스 계층은 이 인터페이스에 의존하는 것이 좋다. 그래야 서비스 코드의 변경 없이 JdbcRepository 를 JpaRepository 로 변경할 수 있다.

서비스 계층이 특정 기술에 종속되지 않기 때문에 비즈니스 로직을 유지보수 하기도 쉽고 테스트하기도 쉽다.

정리 : 서비스 계층은 가급적 비즈니스 로직만 구현하고 특정 기술에 직접 의존해서는 안된다. 이렇게 하면 향후 구현 기술이 변경될 때 변경의 영향 범위를 최소화 할 수 있다.

 

하지만 여기에도 남은 문제가 있다. SQLException이라는 JDBC 기술에 의존. : 이 부분은.. 뒤에서 예외를 다룰 떄 해결 MemberRepositoryV1이라는 구체 클래스에 직접 의존하고 있다. MemberRepository인터페이스를 도입하면 향후 MemberService의 코드 변경 없이 다른 구현 기술로 손쉽게 변경할 수 있다.

 

JDBC 의존이 많고 향후 JDBC를 JPA와 같은 다른 기술로 바꾼다면 다 바꿔야 한다.

  • 문제 정리
  1. 트랜잭션 문제

  2. 예외 누수 문제

  3. JDBC 반복 문제

  4. 트랜잭션 문제 JDBC 구현 기술이 서비스 계층에 누수되는 문제 : 트랜잭션을 적용하기 위해 JDBC 구현 기술이 서비스 계층에 누수되었다. 서비스 계층은 순수해야 한다 >> 구현 기술을 변경해도 서비스 계층 코드는 최대한 유지(변화에 대응)

트랜잭션 동기화 문제 : 같은 트랜잭션을 유지하기 위해 커넥션을 파라미터로 넘겨야 함….

트랜잭션 적용 반복 문제: 트랜잭션 적용 코드를 보면 반복이 많다.. try catch finally 등

  1. 예외 누수 데이터 접근 계층의 JDBC 구현 기술 예외가 서비스 계층으로 전파. (Service에서도 SQLException을 throw처리 해야함) SQLException은 JDBC 전용 기술이다. 향후 데이터 접근 기술을 바꾸면 다른 예외를 변경 그러면 서비스도 변경해야함..

  2. JDBC 반복 문제 유사한 코드의 반복이 너무 많다. try catch finally…..

스프링과 문제 해결! 스프링은 서비스 계층을 순수하게 유지하면서 지금까지 이야기한 문제들을 해결할 수 있는 다양한 방법과 기술들을 제공.

—————————————————————————— 트랜잭션 추상화

데이터 접근 기술을 바꾸면.. 코드가 다 바뀐다 트랜잭션 시작부터..  이렇게 기술을 바꾸면 서비스도 바꿔줘야 한다.. 단일 책임 원칙이 안맞음..

이 문제를 해결하기 위해 “트랜잭션 추상화”

 단순하다.. 생각보다 맞는 구현체 넣으면 된다.

 이렇게 추상화 된 인터페이스에 의존하면 된다! 평소에 하던거 처럼 갈아끼우기 하면 됨!

클라이언트인 서비스는 인터페이스에 의존하고 DI를 사용한 덕분에 OCP(개방폐쇄)원칙을 지키게 되었다.

 스프링의 트랜잭션 추상화.. 이미 다 만들어 있다. 

——————————————— 트랜잭션 동기화

트랜잭션 매니저는 크게 2가지 역할

  1. 트랜잭션 추상화
  2. 리소스 동기화

리소스 동기화 : 트랜잭션을 유지하려면 트랜잭션의 시작부터 끝까지 같은 데이터베이스 커넥션을 유지해야한다. 결국 같은 커넥션을 동기화하기 위해 이전에는 파라미터로 커넥션을 전달.. 하지만 이 방법은 여러가지 단점이 많다.

 트랜잭션 매니저는 동기화 매니저를 부른다. 이것을 쓰레드 로컬에서 사용해서 멀티쓰레드 상황에서 안전하게 커넥션을 동기화 할 수 있다. 커넥션이 필요하면 트랜잭션 동기화 매니저를 통해 커넥션을 획득하면 된다. 따라서 이전처럼 파라미터로 커넥션을 전달하지 않아도 된다.

 트랜잭션 매니저는 데이터소스를 통해 커넥션을 만들고 트랜잭션 시작. 트랜잭션 매니저는 이 트랜잭션의 커넥션을 트랜잭션 동기화 매니저에 보관 리포지토리는 트랜잭션 동기화 매니저에 보관된 커넥션을 꺼내서 사용.(파라미터로 커넥션 전달 x) 트랜잭션이 종료되면 트랜잭션 동기화 매니저에 보관된 커넥션을 통해 트랜잭션 종료, 커넥션 종료

참고 : 쓰레드 로컬을 사용하면 각각의 쓰레드마다 별도의 저장소가 부여된다. 따라서 해당 쓰레드만 해당 데이터에 접근할 수 있다.

——————————————— 트랜잭션 문제 해결 - 트랜잭션 매니저

 이제는 커넥션을 얻고 반납할 떄 DataSourceUtils를 활용해서 해야 한다.

  • DataSourceUtils.getConnection() 트랜잭션 동기화 매니저가 관리하는 커넥션이 있으면 해당 커넥션을 반환! 트랜잭션 동기화 매니저에 커넥션이 없으면 새로운 커넥션을 생성해서 반환!

  • DataSourceUtils.releaseConnection() 트랜잭션을 사용하기 위해 동기화된 커넥션은 커넥션을 닫지 않고 그대로 유지해준다. (트랜잭션의 끝[commit,rollback]은 서비스에서 동기화 매니저로 알려준다. repository는 신경 x) 트랜잭션 동기화 매니저가 관리하는 커넥션이 없는 경우는 닫는다.

 transactionManager를 service에 적용하면 이렇게 하면 된다..

————————— 정리 트랜잭션 추상화 덕분에 서비스 코드는 JDBC 기술에 의존하지 않는다. 트랜잭션 동기화 매니저 덥군에 커넥션을 파라미터로 넘기지 않아도 된다.

————————————— 트랜잭션 템플릿

트랜잭션을 사용하는 로직을 보면 패턴이 반복된다. 이 반복문제를 템플릿 콜백 패턴을 활용해서 해결 가능.

 템플릿 콜백 패턴을 이해해야 함.

비즈니스 로직이 정상 수행되면 커밋 언체크 예외가 발생하면 롤백, 그 외에 경우 그냥 커밋(우선 이렇게 이해)

람다에서는 체크 예외를 밖으로 던질 수 없기 때문에 언체크 예외로 바꾸어 던지도록 예외를 전환.

  • 정리 트랜잭션 템플릿을 써서 반복되는 구문들을 제거 할 수 있다. 하지만 이곳은 서비스로직.. 비즈니스 로직 뿐만 아니라 트랜잭션을 처리하는 기술 로직이 함께 포함되어 있어 수정 필요… 서비스에서의 비즈니스로직 = 핵심 기능, 트랜잭션 = 부가 기능.. 즉 이렇게 두 로직이 두 관심사를 하나에 클래서에서 처리하게 되고 유지보수가 어려워진다. 서비스 로직은 가급적 핵심 비즈니스 로직만 있어야 한다! 하지만 트랜잭션 기술을 사용하려면 어쩔 수 없이 트랜잭션 코드가 나와야 한다..

어떻게 해결…하지?!

—————————————— 트랜잭션 문제 해결 - 트랜잭션 AOP 이해

위의 서비스 계층을 순수한 비즈니스 로직만 남기기 위해서는 스프링 AOP와 프록시로 가능 우선.. 지금은 @Transactional을 사용하면 스프링 AOP를 사용해서 트랜잭션을 편리하게 처리해준다 정도로만

 프록시를 사용하면 트랜잭션을 처리하는 객체와 비즈니스 로직을 처리하는 서비스 객체를 명확하게 분리할 수 있다.

 서비스 코드가.. 진짜 순수한 비즈니스 로직만 남게 된다.

프록시가 트랜잭션 관련된 부분을 맡아주게 되어서 서비스는 순수한 비즈니스 로직만 남음

  • 스프링이 제공하는 트랜잭션 AOP 스프링이 제공하는 AOP기능을 사용하면 프록시를 편리하게 적용 할 수 있음. @Aspect, @Advice, @Pointcut을 사용…

스프링이 제공하는 트랜잭션 AOP를 적용하기 위해 @Transactional 애노테이션 추가.

** 참고 : 테스트를 할 때는 @SpringBootTest를 붙여야지 스프링컨테이너를 생성해줌 @TtestConfiguration은 테스트 안 내부 설정 클래스를 만들어서 사용하고 스프링부트가 자동으로 해줌  그리고 빈과 의존관계 주입도 해줘야 함. ** transactionManager도 빈으로 등록을 하는 이유는 트랜잭션 프록시에서 결국 transactionManager를 씀

 로그를 찍어보면 서비스는 프록시가 붙어서 상속받은? 클래스를 보여주고 있음

@Transactional 을 쓰면 프록시가 처리를 해준다!

——————————— 트랜잭션 AOP 정리

  1. 프록시 호출
  2. 트랜잭션 매니저 사용해서 트랜잭션 동기화 매니저에 커넥션 보관
  3. 데이터 접근 로직은 트랜잭션 동기화 매니저에서 커넥션 획득
  • 선언적 트랜잭션 관리 vs 프로그래밍 방식 트랜잭션 관리 선언적 트랜잭션 관리 : @Transactional 애노테이션 하나만 선언해서 매우 편리하게 트랜잭션을 적용하는 것

프로그래밍 방식 트랜잭션 관리 : 직접 트랜잭션 매니저 또는 트랜잭션 템플릿 등을 사용해서 트랜잭션 관련 코드를 직접 작성.

선언적 트랜잭션 관리가 훨씬 간편하고 실용적이여서…. 실무에서는 대부분 선언적으로 사용 프로그래밍 방식의 트랜잭션 관리는 테스트 시에 가끔 사용될 떄는 있다.

  • 정리 스프링이 제공하는 선언적 트랜잭션 관리 덕분에 순수한 비즈니스 로직을 만들었다. 개발자는 트랜잭션이 필요한 곳에 @Transactional을 쓰면 된다! 이 애노테이션 사용법은 뒤에 있다.

——————————————————— 스프링 부트의 자동 리소스 등록

생각해보면 데이터소스와 트랜잭션 매니저를 스프링 빈으로 직접 등록

스프링 부트는 데이터 소스를 스프링 빈에 자동으로 등록해준다. 자동으로 등록되는 스프링 빈 이름 : dataSource

  • 트랜잭션 매니저 - 자동 등록 스프링 부트는 적절한 트랜잭션 매니저를 자동으로 스프링 빈에 등록한다. 자동으로 등록되는 스프링 빈 이름 : traansactionManager

 생성자로 혹은 Autowired로 스프링부트가 제공하는 데이터 소스와 트랜잭션 매니저를 받을 수 있다. (속성은 applicatin.properties를 참고)

——————————————

자바 예외 처리

  • 예외 계층

자바 기본 예외에 대한 충분한 이해가 필요하다.

모든 자바 객체는 최상이 객체가 Object이다. 인셉션도 객체이기 떄문에 최상위는 Object. Throwable에서 Exception과 Error로 나뉨

Error : 메모리 부족이나 심각한 시스템 오류 같이 복구 불가능한 시스템 예외!! 심각한거 애플리케이션 개발자는 이 예외를 잡으려고 해서는 안된다.

상위 예외를 catch로 잡으면 하위 예외까지 포함된다. 따라서 애플리케이션 로직에서는 Throwable 예외도 잡으면 안된다. 위의 Error예외도 함께 잡을 수 있기 떄문이다. 애플리케이션 로직은 이러한 이유로 Exception부터 필요한 예외로 생각하고 잡으면 된다.

Exception : 체크 예외 애플리케이션 롲기에서 사용할 수 있는 실질적인 최상위 예외. Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외이다.

단 RuntimeException은 예외. RuntimeException : 언체크 예외, 런타임 예외

————————————— 예외 기본 규칙

예외는 폭탄 돌리기와 같다. 잡아서 처리하거나 처리할 수 없으면 밖으로 던져야 한다!!

예외를 처리하지 못하면 호출한 곳으로 예외를 계속 던진다.

  • 예외에 대한 기본 규칙 2가지
  1. 예외를 잡아서 처리하거나 던져야 한다.
  2. 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리된다. 예로 Exception을 catch로 잡으면 하위 예외들도 모두 잡을 수 있다. 예로 Exception을 throws로 던지면 하위 예외들도 모두 던질 수 있다.

참고 : 예외를 처리하지 못하고 계속 던지면?? 자바 main 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료 웹 애플리케이션인 경우 사용자의 요청을 처리하기 떄문에 하나의 예외 때문에 시스템이 종료되면 안된다. WAS가 해당 예외를 받아서 처리하는데 ….. 주로 사용자에게 개발자가 지정한 오류 페이지를 보여준다.