[231011] <10> AoP, Mybatis 준비

MJ·2023년 10월 21일

수업 TIL🐣💚

목록 보기
60/68
post-thumbnail

1교시

  • 10_springjdbc_aop 프로젝트 생성
  • 로그

2교시

  • springjdbc 디펜던시 추가

3교시 aop

공통 로직을 여러번 작성하지 않고 한번만 작성해서 처리

예를 들어 detail, modify, delete 등 각 메소드마다 찍어줬던 로그를 aop로 한번만 작성해서 처리할 수 있음 (공통 로직이니까)

  • 우선 디펜던시가 필요. AspectJ Weaver 1.96 버전
  • aop 학습을 위해 알아야할 용어 세가지. 조인포인트, 포인트컷, 어드바이스
  1. AOP 관련 핵심 용어 3가지
    1) 조인포인트(JoinPoint) : AOP를 적용시킬 수 있는 메소드 전체 - 예시) 목록, 상세, 삽입, 수정, 삭제
    2) 포인트컷(PointCut) : 조인포인트 중에서 AOP를 동작시킬 메소드 - 예시) 삽입, 수정, 삭제
    3) 어드바이스(Advice) : 포인트컷에 동작시킬 AOP 동작 자체 - 예시) 트랜잭션
  1. 어드바이스 동작 시점

  2. 어드바이스 동작 순서
    @Around -> @Before -> @After
    --> around는 특별한 경우 아니면 안쓰는게 좋다. 이전에도 동작하고 이후에도 동작하기 땜에 이전과 이후에 모두 호출해야 하는 경우에 사용

  3. 표현식(Expression) 작성 방법

    1) 형식
    execution(반환타입 패키지.클래스.메소드(매개변수))

    2) 의미
    (1) 반환타입
    : 모든 반환타입
    ② void : void 반환타입
    ③ !void : void를 제외한 반환타입
    (2) 매개변수
    ① .. : 모든 매개변수
    : 1개의 모든 매개변수


BeforeAop

@Slf4j  // private static final Logger log = LoggerFactory.getLogger(BeforeAop.class);
@Aspect
@Component
public class BeforeAop {

  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() { }  // 이름만 제공하는 메소드(이름은 마음대로 본문도 필요 없다.)
  
  // 어드바이스 : 무슨 동작을 하는가?
  @Before("setPointCut()")
  public void beforeAdvice(JoinPoint joinPoint) {
    
    /*
     * Before 어드바이스
     * 1. 반환타입 : void
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : JoinPoint
     */
    
    /* 모든 컨트롤러의 모든 메소드가 동작하기 전에 요청(방식/주소/파라미터) 출력하기 */
        
    // 1. HttpServletRequest (그동안과 달리 받아오거나 선언되어 있는 상황 아니라서 리퀘스트 받아오지 못함. 그래서 구구절절 써준거)
    ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = servletRequestAttributes.getRequest();
    
    // 2. 요청 파라미터 -> Map 변환 (파라미터 종류가 다 다르므로)
    Map<String, String[]> map = request.getParameterMap();
    
    // 3. 요청 파라미터 출력 형태 만들기
    String params = "";
    if(map.isEmpty()) {
      params += "No Parameter";
    } else {
      for(Map.Entry<String, String[]> entry : map.entrySet()) {
        params += entry.getKey() + ":" + Arrays.toString(entry.getValue()) + " ";
      }
    }
    
    // 4. 로그 찍기 (치환 문자 {} 활용)
    log.info("{} {}", request.getMethod(), request.getRequestURI());  // 요청 방식, 요청 주소
    log.info("{}", params);                                           // 요청 파라미터
    
  }
  
}
  • aop 작업을 하는 aop 클래스를 만들땐 @Aspect를 붙여준다.
  • 포인트컷과 어드바이스로 언제 무슨 동작 할지 적어준다.
  • 해당 객체를 bean으로 만들어서 @EnableAspectJAutoProxy


현재 실행할 코드

로그찍혔다.
완성 코드 결과 아님. 완성 코드는 detail:6을 지우고 Before 어드바이스 동작으로 대체하게 된다. 지금은 before 어드바이스 타이밍 본 것.


4교시

AfterAoP

@Slf4j
@Aspect
@Component
public class AfterAop {

  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() { }
  
  // 어드바이스 : 무슨 동작을 하는가?
  @After("setPointCut()")
  public void afterAdvice(JoinPoint joinPoint) {
    
    /*
     * After 어드바이스
     * 1. 반환타입 : void
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : JoinPoint
     */
    
    // 로그 찍기
    log.info("==================================================================");
    
  }
  
}

5교시

AroundAoP

@Slf4j
@Aspect
@Component
public class AroundAop {

  // 포인트컷 : 언제 동작하는가?
  @Pointcut("execution(* com.gdu.app10.controller.*Controller.*(..))")
  public void setPointCut() { }
  
  // 어드바이스 : 무슨 동작을 하는가?
  @Around("setPointCut()")
  public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
    /*
     * Around 어드바이스
     * 1. 반환타입 : Object
     * 2. 메소드명 : 마음대로
     * 3. 매개변수 : ProceedingJoinPoint
     */
    
    log.info("==================================================================");      // 포인트컷 실행 이전에 실행(@Before 이전에 동작)
    
    Object obj = proceedingJoinPoint.proceed();                                          // 포인트컷이 실행되는 시점
    
    log.info("{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date()));  // 포인트컷 실행 이후에 실행(@After 이전에 동작)
    
    return obj;
    
  }
  
}

AppConfig

@EnableAspectJAutoProxy
@Configuration
public class AppConfig {

  // DataSource : CP(Connection Pool)을 처리하는 javax.sql.DataSource 인터페이스
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();  // DriverManagerDataSource : CP(Connection Pool)을 처리하는 스프링 클래스
    dataSource.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
    dataSource.setUrl("jdbc:log4jdbc:oracle:thin:@localhost:1521:xe");
    dataSource.setUsername("GD");
    dataSource.setPassword("1111");
    return dataSource;
  }
  
  // JdbcTemplate : Jdbc를 처리하는 스프링 클래스(Connection, PreparedStatement, ResultSet 처리 담당)
  @Bean
  public JdbcTemplate jdbcTemplate() {
    return new JdbcTemplate(dataSource());
  }

  // TransactionManager : 트랜잭션을 처리하는 스프링 인터페이스
  @Bean
  public TransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
  }

  /* 지금부터 AOP를 이용한 트랜잭션 처리를 위해 필요한 Bean */
  
  // TransactionInterceptor : 트랜잭션 처리를 위해 언제 rollback 할 것인지 정의하는 스프링 클래스
  @Bean
  public TransactionInterceptor transactionInterceptor() {
    
    // 규칙
    RuleBasedTransactionAttribute ruleBasedTransactionAttribute = new RuleBasedTransactionAttribute();
    ruleBasedTransactionAttribute.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
    
    MatchAlwaysTransactionAttributeSource matchAlwaysTransactionAttributeSource = new MatchAlwaysTransactionAttributeSource();
    matchAlwaysTransactionAttributeSource.setTransactionAttribute(ruleBasedTransactionAttribute);
    
    // 반환
    return new TransactionInterceptor(transactionManager(), matchAlwaysTransactionAttributeSource);
    
  }
  
  // Advisor : 트랜잭션 동작을 언제 할 것인지 결정하는 스프링 인터페이스
  @Bean
  public Advisor advisor() {
    
    // 포인트컷
    AspectJExpressionPointcut aspectJExpressionPointcut = new AspectJExpressionPointcut();
    aspectJExpressionPointcut.setExpression("execution(* com.gdu.app10.service.*Impl.*(..))");
    
    // 어드바이스 반환
    return new DefaultPointcutAdvisor(aspectJExpressionPointcut, transactionInterceptor());
    
  }
  
}

앞으로도 트랜잭션은 무조건 하겠다고 가정하고 이 세개의 빈은 항상 가지고 다니자

  // DataSource : CP(Connection Pool)을 처리하는 javax.sql.DataSource 인터페이스
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();  // DriverManagerDataSource : CP(Connection Pool)을 처리하는 스프링 클래스
    dataSource.setDriverClassName("net.sf.log4jdbc.sql.jdbcapi.DriverSpy");
    dataSource.setUrl("jdbc:log4jdbc:oracle:thin:@localhost:1521:xe");
    dataSource.setUsername("GD");
    dataSource.setPassword("1111");
    return dataSource;
  }
  
  // JdbcTemplate : Jdbc를 처리하는 스프링 클래스(Connection, PreparedStatement, ResultSet 처리 담당)
  @Bean
  public JdbcTemplate jdbcTemplate() {
    return new JdbcTemplate(dataSource());
  }

  // TransactionManager : 트랜잭션을 처리하는 스프링 인터페이스
  @Bean
  public TransactionManager transactionManager() {
    return new DataSourceTransactionManager(dataSource());
  }

6교시

  • afteraop, beforeaop, aroundaop 각각에 @Component를 붙여서

    이런식으로 AppConfig에서 따로 만들지 않아도 되게 대체해준다

appconfig 계속 하는중,,,

contactserviceImpl도 하는중,,,

contactserviceImpl

@RequiredArgsConstructor  // private final ContactDao contactDao;에 @Autowired를 하기 위한 코드이다.
@Service  // ContactService 타입의 객체(Bean)을 Spring Container에 저장한다.
public class ContactServiceImpl implements ContactService {

  private final ContactDao contactDao;
  
  @Override
  public int addContact(ContactDto contactDto) {
    int addResult = contactDao.insert(contactDto);
    return addResult;
  }

  @Override
  public int modifyContact(ContactDto contactDto) {
    int modifyResult = contactDao.update(contactDto);
    return modifyResult;
  }

  @Override
  public int deleteContact(int contact_no) {
    int deleteResult = contactDao.delete(contact_no);
    return deleteResult;
  }

  @Override
  public List<ContactDto> getContactList() {
    return contactDao.selectList();
  }

  @Override
  public ContactDto getContactByNo(int contact_no) {
    return contactDao.selectContactByNo(contact_no);
  }
  
  @Override
  public void txTest() {
    
    // AOP를 활용한 트랜잭션 처리 테스트 메소드
    
    // "성공1개+실패1개" DB처리를 동시에 수행했을 때 모두 실패로 되는지 확인하기
    
    // 성공
    contactDao.insert(new ContactDto(0, "이름", "전화번호", "이메일", "주소", null));
    
    // 실패
    contactDao.insert(new ContactDto());  // NAME 칼럼은 NOT NULL이므로 전달된 이름이 없으면 Exception이 발생한다.
    
  }

}

aop랑 트랜잭션 수업을 같이 해서 이제 트랜잭션 수업도 끝났다.
동작할 패키지만 바꿔주면 된다. 앞으로는..
단점이라면 트랜잭션 안돌아도 될 메소드 포함 모든 메소드에서 트랜잭션이 돌고 있다는 것. 이걸 구분할거면 안 돌 메소드, 돌 메소드 나눠서 트랜잭션 표시를 해주면 되는데(나중에 배움) 이럴거면 aop를 할 필요가 없ㄷ아


7교시 Mybatis

  • 본격적 마이바티스는 내일부터
  • 오늘은 준비
  • 11_mybatis_mockmvc
  • junit은 dao(db)만 테스트하는데 mockmvc는 컨트롤러(컨트롤러가 서비스를 부르고 서비스가 dao를 부르니까 전부 다 테스트 = 통합테스트)

mybatis 쓸 때 spring-jdbc도 가져다쓰고 부트에서 기본으로 채택한 커넥션 풀인 hikariCP (기존엔 Datasauce 썼었음) 이용할거임, 커넥션 얻어내는 애 -> 디펜던시 추가 작업 생략
hikariCP 3.4.5
mybatis 3.5.13
mybatis spring 2.0.7


0개의 댓글