[코드로 배우는 스프링 웹 프로젝트 개정판] 5장 정리

관점 지향 프로그래밍(AOP)는 개발자들의 고민을 별도의 '관심사'로 분리하고, 핵심 비즈니스 로직만을 작성할 것을 권장한다. AOP는 코드 수정 없이 메서드의 실행 시간 기록, 잘못된 파라미터로 인한 예외 제어 등을 가능하게 한다. 또한, 트랜잭션 관리는 비즈니스에서 쪼개질 수 없는 하나의 단위 작업을 처리하며, ACID 원칙을 따른다. 데이터베이스 설계에서는 정규화를 통해 데이터 저장의 효율을 높이고, 필요에 따라 반정규화를 진행한다. 스프링에서는 @Transactional 어노테이션을 이용해 트랜잭션 처리를 한다.
DriedPollack's avatar
Mar 21, 2024
[코드로 배우는 스프링 웹 프로젝트 개정판] 5장 정리

🌼AOP라는 패러다임

💡핵심 키워드

  • AOP는 흔히 관점 지향 프로그래밍이라는 용어로 번역되는데, 이때 관점(Aspect)이란 개발자들에겐 관심사(Concren)라는 말로 통용된다. 관심사는 개발 시 필요한 고민이나 염두에 두어야 하는 일로, 주로 다음과 같다.
    • 파라미터가 올바르게 들어왔는가?
    • 이 작업을 하는 동안 사용자가 적절한 권한을 가진 사용자인가?
    • 이 작업에서 발생할 수 있는 모든 예외는 어떻게 처리해야 하는가?
  • AOP는 이러한 고민들을 별도의 관심사로 분리하고, 핵심 비즈니스 로직만을 작성할 것을 권장한다.
  • 이를 이용하면 작성된 모든 메서드의 실행 시간이 얼마인지를 기록하는 기능을 기존 코드의 수정 없이도 작성할 수 있고, 잘못된 파라미터가 들어와서 예외가 발생하는 상황을 기존 코드의 수정 없이도 제어할 수 있다.

AOP 용어들

  • Target은 개발자가 작성한 핵심 비즈니스 로직을 가지는 객체이다.
    • Target은 순수한 비즈니스 로직을 의미하고, 어떠한 관심사들과도 관계를 맺지 않는다.
  • Proxy는 Target을 전체적으로 감싸고 있는 존재다.
    • Proxy는 내부적으로 Target을 호출하지만, 중간에 필요한 관심사들을 거쳐서 Target을 호출하도록 작성된다.
  • JoinPoint는 Target 객체가 가진 여러 메서드다.
  • 외부의 호출은 proxy 객체를 통해서 Target 객체의 Joinpoint를 호출하는 방식으로 이해할 수 있다.
  • Pointcut은 관심사와 비즈니스 로직이 결합되는 지점이다.
    • Target에는 여러 메서드가 존재하기 때문에 어떤 메서드에 관심사를 결합할 것인지를 결정해야 하는데 이를 Pointcut이라고 한다.
  • 관심사는 Advice와 Aspect로 표현한다.
    • Aspect는 관심사 자체를 의미하는 추상적인 개념이다.
    • Advice는 실제 걱정거리를 분리해 놓은 코드다.
      • Target에 어떤 Advice를 적용할지는 어노테이션을 이용하는 방식이 있다.
      • 구분
        설명
        Before Advice
        Target의 Joinpoint를 호출하기 전에 실행되는 코드다. 코드의 실행 자체에는 관여할 수 없다.
        After Returning Advice
        모든 실행이 정상적으로 이루어진 후에 동작하는 코드다.
        After Throwing Advice
        예외가 발생한 뒤에 동작하는 코드다.
        After Advice
        정상적으로 실행되거나 예외가 발생했을 때 구분 없이 실행되는 코드다.
        Around Advice
        메서드의 실행 자체를 제어할 수 있는 가장 강력한 코드다. 직접 대상 메서드를 호출하고 결과나 예외를 처리할 수 있다.
  • PointCut은 Advice를 어떤 Joinpoint에 결합할 것인지를 결정하는 설정이다. Target은 결과적으로 Pointcut에 의해 자신에게는 없는 기능들을 가지게 된다.
    • 구분
      설명
      execution(@execution)
      메서드를 기준으로 Pointcut을 설정한다.
      within(@within)
      특정한 타입(클래스)을 기준으로 Pointcut을 설정한다.
      this
      주어진 인터페이스를 구현한 객체를 대상으호 Pointcut을 설정한다
      args(@args)
      특정한 파라미터를 가지는 대상들만을 Pointcut으로 설정한다.
      @annotation
      특정한 어노테이션이 적용된 대상들만을 Pointcut으로 설정한다.

AOP 실습

  • AOP 기능은 주로 일반적인 Java API를 이용하는 클래스(POJO-Plain Old Java Object)들에 적용한다. Controller에 적용이 불가능한 것은 아니지만, Controller의 경우 인터셉터나 필터 등을 이용한다.
  • 인터페이스를 구현한 클래스를 스프링에서 빈으로 사용할 수 있게 하기 위해서는 @Service 어노테이션을 추가해야 한다.
  • @Aspect는 해당 클래스의 객체가 Aspect를 구현한 것임으로 나타내기 위해서 사용한다.
  • @Component는 AOP와는 관계가 없지만 스프링에서 빈으로 사용하기 위해 사용한다.
  • Pointcut은 다음과 같이 작성한다.
    • @Before("execution(* org.zerock.service.SampleService*.*(..))")
    • execution...문자열은 AspectJ의 표현식이다. execution의 경우 접근제한자와 특정 클래스의 메서드를 지정할 수 있다.
    • 맨 앞의 *은 접근제한자를 의미하고, 맨 뒤의 *은 클래스의 이름과 메서드의 이름을 의미한다.

AOP 실습

  • root-context.xml의 네임스페이스에서 aopcontext를 추가한다.
  • <component-scan>을 이용해서 org.zerock.service 패키지와 org.zerock.aop 패키지를 스캔한다. 이 과정에서 @Service@Component 가 붙은 객체는 스프링의 빈으로 등록되고, <aop:aspectj-autoproxy>를 이용해서 설정했던 @Before가 동작된다.
    • <aop:aspectj-autoproxy> 가 정상적으로 동작한다면 작성해뒀던 클래스는 Proxy 클래스의 인스턴스가 된다.
  • @AfterThrowing은 지정된 대상이 예외를 발생한 후에 동작하면서 문제를 찾을 수 있도록 도와준다.
  • @Around와 ProceedingJointPoint를 결합해서 파라미터나 예외를 처리할 수 있다.
 

🌼스프링에서 트랜잭션 관리

💡핵심 키워드

  • 비즈니스에서는 쪼개질 수 없는 하나의 단위 작업을 말할 때 트랜잭션(Transaction)이라는 용어를 사용한다.
  • 트랜잭션의 성격은 ACID 원칙 으로 설명한다.
    • 원자성(Atomicity)
      하나의 트랜잭션은 모두 하나의 단위로 처리되어야 한다.어떤 작업이 잘못되는 경우 모든 것은 다시 원점으로 되돌아가야만 한다.
      일관성(Consistency)
      트랜잭션이 성공했다면 데이터베이스의 모든 데이터는 일관성을 유지해야만 한다. 트랜잭션으로 처리된 데이터와 일반 데이터 사이에는 전혀 차이가 없어야만 한다.
      격리(Isolation)
      트랜잭션으로 처리되는 중간에 외부에서의 간섭은 없어야만 한다.
      영속성(Durability)
      트랜잭션이 성공적으로 처리되면, 그 결과는 영속적으로 보관되어야 한다.

데이터베이스 설계와 트랜잭션

  • 데이터베이스의 저장 구조를 효율적으로 관리하기 위해서 정규화라는 작업을 한다. 정규화의 가장 기본은 중복된 데이터를 제거 해서 데이터 저장의 효율을 올리자는 것이다.
  • 정규화를 진행하면 테이블은 늘어나고, 각 테이블의 데이터 양은 줄어드는 것이 일반적이다.
  • 정규화를 진행하면서 원칙적으로 칼럼으로 처리되지 않는 데이터는 다음과 같다.
    • 시간이 흐르면 변경되는 데이터를 칼럼으로 기록하지 않는다.
      • 대표적으로 사용자의 생년월일의 경우 칼럼에 기록하지만, 현재 나이는 칼럼으로 유지하지 않는다. (만일 나이에 대한 연산이 너무 빈번한 경우에는 칼럼으로 설정할 수 있다.)
    • 계산이 가능한 데이터를 칼럼으로 기록하지 않는다.
      • 주문과 주문 상세가 별도의 테이블로 분리되어 있다면 사용자가 한 번에 몇 개의 상품을 주문했는지 등은 칼럼으로 기록하지 않는다. (집합 연산이 성능에 영향을 주는 경우에만 칼럼으로 고려한다.)
    • 누구에게나 정해진 값을 이용하는 경우 데이터베이스에서 취급하지 않는다.
      • 예를 들어 2018년 1월 1일은 월요일 이었고, 이 사실은 동일한 시간대를 사용하는 모든 사람들에세는 통용되기 때문에 데이터베이스에 기록하지 않는다.
  • 정규화가 잘 되었다면 트랜잭션이 많이 일어나지는 않는다. 정규화가 진행될수록 테이블은 점점 더 순수한 형태가 되어가는데, 순수한 형태가 되면 될수록 트랜잭션 처리의 대상에서 멀어진다.
    • 반대로 쿼리 등을 이용해서 필요한 데이터를 가져오는 입장에서는 점점 불편해진다. 현재 상황을 알기 위해서는 단순히 조회를 하는 것이 아니라 직접 조인이나 서브쿼리를 이용해서 처리해야 하기 때문이다.
  • 조인이나 서브쿼리를 이용하게 되면다시 성능의 이슈가 발생할 수 있다. 이러한 상황에서는 반정규화를 하게 된다.
    • 반정규화란 중복이나 계산되는 값을 데이터베이스 상에 보관하고, 대신에 조인이나 서브쿼리의 사용을 줄이는 방식이다.
      • 예를 들어 게시글의 목록 페이지에서 일반적으로 댓글의 숫자도 같이 표시하는 경우가 있다. 댓글을 추가한 뒤에 댓글의 숫자를 표시하려면 조인을 하거나 서브쿼리를 이용해서 처리하게 된다.
      • 이러한 상황에서는 게시글 테이블에 댓글의 숫자를 칼럼으로 처리하는 경우가 많다. 댓글의 숫자를 칼럼으로 처리하게 되면 게시물의 목록을 가져올 경우에는 댓글 테이블을 사용해야 하는 일이 없기 때문에 성능상으로 더 이득을 볼 수 있다.
  • @Transactional 어노테이션이 추가되면 트랜잭션 처리가 일어나서 트랜잭션 내의 한 테이블에 문제가 발생했을 경우, rollback()되는 것을 확인할 수 있다.
  • @Transactional 어노테이션은 몇 가지 중요한 속성을 가지고 있다.
    • 전파(Propagation) 속성
      • PROPAGATION _MANDATORY: 작업은 반드시 특정한 트랜잭션이 존재한 상태에서만 가능
      • PROPAGATION_NESTED: 기존에 트랜잭션이 있는 경우, 포함되어서 실행
      • PROPAGATION_NEVER: 트랜잭션 상황하에 실행되면 예외 발생
      • PROPAGATION_NOT_SUPPORTED: 트랜잭션이 있는 경우엔 트랜잭션이 끝날 때까지 보류된 후 실행
      • PROPAGATION_REQUIRED: 트랜잭션이 있으면 그 상황에서 실행, 없으면 새로운 트랜잭션 실행(기본 설정)
      • PROPAGATION_REQUIRED_NEW: 대상은 자신만의 고유한 트랜잭션으로 실행
      • PROPAGATION_SUPPORTS: 트랜잭션을 필요로 하지 않으나, 트랜잭션 상황하에 있다면 포함이 되어서 실행
    • 격리(Isolation) 레벨
      • DEAULT: DB 설정, 기본 격리 수준(기본 설정)
      • SERIALIZABLE: 가장 높은 격리, 성능 저하의 우려가 있음
      • READ_UNCOMMITED: 커밋되지 않은 데이터에 대한 읽기를 허용
      • READ_COMMITED: 커밋된 데이터에 대해 읽기 허용
      • REPEATABLE_READ: 동일 필드에 대해 다중 접근 시 모두 동일한 결과를 보장
    • Read-only 속성
      • true인 경우 insert, update, delete 실행 시 예외 발생. 기본 설정은 false
    • Rollback_for_예외
      • 특정 예외가 발생 시 강제로 Rollback
    • No-rollback-for-예외
      • 특정 예외의 발생 시에는 Rollback 처리되지 않음
  • @Transactional 어노테이션의 우선순위는 다음과 같다.
    • 메서드의 @Transactional 설정
    • 클래스의 @Transactional 설정
    • 인터페이스의 @Transactional 설정
    • 위의 규칙대로 적용되는 것을 기준으로 작성하자면 인터페이스에서는 가장 기준이 되는 @Transactional과 같은 설정을 지정하고, 클래스나 메서드에 필요한 어노테이션을 처리하는 것이 좋다.
 

🌼댓글과 댓글 수에 대한 처리

💡핵심 키워드

프로젝트 수정

  • MyBatis의 SQL을 처리하기 위해서는 기본적으로 하나의 파라미터 타입을 사용하기 때문에 2개 이상의 데이터를 전달하려면 @Param 어노테이션을 이용해서 처리할 수 있다.
 

🏁결론

해당 내용을 정리하면서 AOP를 적용해서 기존의 코드에 첨삭 없이, 메소드의 호출 이전 또는 이후에 필요한 로직을 수행하는 방법을 적용할 수 있었다.
또한 트랜잭션의 경우 두 개 이상의 작업이 같이 영향을 받는 경우에 필요하다는 것과, 어노테이션을 이용해서 트랜잭션이 처리된 결과를 만들어 낼 수 있다는 것을 알 수 있었다.
Share article

More articles

See more posts

👨🏻‍💻DriedPollack's Blog