우당탕탕 개발일지

63일차_AOP & JDBC (방법 3가지) 본문

비트캠프/이론 및 정리

63일차_AOP & JDBC (방법 3가지)

ujin302 2024. 10. 4. 20:14
반응형

[ AOP(Aspect-Oriented Programming) ]

관점(Aspect) 지향 프로그래밍

: 관점을 기준으로 다양한 기능을 분리하여 보는 프로그래밍

 

* 관점(Aspect)이란?

: 부가 기능과 그 적용처를 정의하고 합쳐서 모듈로 만든 것

 

1. AOP의 목적

OOP(객체 지향 프로그래밍) 보완한 개념

OOP에서는 핵심 비즈니스 로직이든 부가 기능의 로직이든 하나의 객체로 분리하여 사용한다. 여기서 객체의 기능을 나눠 사용할 필요가 있다고 느껴 나온 것이 AOP이다. 

 

아래 사진과 같이 함수3개가 있다.

함수1과 함수2는 입실, 퇴실이라는 공통 부분이 존재한다. 이를 공통 코드라고 부른다.

또한, 수업과 카톡은 다른 부분이다. 이를 핵심 코드라고 부른다. 

예시로는 DAO에서 모든 함수에서 드라이버 연결 후, sql 문 실행, 드라이버 닫기를 반복한다.

여기서 핵심 로직은 sql문 실행이 되면 함수에 따라 다른 문장을 실행한다.

하지만 드라이버 연결 및 닫기는 부가 기능으로 모든 함수에서 반복적으로 사용한다.

AOP를 사용하면 이를 핵심 기능만 구현하면 된다. 

 

 

 

2. AOP 용어

1. Target : 부가기능을 부여할 대상, 핵심기능이 담긴 클래스

 

2. Advice : 부가기능을 담은 모듈

언제 공통 관심 기능을 핵심 로직에 적용할 지를 정의

  • Before : 조인 포인트 이전
  • After Returning : 조인 포인트 완료 후
  • After Throwing : 함수에서 예외를 던지는 경우
  • Around : 함수 호출 전후 

 

3. Joinpoint : Advice를 적용 가능한 지점을 의미

메소드 호출, 필드 값 변경 등

스프링의 프록시 AOP에서 조인 포인트는 메소드의 실행 단계뿐이다.

타깃 오브젝트가 구현한 인터페이스의 모든 메소드가 조인 포인트가 된다

 

4. Pointcut : 조인 포인트를 선별하는 기능을 정의한 모듈

가능한 조인 포인트들 중에 실제로 부가기능 적용할 것들을 선별한다. 즉, 핵심코드가 정의되어 있는 함수를 선별한다.

클래스를 선정하고, 그 안의 메소드를 선정하는 과정을 거친다 실제로 Advice가 적용되는 Joinpoint를 나타낸다

 

5. Proxy : 클라이언트타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트

클라이언트는 타깃을 요청하지만, 클라이언트에게는 DI를 통해 타깃 대신 프록시가 주입된다

클라이언트의 메소드 호출을 대신 받아서 타깃에게 위임하며, 그 과정에서 부가기능을 부여한다.

스프링 AOP는 프록시를 이용한다

 

6. Advisor : 어드바이스와 포인트컷을 하나로 묶어 취급한 것

AOP의 가장 기본이 되는 모듈이다.

스프링은 자동 프록시 생성기가 어드바이저 단위로 검색해서 AOP를 적용한다.

 

7. Aspect : 다수의 포인트컷과 어드바이스의 조합으로 만들어진다.

보통 싱글톤 형태의 오브젝트로 존재한다.

어드바이저는 아주 단순한 애스펙트라고 볼 수 있다

 

8. Weaving : Advice를 핵심로직코드에 적용하는 것을 Weaving라고 한다.

 

3. 환경설정 : pom.xml

aop를 사용하기 위해서는 pom.xml 파일에 dependency 를 적용시켜줘야 한다. 

 

 

 

[ AOP 예제 ]

예제1: XML

 

프로젝트명: Chapter03_XML

패키지명: sample01

 

 

 

 

 

 

 

 

1. Bean 등록

acQuickStart.xml

messageBeanImpl

  • 클래스: sample01.MessageBeanImpl 
  • setter 주입: str
  • 핵심 코드를 가지고 있는 클래스

loggingAdvice

  • 클래스: sample01.LoggingAdvice
  • 공통 코드를 가지고 있는 클래스
<bean id="messageBeanImpl" class="sample01.MessageBeanImpl">
    <property name="str" value="Have a nice day!!"/>
</bean>

<bean id="loggingAdvice" class="sample01.LoggingAdvice"></bean>

 

 

2. AOP 설정

acQuickStart.xml

<aop:pointcut/>

: 타켓 클래스의 함수 정의

  • execution(함수형태): 프로젝트가 실행될 때 적용
    • 함수형태: 범위 반환값 클래스경로.함수명()
      • 매개변수 있을 경우 >> 함수(..)
  • id: 개발자가 설정한 포인트컷의 이름

<aop:aspect/>

: 부가기능 함수 정의

  • ref: 부가 기능이 정의되어 있는 클래스

<aop:~~~/>

  • before : 공통 코드 -> 핵심 코드
  • after : 핵심 코드 -> 공통 코드 
  • around : 공통 코드 -> 핵심 코드 -> 공통 코드 
    • method : loggingAdvice 클래스에서 공통 코드가 정의되어 있는 함수
    • pointcut-ref : 핵심 코드가 정의되어 있는 함수, 즉 포인트컷을 의미함.

 

 

Before & After & Around

MessageBean.java

package sample01;

public interface MessageBean {
	public void showPrintBefore();
	public void viewPrintBefore();
	
	public void showPrintAfter();
	public void viewPrintAfter();
	
	public String showPrint();
	public void viewPrint();
	
	public void display();
}

 

MessageBeanImpl.java

핵심코드

Before

  • showPrintBefore()
  • viewPrintBefore()

After

  • showPrintAfter()
  • viewPrintAfter()

Around

  • showPrint()
  • viewPrint()

display() 함수는 일반 함수

package sample01;

import lombok.Setter;

@Setter
public class MessageBeanImpl implements MessageBean { // 타겟 클래스
	private String str;

	// join point & point cut
	@Override
	public void showPrintBefore() {
		System.out.println("showPrintBefore 메세지: " + str);
	}

	// join point & point cut
	@Override
	public void viewPrintBefore() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("viewPrintBefore 메세지: " + str);
	}
	
	@Override
	public void showPrintAfter() {
		System.out.println("showPrintAfter 메세지: " + str);
	}

	@Override
	public void viewPrintAfter() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("viewPrintAfter 메세지: " + str);
	}

	@Override
	public String showPrint() {
		System.out.println("showPrint 메세지: " + str);
		
		return "Spring";
	}
	
	@Override
	public void viewPrint() {
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("viewPrint 메세지: " + str);
	}
	
	// join point
	@Override
	public void display() {
		System.out.println("display 메세지: " + str);
	}

}

 

LoggingAdvice.java

부가기능

before

  • beforeTrace()

after 

  • afterTrace()

around

  • aroundTrace()

before, after 함수는 크게 특별한 것이 없이 xml 파일에서 정의한 것처럼 수행된다. 

 

하지만 around는 ProceedingJoinPoint 객체를 사용하여 핵심코드를 언제 호출할지 위치를 나타낸다. 

joinPoint.proceed() 함수를 사용하여 핵심코드를 호출한다. 

ProceedingJoinPoint  객체를 사용하면 핵심 코드의 시작 시간, 종료 시간도 알 수 있다. 

package sample01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;

public class LoggingAdvice {
	public void beforeTrace() {
		System.out.println("before Trace");
	}
	
	public void afterTrace() {
		System.out.println("after Trace");
	}
	
	public void aroundTrace(ProceedingJoinPoint joinPoint ) throws Throwable {
		System.out.println("1. around Trace");
		
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println("메소드: " + methodName);
		
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		Object ob = joinPoint.proceed(); // 핵심 코드 호출
		System.out.println("반환값: " + ob);
		
		stopWatch.stop();
		
		System.out.println("처리 시간: " + stopWatch.getTotalTimeMillis()/1000 + "초");
		System.out.println("2. around Trace");
		
	}
}

 

HelloSpring.java

함수 호출

package sample01;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloSpring {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("acQuickStart.xml");
		MessageBean messageBean = (MessageBean) context.getBean("messageBeanImpl");
		System.out.println("*** Before ***");
		messageBean.showPrintBefore();
		
		System.out.println();
		messageBean.viewPrintBefore();

		System.out.println();
		messageBean.display();
		
		
		System.out.println("\n");
		System.out.println("*** After ***");
		messageBean.showPrintAfter();
		
		System.out.println();
		messageBean.viewPrintAfter();
		
		System.out.println();
		messageBean.display();
		
		System.out.println("\n");
		System.out.println("*** Around ***");
		messageBean.showPrint();
		
		System.out.println();
		messageBean.viewPrint();
		
		System.out.println();
		messageBean.display();
	}

}

 

 

결과 화면

 

 

 

예제2: 어노테이션

프로젝트명: Chapter03_ANNO

패키지명: sample01

 

 

예제1에서는 xml 파일에 모든 것을 정의했다면 이번에는 어노테이션을 가지고 같은 결과를 나타내보겠다. 

예제1에서 어노테이션을 제외한 모든 코드가 동일하다. 

 

 

 

 

 

1. 환경설정 파일

acQuickStart.xml

<aop:aspectj-autoproxy />

  • aop를 사용하겠다고 알려줌

<context:component-scan/> 

  • 빈를 생성할 패키지를 등록

 

 

2. 공통 코드

LoggingAdvice.java

 @Aspect

  • 공통 코드가 정의되어 있는 함수가 있는 클래스를 의미함.

 

() : 핵심 코드 부분 정의 & 예제1에서 xml과 동일함.

@Before("execution(public void sample01.MessageBeanImpl.*Before())")

  • 공통 코드 -> 핵심 코드 

@After("execution(public void sample01.MessageBeanImpl.*After())")

  • 핵심 코드 -> 공통 코드

@Around("execution(public * sample01.MessageBeanImpl.*Print())")

  • 공통 코드 -> 핵심 코드 -> 공통 코드
package sample01;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

@Aspect // 공통 코드 
@Component
public class LoggingAdvice {
	@Before("execution(public void sample01.MessageBeanImpl.*Before())")
	public void beforeTrace() {
		System.out.println("before Trace");
	}
	
	@After("execution(public void sample01.MessageBeanImpl.*After())")
	public void afterTrace() {
		System.out.println("after Trace");
	}
	
	@Around("execution(public * sample01.MessageBeanImpl.*Print())")
	public void aroundTrace(ProceedingJoinPoint joinPoint ) throws Throwable {
		System.out.println("1. around Trace");
		
		String methodName = joinPoint.getSignature().toShortString();
		System.out.println("메소드: " + methodName);
		
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		
		Object ob = joinPoint.proceed(); // 핵심 코드 호출
		System.out.println("반환값: " + ob);
		
		stopWatch.stop();
		
		System.out.println("처리 시간: " + stopWatch.getTotalTimeMillis()/1000 + "초");
		System.out.println("2. around Trace");
		
	}
}

 

 

 

3. 예제3: Configuration

프로젝트명: Chapter03_ANNO

패키지명: sample01

 

 

예제2에서는 어노테이션 사용했고, 예제3에서는 Java 환경 설정 파일을 사용한다. 

예제1에서 어노테이션을 제외한 모든 코드가 동일하다. 

 

 

 

 

 

 

1. 환경설정 파일

acQuickStart.xml

컨피그 파일 등록

 

 

2. @Configuration

SpringConfiguration.java

@EnableAspectJAutoProxy

  • <aop:aspectj-autoproxy />와 같은 역할
  • 클라이언트 타깃 사이에 존재하면서 부가기능을 제공하는 오브젝트

 

Bean 등록

  • MessageBeanImpl
  • LoggingAdvice
package spring.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

import sample01.LoggingAdvice;
import sample01.MessageBeanImpl;

@Configuration
@EnableAspectJAutoProxy
public class SpringConfiguration {
	
	@Bean
	public LoggingAdvice loggingAdvice() {
		return new LoggingAdvice();
	}
	
	@Bean
	public MessageBeanImpl messageBeanImpl() {
		return new MessageBeanImpl();
	}
}

 

 

3. 공통 코드

LoggingAdvice.java

예제2와 동일

 

 

 

[ Spring & JDBC ]

스프링은 데이터베이스 연동을 위한 템플릿 클래스 제공

>> JDBC의 중복된 코드 줄임

 

스프링이 직접적으로 커넥션풀을 제공하진 않지만 DBCP(Jakarta Commons Database Connection Pool) API와 같은 커넥션 풀 라이브러리를 이용

 

환경 설정: pom.xml

 

 

[ JDBC 예제 ]

예제1:  JDBC &  XML

 

프로젝트명: Chapter04_XML

 

 

 

 

 

스프링이 직접적으로 커넥션풀을 제공 X

DBCP(Jakarta Commons Database Connection Pool) API와 같은 커넥션 풀 라이브러리를 이용

DBCP에는 풀링 기능을 제공하는 BasicDataSource 사용

 

 

 

 

 

 

1. JDBC 환경 설정

JDBC에 연결하는 방법 3가지

 

1. BasicDataSource 클래스의 setter 주입

 

 

2. p 사용

 

3. 변수화

 

 

2. 드라이버 연결

 

 

3. Bean 등록

applicationContext.xml

userDAOImpl

  • jdbc와 연동되는 jdbcTemplate 객체를 사용하여  Bean 생성
<bean id="userDAOImpl" class="user.dao.impl.UserDAOImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>

<bean id="userDTO" class="user.bean.UserDTO"></bean>
<bean id="helloSpring" class="user.main.HelloSpring"></bean>

<!-- service -->
<bean id="userInsertService" class="user.service.UserInsertService">
    <property name="userDTO" ref="userDTO"></property>
    <property name="userDAO" ref="userDAOImpl"></property>
</bean>
<bean id="userSelectService" class="user.service.UserSelectService">
    <property name="userDAO" ref="userDAOImpl"></property>
</bean>
<bean id="userUpdateService" class="user.service.UserUpdateService">
    <property name="userDTO" ref="userDTO"></property>
    <property name="userDAO" ref="userDAOImpl"></property>
</bean>

 

 

4. SQL문 수행

UserDAOImpl.java

JdbcTemplate

  • 해당 객체를 사용하여 PreparedStatement 열기 & 닫기 자동으로 해줌

이전 자바 프로젝트에서는 DAO에서 싱글톤 설정, 모든 함수에서 드라이버 연결 및 해지 등의 작업을 진행했다.

하지만 spring의 bean 특징을 사용하여 싱글톤 설정을 하지 않아도 된다.

또한 JdbcTemplate 객체를 사용하여 드라이버 연결 및 해지 작업을 자동으로 해준다.

 

반복되는 코드가 줄어 시간 절약이 되고 코드가 간결해졌다. 

package user.dao.impl;

import java.sql.PreparedStatement;
import java.util.List;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import com.mysql.cj.xdevapi.PreparableStatement;

import lombok.Setter;
import user.bean.UserDTO;
import user.dao.UserDAO;

public class UserDAOImpl implements UserDAO {
	@Setter
	private JdbcTemplate jdbcTemplate; // PreparedStatement 열기 & 닫기 자동으로 해줌
	String sql;
	
	@Override
	public void write(UserDTO userDTO) {
		sql = "insert into usertable values(?, ?, ?)";
		jdbcTemplate.update(sql, userDTO.getName(), userDTO.getId(), userDTO.getPwd());
	}
	
	@Override
	public List<UserDTO> getUserList() {
		sql = "select * from usertable";
		// new BeanPropertyRowMapper(UserDTO.class)
		// >> 컬럼과 userDTO의 필드변수와 자동매핑
		return jdbcTemplate.query(sql, new BeanPropertyRowMapper<UserDTO>(UserDTO.class));
	}
	
	@Override
	public boolean isFindId(String id) {
		sql = "select count(*) from usertable where id = ?";
		boolean isFind = false;
		int result = jdbcTemplate.queryForObject(sql, Integer.class, id);
		
		if(result==1) isFind = true;
		return isFind;
	}
	
	@Override
	public void isUpdate(UserDTO userDTO) {
		sql = "update usertable set name = ?, pwd= ? where id = ?";
		jdbcTemplate.update(sql, userDTO.getName(), userDTO.getPwd(), userDTO.getId());
	}
}

 

 

결과 화면

 

 

 

 

 

 

예제2:  JDBC & ANNO

 

프로젝트명: Chapter04_ANNO

* 예제1 프로젝트 복사하여 사용 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1. Bean 환경 설정

applicationContext.xml

 

SpringConfiguration.java

DB 정보 설정

 

 

 

2. Bean 등록

user.main

 

user.bean

 

user.service

@Service와 @Component와 동일하게 Bean 등록하는 역할

다만 해당 클래스의 역할을 한눈에 알아볼 수 있게 @Service 사용

 

user.dao.impl

@Repository와 @Component와 동일하게 Bean 등록하는 역할

다만 해당 클래스의 역할을 한눈에 알아볼 수 있게 @Repository사용

 

결과 화면

 

 

* JDBC 설정 방법 3가지

1.JdbcTemplate객체 변수 선언

앞의 예제에서 사용했던 방법

UserDAOImpl 클래스에서 JdbcTemplate 객체를 통해 JDBC와 연결 및 해제 

 

2. JdbcDaoSupport 클래스 상속

 

이번에는 JdbcTemplate 객체를 가지고 있는 JdbcDaoSupport 클래스를 상속받는다. 

 

JdbcDaoSupport 클래스를 살펴보면 setDataSource 함수를 통해서 dataSource를 설정할 수 있다.

setDataSource 함수가 final로 되어 있어 오버라이드는 불가능하다 따라서 부모 setDataSource 함수 사용하여야 한다.

xml에서 빈 등록 할 때, setter주입을 통해서 dataSource를 초기화한다. 

 

JdbcTemplate 객체를 사용할 때에는 JdbcDaoSupport 클래스의 getJdbcTemplate() 함수를 호출하여 사용한다. 

 

3. NamedParameterJdbcDaoSupport 클래스 상속

 

NamedParameterJdbcDaoSupport클래스 상속받아 JdbcTemplate 객체를 사용한다. 

해당 클래스를 상속받으면 getNamedParameterJdbcTemplate() 함수를 통해  JdbcTemplate 객체를 사용할 수 있다. 

getNamedParameterJdbcTemplate() 함수는 map의 Key 값을 통해 value를 가져와 매핑한다. 

 

NamedParameterJdbcDaoSupport 클래스는 JdbcDaoSupport 클래스 상속 받기 때문에 getJdbcTemplate() 함수도 사용 가능하다. 

반응형