AOP란 무엇일까?

AOP(Aspect-Oriented Programming)는 관점 지향 프로그래밍이라고 부른다. 너무 어려운데 풀어서 설명하면 프로그램의 핵심 로직과 공통 기능(예 : 로깅, 보안, 트랜잭션 관리)을 분리하여 작성할 수 있도록 도와주는 것이라고 보면 된다.

 

즉, 핵심 비즈니스 로직에 영향을 주지 않으면서도 여러 곳에서 반복되는 기능을 한 곳에 모아서 관리할 수 있게 해준다(너무 좋은것 아닌가?)

 

더보기
더보기

더 쉽게 예를 들어 학교에서 학생들이 공부하는 주요 수업(핵심 로직)이 있다고 해보자. 그런데, 시험 감독, 출석 체크, 학부모 연락 등과 같은 공통 작업도 있는데, 이 작업들은 모든 수업에 걸쳐 반복된다. 

AOP는 이처럼 "공통 작업(예 : 시험 감독)" 을 한 곳에서 관리하고, 각 수업(비즈니스 로직)에서는 신경 쓰지 않도록 분리하는 역할을 한다.

 

AOP의 주요 개념

  1.  Aspect (관점) : AOP의 핵심 모듈로, 여러 곳에 적용할 공통 기능을 모아둔 단위이다. 예를 들어, 로깅 Aspect는 애플리케이션 전반에 걸쳐 로그를 기록하는 기능을 한 곳에 모아서 관리한다.
  2.  Join Point (조인 포인트) : Aspect가 적용될 수 있는 지점을 의미한다. 메서드 호출, 예외 발생 등이 Join Point가 될 수 있다.
  3.  Pointcut (포인트컷) : 어떤 Join Poin에 Aspect(Advice)를 적용할 것인지 정하는 조건이다. "어떤 메서드가 호출될 때마다 로그를 남겨라"와 같은 조건을 정의한다.
  4.  Advice (어드바이스) : 실제로 수행되는 작업이다. 
  5.  Weaving (위빙) : Aspect를 실제 코드에 적용하는 과정이다. Srping AOP는 런타임(실행 시)에 동적으로 위빙을 수행하여, 개발자가 별도로 공통 기능 코드를 삽입하지 않아도 자동으로 적용된다.

 

Spring Boot 에서 Spring AOP 설정

별도의 XML 설정 없이, spring-boot-stater-aop 의존성을 추가하기만 하면 AOP 기능을 사용할 수 있다.

build.gradle 예시

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter'
}

 

 

간단한 로깅 Aspect 예시

메서드 실행 전후에 로그를 남기는 간단한 예시

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component

@Aspect
@Component
class LoggingAspect {
    private val logger = LoggerFactory.getLogger(this.javaClass)

    // 모든 Service 패키지 내의 모든 메서드 실행 시점에 적용
    @Around("execution(* com.example.service.*.*(..))")
    fun logAround(joinPoint: ProceedingJoinPoint): Any? {
        val methodName = joinPoint.signature.name
        
        // 로그 출력 예시: "메서드 시작: getUserById"
        logger.info("메서드 시작: $methodName")  // 예시 로그: [INFO] 2025-02-26 12:34:56 - 메서드 시작: getUserById
        
        try {
            val result = joinPoint.proceed() // 실제 메서드 실행
            
            // 로그 출력 예시: "메서드 종료: getUserById"
            logger.info("메서드 종료: $methodName")  // 예시 로그: [INFO] 2025-02-26 12:34:57 - 메서드 종료: getUserById
            
            return result
        } catch (e: Throwable) {
            // 로그 출력 예시: "메서드 오류: getUserById, 예외: NullPointerException"
            logger.error("메서드 오류: $methodName, 예외: ${e.message}")  // 예시 로그: [ERROR] 2025-02-26 12:34:57 - 메서드 오류: getUserById, 예외: NullPointerException
            throw e
        }
    }
}

 

  • @Aspect: 이 클래스가 Aspect임을 선언한다.
  • @Component: Spring의 빈으로 등록하여 자동으로 관리한다.
  • @Around: 지정한 Pointcut(여기서는 com.example.service 패키지의 모든 메서드)에 대해, 메서드 실행 전후로 Advice를 실행한다.
  • joinPoint.proceed(): 실제 메서드 호출을 진행하는 부분으로, 이 전후로 로그를 남겨 전체 실행 흐름을 모니터링한다.

이렇게 작성한 Aspect는 지정된 패키지 내의 모든 메서드 실행 시 자동으로 로그를 남긴다. 

 

서비스 클래스 예시

package com.example.service

import org.springframework.stereotype.Service

@Service
class UserService {
    fun getUserById(id: Long): User {
        // 사용자 조회 로직...
        return User(id, "Alice")
    }
}

 

이때 getUserById 메서드가 호출되면, Aspect의 joinPoint.signature.name은 "getUserById"를 반환하게 되고, 로그에도 "메서드 시작: getUserById"와 같이 출력된다.

 

하지만 이런 AOP를 사용시 주의할 점이 있다.

 

  • 과도한 사용 주의 : 너무 많이 사용하면 코드의 흐름을 파악하기 어려울 수 있다.
  • 디버깅 어려움 : AOP에 의해 동적으로 적용된 로직은 디버깅 시 실제 코드 흐름을 파악하기 어렵게 만들 수 있다.
  • 성능 고려 : Aspect가 많은 경우, 메서드 호출 전후에 추가적인 처리 시간이 발생할 수 있으니 성능에 미치는 영향을 고려해야한다.

 

 

일을 하다 보면 데이터가 중복으로 들어가 있는 테이블에서 특정 조건을 만족하는 데이터만 조회하는 경우가 정말 많다. 

매번 찾아보기도 힘들고 한번 알아두면 누구보다 빠르게 쿼리를 작성할 수 있기 때문에 처음이자 마지막으로 정리를 하려고 한다. 

 

 

우선 GROUP BY는 각종 집계함수, 그룹함수와 함께 쓰이며 그룹화된 정보를 제공한다. 데이터를 그룹으로 나누어, 그룹별로 집계된 정보를 출력하고 비교할 때 GROUP BY 가 사용된다.

 

GROUP BY로 그룹화된 결과에 대해 조건을 걸 수 있는데 이것이 HAVING 절이다. 그룹별로 적용할 수 있는 함수 및 연산자를 사용할 수 있다.

 

1. HAVING에서 사용할 수 있는 연산자

연산자 설명 예제
= 특정 값과 같은 그룹 선택 HAVING COUNT(*) = 10
!=, <> 특정 값과 다른 그룹 선택 HAVING COUNT(*) <> 5
>, <, >=, <= 특정 값보다 크거나 작은 그룹 선택 HAVING SUM(salary) > 50000
IN 특정 값 목록에 포함된 그룹 선택 HAVING department IN ('HR', 'IT')
NOT IN 특정 값 목록에 포함되지 않은 그룹 선택 HAVING department NOT IN ('HR', 'IT')
BETWEEN 값의 범위를 지정 HAVING SUM(sales) BETWEEN 10000 AND 50000
LIKE 특정 패턴과 일치하는 그룹 선택 HAVING department LIKE 'Tech%'
IS NULL, IS NOT NULL 그룹 내 집계 결과가 NULL인지 확인 HAVING AVG(salary) IS NOT NULL

 

 

2. HAVING에서 사용할 수 있는 집계 함수

HAVING 절에서는 GROUP BY로 묶인 그룹에 대한 집계 함수(Aggregate Function)를 사용할 수 있다.

집계 함수 설명 예제
COUNT(*) 그룹 내 행 개수 HAVING COUNT(*) > 5
COUNT(column) 특정 컬럼 값이 NULL이 아닌 행 개수 HAVING COUNT(department) > 10
SUM(column) 그룹 내 합계 HAVING SUM(sales) > 50000
AVG(column) 그룹 내 평균 HAVING AVG(salary) >= 40000
MIN(column) 그룹 내 최소값 HAVING MIN(salary) > 20000
MAX(column) 그룹 내 최대값 HAVING MAX(salary) < 100000
STDDEV(column) 그룹 내 표준편차 HAVING STDDEV(salary) > 10000
VARIANCE(column) 그룹 내 분산 HAVING VARIANCE(salary) > 5000
MEDIAN(column) 그룹 내 중앙값 (Oracle 전용) HAVING MEDIAN(salary) > 45000

 

 

3. HAVING에서 여러 조건을 조합하는 논리 연산자

여러 조건을 AND, OR, NOT 연산자로 조합할 수 있다.

연산자 설명 예제
AND 여러 조건을 모두 만족해야 함 HAVING COUNT(*) > 5 AND SUM(sales) > 10000
OR 하나라도 만족하면 선택 HAVING COUNT(*) > 5 OR SUM(sales) > 10000
NOT 특정 조건을 제외 HAVING NOT COUNT(*) > 10

 

 

 

4. HAVING 절 예제

 

4-1. COUNT() 사용하여 특정 개수 이상인 그룹만 조회

SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;

 

* 각 부서별 직원 수가 5명 이상인 부서만 조회

 

 

4-2. SUM() 사용하여 총 매출이 50,000 이상인 그룹만 조회

SELECT region, SUM(sales) AS total_sales
FROM sales_data
GROUP BY region
HAVING SUM(sales) >= 50000;

 

* 각 지역(region)별 총 매출이 50,000 이상인 그룹만 조회

 

 

4-3. AVG() + MAX() 사용

SELECT department, AVG(salary) AS avg_salary, MAX(salary) AS max_salary
FROM employees
GROUP BY department
HAVING AVG(salary) > 40000 AND MAX(salary) < 100000;

* 부서별 평균 급여가 40,000 이상이고, 최대 급여가 100,000 미만인 부서만 조회

 

 

4-4. COUNT(DISTINCT column) 사용하여 중복 제거 후 개수 비교

SELECT category, COUNT(DISTINCT product_id) AS unique_products
FROM products
GROUP BY category
HAVING COUNT(DISTINCT product_id) > 10;

* 카테고리별 고유한 상품 개수가 10개 이상인 경우만 조회

 

 

4-5. HAVING + ORDER BY 함께 사용

SELECT department, COUNT(*) AS employee_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5
ORDER BY employee_count DESC;

* 직원 수가 5명 이상인 부서를 조회하면서, 직원 수가 많은 순으로 정렬

 

 

 

COUNT() 함수

 

오라클에서 COUNT() 함수는 특정 컬럼 또는 표현식의 개수를 세는 집계함수이다.

HAVING COUNT(*) 에서 괄호 안 * 에 들어갈 수  있는 요소를 정리해보자.

 

1. COUNT(*) (전체 행 개수)

SELECT department, COUNT(*) AS total_count
FROM employees
GROUP BY department
HAVING COUNT(*) > 5;

* 각 부서별 총 직원 수가 5명 이상인 부서만 조회

* NULL 값을 포함한 모든 행을 개수로 셈

 

 

2. 특정 컬럼 값 개수( COUNT(column) )

SELECT department, COUNT(salary) AS non_null_salaries
FROM employees
GROUP BY department
HAVING COUNT(salary) > 5;

* 부서별 salary가 NULL이 아닌 행의 개수를 카운트

* NULL 값은 개수에서 제외된다!!!!!!

 

 

3. DISTINCT을 사용하여 중복 제거한 개수( COUNT(DISTINCT column) )

SELECT department, COUNT(DISTINCT job_title) AS unique_jobs
FROM employees
GROUP BY department
HAVING COUNT(DISTINCT job_title) > 3;

* 부서별 중복되지 않은 직무(job_title) 개수가 3개 이상인 부서만 조회

* NULL 값은 개수에서 제외된다.

 

 

4. 특정 조건을 만족하는 값 개수 ( COUNT(CASE WHEN ...) ) 

SELECT department, 
       COUNT(CASE WHEN salary > 50000 THEN 1 END) AS high_salary_count
FROM employees
GROUP BY department
HAVING COUNT(CASE WHEN salary > 50000 THEN 1 END) > 3;

* 급여(salary)가 50,000 이상인 직원 수가 3명 이상인 부서만 조회

* 조건을 만족하지 않는 행은 개수에서 제외됨

 

 

5. 다중 도건 사용 ( COUNT(CASE WHEN ...) 여러개 ) ** 이걸 쓸 일이 많다.

SELECT department, 
       COUNT(CASE WHEN salary > 50000 THEN 1 END) AS high_salary_count,
       COUNT(CASE WHEN job_title = 'Manager' THEN 1 END) AS manager_count
FROM employees
GROUP BY department
HAVING COUNT(CASE WHEN salary > 50000 THEN 1 END) > 3
   AND COUNT(CASE WHEN job_title = 'Manager' THEN 1 END) > 2;

* 급여 50,000 이상 직원 수가 3명 이상이고, 매니저가 2명 이상인 부서만 조회

 

 

6. NULL 값 포함 여부 체크 ( COUNT(NULLIF(column, 조건)) )

SELECT department, COUNT(NULLIF(salary, 0)) AS non_zero_salaries
FROM employees
GROUP BY department
HAVING COUNT(NULLIF(salary, 0)) > 5;

* 급여가 0이 아닌 직원 수가 5명 이상인 부서만 조회

* NULLIF(salary, 0)는 salary가 0이면 NULL 로 변환하여 COUNT 에서 제외시킨다.

 

 

7. 조합 사용 (COUNT + SUM/AVG 등)

 SELECT department, COUNT(*) AS total_employees, SUM(salary) AS total_salary
FROM employees
GROUP BY department
HAVING COUNT(*) > 10 AND SUM(salary) > 500000;

* 직원 수가 10명 이상이고, 급여 총합이 500,000 이상인 부서만 조회

 

 

외울건 아니지만 이런 경우에 이런걸 쓰는구나~ 라는걸 알고 있으면 쿼리를 짤때 도움이 많이 될 것 같다.

 

Spring Boot는 빠르게 프로토타입을 만글고, 반복적인 설정 작업 없이 비즈니스 로직에 집중할 수 있게

Auto Configuration (자동 구성) 기능을 제공한다.

 

한번 Spring Boot Auto Configuration 기본 개념과 동작 원리, 활용예제에 대해서 알아보자.

 

Auto Configuration이란 무엇인가?

Auto Configuration은 개발자가 매번 복잡한 설정 파일이나 보일러플레이트 코드를 작성하지 않아도, 애플리케이션이 실행될 때 필요한 설정을 자동으로 구성해주는 기능이다. Spring Boot는 클래스패스에 포함된 라이브러리와 프로젝트 설정을 기반으로, 애플리케이션에 필요한(Bean)들을 자동으로 등록한다.

더보기

비유를 들어보자면 새로운 스마트폰을 구입했다고 생각해보자, 별도의 복잡한 설정 없이 기본 앱들과 기능들이 이미 준비되어 있는 상태일것이다. Spring Boot 의 Auto Configuration은 바로 그런 스마트폰과 같이, 개발자가 직접 설정하지 않아도 "기본값"으로 모든 것이 준비되도록 도와준다.

 

왜 Auto Configuration이 필요할까?

전통적인 Spring 애플리케이션은 XML이나 자바 기반의 설정 파일을 통해 수많은 설정을 해야 한다.( 정말 정말 복잡하고 귀찮은 작업이다..) 이러한 작업은 시간도 오래걸리고, 실수로 인한 오류가 발생하기 쉽다.

그렇기 때문에  Spring Boot 의 Auto Configuration 은 장점을 갖는다.

 

  • 개발 속도 향상: 복잡한 설정을 자동으로 처리하여 개발자가 비즈니스 로직에 집중할 수 있다.
  • 일관성 있는 설정: 기본적인 설정이 표준화되어 있어, 팀 내에서 일관된 개발 환경을 유지할 수 있다.
  • 쉬운 시작: 처음 프로젝트를 시작할 때 최소한의 설정만으로도 애플리케이션을 실행할 수 있다.

 

Auto Configuration의 동작 원리

Spring Boot의 Auto Configuration은 내부적으로 여러 가지 메커니즘과 어노테이션을 사용해 동작한다.

 

@EnableAutoConfiguration / @SpringBootApplication

Spring Boot 애플리케이션의 진입점에는 보통 @SpringBootApplication 어노테이션이 붙는다. 이 어노테이션은 

@EnableAutoConfiguration, @ComponentScan, @Configuration 등 여러 어노테이션을 합친 축약 표현이다.

@SpringBootApplication
class MyApplication

fun main(args: Array<String>) {
    runApplication<MyApplication>(*args)
}

 

@EnableAutoConfiguration : 해당 어노테이션은 Spring Boot에게 애플리케이션 시작 시 자동 구성을 활성화하라고 지시한다.

 

 

Auto Configuration은 클래스패스에 있는 라이브러리와 설정 파일을 확인한 후, 필요한 빈들을 등록한다.

이를 위해 조건부(Conditional) 어노테이션들이 사용된다. 대표적으로 @ConditionalOnClass, @ConditionalOnMissingBean, @ConditionalOnProperty 등이 있다.

 

예를 들어, H2 데이터베이스 라이브러리가 클래스패스에 있다면, Spring Boot는 자동으로 H2 데이터베이스 관련 빈을 등록한다.

만약 사용자가 직접 데이터 소스(DataSource)를 정의했다면, @ConditionalOnMissingBean 어노테이션 덕분에 자동 구성은 이를 무시하고 사용자가 정의한 설정을 우선시한다.

@Configuration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean
    public DataSource dataSource() {
        // 기본 H2 데이터베이스 설정을 반환
        return new EmbeddedDatabaseBuilder()
                    .setType(EmbeddedDatabaseType.H2)
                    .build();
    }
}

 

 

 

Auto Configuration 파일의 구조

Spring Boot의 Auto Configuration 설정은 보통 spring.factories 파일에 정의되어 있다. 이 파일은 각 라이브러리별로 어떤 Auto Configuration 클래스를 적용할지 명시해 두며, 애플리케이션이 시작될 때 해당 클래스들이 로드된다.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.autoconfig.DataSourceAutoConfiguration,\
com.example.autoconfig.WebMvcAutoConfiguration

 

 

이렇게 장점만 있을것 같은 Auto Configuration도 한계가 있다.

 

  • 숨겨진 동작:
    자동으로 이루어지는 설정들이 때로는 개발자가 의도하지 않은 방식으로 동작할 수 있으므로, 내부 동작을 잘 이해해야 한다.
  • 디버깅의 어려움:
    Auto Configuration이 복잡하게 작동하는 경우, 문제가 발생하면 원인을 파악하기 어려울 수 있다.
  • 학습 곡선:
    초보자가 Auto Configuration의 내부 메커니즘을 완벽하게 이해하기까지는 시간이 걸릴 수 있다.

이러한 한계에도 불구하고, Auto Configuration은 대부분의 애플리케이션 개발에 있어 생산성과 유지보수성을 크게 향상시키는 도구임에는 분명하다.

+ Recent posts