AOP의 개념과 용어

 

- 공통 코드의 분리

 

코드 분리의 기준)

1. 관심사

2. 변하는 것, 변하지 않는 것

3. 공통 코드

 

공통 코드를 분리하여 관리하고, 필요한 메서드에 자동으로 추가되도록 한다.

(변경에 유리한 코드가 됨)

 

- 코드를 자동으로 추가한다면, 어디에 추가해야 할까?

 

1) 메서드의 맨 앞에 추가(Before advice) 

2) 메서드의 맨 뒤에 추가(After advice)

3) 코드의 맨 앞과 맨 뒤에 추가(Around advice)

 

메서드 중간에는 추가할 수 없다.

 

- AOP

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

 

부가 기능(advice)을 동적(실행중에)으로 추가해주는 기술

메서드의 시작 또는 끝에 자동으로 코드(advice)를 추가할 수 있다.

 

관련 용어)

target : advice가 추가될 객체

advice : target에 동적으로 추가될 부가 기능(코드)

join point : advice가 추가(join)될 대상(메서드)

pointcut : join point들을 정의한 패턴. ex) execution( * com.fastcampus.*.*(..))

proxy : target에 advice가 동적으로 추가되어 생성된 객체

weaving : target에 advice를 추가해서 proxy를 생성하는 것

 

 

target에는 핵심기능만 남겨놓고, 별도의 부가기능은 advice에 따로 나눠놨다가 실행중에 하나로 합쳐짐.

advice + target = proxy,

 

 

- Advice의 종류

종류 애너테이션 설명
around advice @Around 메서드의 시작과 끝 부분에 추가되는 부가 기능
before advice @Before 메서드의 시작 부분에 추가되는 부가 기능
after advice @After 메서드의 끝 부분에 추가되는 부가 기능
after returning @AfterReturning 예외가 발생하지 않았을 때, 실행되는 부가 기능
after throwing @AfterThrowing 예외가 발생했을 때, 실행되는 부가 기능

(Advice의 설정은 XML과 애너테이션, 두 가지 방법으로 가능)

 

 

- pointcut expression 

 

advice가 추가될 메서드를 지정하기 위한 패턴

@Component
@Aspect
public class LoggingAdvice {
    @Around("execution(* com.fastcampus.ch3.aop.*.*(..))")
    public Object methodCallLog(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println("<<[start] "
                + pjp.getSignature(),getName+Arrays.deepToString(pjp.getArgs()));

        Object result = pjp.proceed(); // 메서드 호출

        System.out.println("result = " + result);
        System.out.println("[end]>> "+ (System.currentTimeMillis() - start)+"ms");
        return result; // 메서드 호출결과 반환
	}
}

형식 ㅡ "execution(반환타입 패키지명.클래스명.메소드명(매개변수 목록))"

 

이 코드의 경우 반환타입 앞에 접근제어자가 생략되어있음. (원래 생략이 가능)

반환타입 : * (모든 반환타입이 가능하다는 의미)

패키지명 : com.fastcampus.ch3.aop

클래스명 : * (모든 클래스가 가능하다는 의미)

메서드명 : * (모든 메소드가 가능하다는 의미)

매개변수 목록 : .. (개수 상관없고 모든 타입이 가능하다는 의미)

 

advice의 리턴 값이 있는 경우 반환 타입은 Object 이어야 하며, 없으면 void로 해도 된다.

advice도 bean으로 등록해야 해서 @Component를 붙여야 하며 @Aspect도 붙여야 함.

 

사용한 매개변수에 관한 정보)

ProceedingJoinPoint pjp에는 메서드의 모든정보가 들어있음

pjp.getSignature().getName() : 메서드 이름

pjp.getArgs() : 매개변수들

 

 

 

서비스 계층의 분리와 @Transactional

 

- 서비스 계층(Layer)의 분리 - 비지니스 로직의 분리

 

PRESENTATION LAYER(표현 계층) : @Controller

BUSINESS LAYER(서비스 계층) : @Service

PERSISTENCE LAYER (영속계층) : @Repository

(세 애너테이션 모두 @Component를 포함하고 있어서 <component-scan>에 스캔이 됨)

 

Transaction을 처리하기에는 서비스 계층이 적합하다.(컨트롤러에 하면 너무 복잡해짐)

 

- TransactionManager

 

DAO의 각 메서드는 개별 Connection을 사용한다.

그런데 하나의 Tx로 묶으려면 같은 Connection을 사용해야 한다.

 

같은 Tx에 있는 명령들이 같은 Connection을 쓰게 해주는게 TransactionManager이다.

 

DAO에서 Connection을 얻거나 반환할 때 DataSourceUtils를 사용하게 해야 함.

(개별 Connection을 얻는 코드가 수정되어야 함) 

// conn = ds.getConnection();
conn = DataSourceUtils.getConnection(ds);

 

ex) TransactionManager로 Transaction 적용하기

public void insertWithTx() throws Exception {
        PlatformTransactionManager tm = new DataSourceTransactionManager(ds);
        TransactionStatus status = tm.getTransaction(new DefaultTransactionDefinition());
        // Tx 시작
        try {
            a1Dao.insert(1, 100);
            a1Dao.insert(1, 200);
            tm.commit(status); // Tx 끝 - 성공(커밋)
        } catch (Exception ex) {
            tm.rollback(status); // Tx 끝 - 실패(롤백)
        }
    }

 

- @Transactional로 Transaction적용하기

 

AOP를 이용한 핵심 기능과 부가 기능의 분리

@Transactional은 클래스나 인터페이스에도 붙일 수 있음(클래스와 인터페이스 내의 모든 메서드에 적용)

@Transactional
public void insertWithTx() throws Exception {
	a1Dao.insert(1,100);
	a1Dao.insert(1,200);
}

이렇게 핵심 기능만 따로 관리할 수 있게 된다.

 

 

 

 

Transaction을 적용하는 경우,

 

1) 예를 들어 중복된 Primary Key 값을 저장할 때(에러가 발생하는 경우)

같은 Connection이 사용되고 rollback이 발생하므로 어떤 데이터도 저장이 되지 않는다.

다만 여기서 @Transactional 애너테이션에 따로 설정을 하지 않으면 RuntimeException과 Error가 발생했을때만 rollback을 하고 다른 Exception이 발생하면 rollback이 되지 않는다.

따라서 @Transactional(rollbackFor = Exception.class) 과 같이 예외 클래스를 따로 지정해줘야 rollback이 잘 된다.

 

2) 중복된 Primary Key 값이 없을 때(에러가 발생하지 않는 경우)

같은 Connection이 사용되고 두 줄의 데이터가 저장이 된다.

 

 

- @Transactional의 속성

 

속성 설명
propagation Tx의 경계(boundary)를 설정하는 방법을 지정
isolation Tx의 isolation level을 지정. DEFAULT, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
readOnly Tx이 데이터를 읽기만 하는 경우, true로 지정하면 성능이 향상
rollbackFor 지정된 예외가 발생하면, Tx을 rollback
RuntimeException과 Error는 자동 rollback
noRollbackFor 지정된 예외가 발생해도, Tx을 rollback하지 않음
timeout 지정된 시간(초) 내에 Tx이 종료되지 않으면, Tx를 강제 종료

 

propagation 속성의 값)

설명
REQUIRED Tx이 진행중이면 참여하고, 없으면 새로운 TX 시작(디폴트)
REQUIRES_NEW Tx이 진행중이건 아니건, 새로 Tx 시작
NESTED Tx이 진행중이면 Tx의 내부 Tx를 만들어 실행
MANDATORY 반드시 진행중인 Tx내에서만 실행가능. 아니면 예외 발생
SUPPORTS Tx이 진행중이건 아니건 상관없이 실행
NOT_SUPPORTED Tx없이 처리. Tx이 진행중이면 잠시 중단(suspend)
NEVER Tx없이 처리. Tx이 진행중이면 예외 발생

 

 

REQUIRED와 REQUIRES_NEW)

 

(1) - REQUIRED

다른 Tx에 끼어들게 됨. 예외가 발생해서 rollback이 발생하면 먼저 존재했던 Tx의 처음으로 rollback이 됨.

 

(2) - REQUIRES_NEW

다른 Tx에 상관없이 새로운 Tx를 만들게 됨.


* @Transactional(REQUIRES_NEW) 을 적용할 때 주의할 점)

같은 클래스 안의 두 메서드는 Tx로 묶었을 때 connection이 의도한대로 묶이지 않고

에러가 발생해도 rollback이 제대로 되지 않음

@Transactional의 기본 설정(프록시 방식)이 같은 메서드에 있을때는 적용이 안 됨.

프록시 방식을 다른 방식으로 바꿔야 해결 된다.(자세한 내용은 학습 범위를 벗어나므로 생략...)


 

 

[Chapter 4]

MyBatis의 소개와 설정

 

- MyBatis란?

SQL Mapping Framework 로서 사용하기 쉽고 간단하다.

자바 코드로부터 SQL문을 분리해서 관리한다.

매개변수 설정과 쿼리 결과를 읽어오는 코드를 제거(MyBatis에서 알아서 해줌)

작성할 코드가 줄어서 생산성 향상 & 유지 보수에 편리하다.

 

 

- SqlSessionFactoryBean과 SqlSessionTemplate

 

SqlSessionFactory - SqlSession을 생성해서 제공

SqlSession - SQL 명령을 수행하는데 필요한 메소드 제공

(둘 다 인터페이스이며 mybatis에서 제공)

(root-context.xml에 두 인터페이스의 빈을 등록해야함)

 

SqlSessionFactoryBean - SqlSessionFactory를 Spring에서 사용하기 위한 빈 (mybatis-spring에서 제공)

SqlSessionTemplate - SQL 명령을 수행하는데 필요한 메소드 제공. thread-safe

(SqlSessionTemplate를 여러 Dao에서 공유해서 쓸 수 있음. 멀티쓰레드에 안전함)

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
   <property name="dataSource" ref="dataSource"/>
   <property name="configLocation"  value="classpath:mybatis-config.xml"/>
   <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
   <constructor-arg ref="sqlSessionFactory"/>
</bean>

Mapper.xml 파일은 SQL문이 들어있는 xml 문서이다.

 

(4번째 줄에서 *Mapper.xml 위치를 지정해줘야 함. *은 접두어를 의미)

 

 

- SqlSession의 주요 메서드

메서드 설명
int insert(String statement)
int insert(String statement, Object parameter)
insert문을 실행하고 insert된 행의 갯수를 반환
int delete(String statement)
int delete(String statement, Object parameter)
delete문을 실행하고 delete된 행의 갯수를 반환
int update(String statement)
int update(String statement, Object parameter)
update문을 실행하고 update된 행의 갯수를 반환 
T selectOne(String statement)
T selectOne(String statement, Object parameter)
하나의 행을 반환하는 select에 사용
parameter로 SQL에 binding될 값을 제공
List<E> selectList(String statement)
List<E> selectList(String statement, Object parameter)
여러행을 반환하는 select에 사용
parameter로 SQL에 binding될 값을 제공
Map<K, V> selectMap(String statement, String keyCol)
Map<K, V> selectMap(String statement, String keyCol, Object parameter)
여러행을 반환하는 select에 사용
keyCol에 Map의 Key로 사용할 컬럼을 지정

한 행(row)를 가져올 땐 selectOne을 사용하고

여러 행을 가져올 땐 selectList, selectMap 을 사용한다(보통 List를 많이 사용함)

 

Object parameter는 넘겨줄 값이 담긴 객체인데, User 객체에 담아서 주거나 Map에 담아서 주면 된다.

넘겨줄 값이 없으면 parameter 없이 줌.

 

 

- Mapper XML의 작성

 

 

[ boardMapper.xml ]

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.fastcampus.ch4.dao.BoardMapper">
 
    <delete id="deleteAll">
        DELETE FROM board
    </delete>

    <delete id="delete" parameterType="map">
        DELETE FROM board WHERE bno = #{bno} and writer = #{writer}
    </delete>

    <insert id="insert" parameterType="BoardDto">
        INSERT INTO board
            (title, content, writer)
        VALUES
            (#{title}, #{content}, #{writer})
    </insert>

    <select id="selectAll" resultType="BoardDto">
        SELECT bno, title, content, writer, view_cnt, comment_cnt, reg_date
        FROM board
        ORDER BY reg_date DESC, bno DESC
    </select>

    <select id="select" parameterType="int" resultType="BoardDto">
        <include refid="selectFromBoard"/>
        WHERE bno = #{bno}
    </select>

    <update id="update" parameterType="BoardDto">
        UPDATE board
        SET   title = #{title}
          , content = #{content}
          , up_date = now()
        WHERE bno = #{bno}
    </update>

</mapper>
public String getServerTime() throws Exception {
	return session.selectOne(namespace+"now");
} // T selectOne(String statement)

boardMapper.xml에 등록된, namespace가 일치하고 id="now" 인 sql문을 실행함.

 

boardMapper.xml에서

 

resultType은 반환 타입을 의미함.

반환 타입이 항상 일정하면 resultType을 생략할 수 있음.

(예를 들면 id="insert"의 경우, insert는 항상 int를 반환하므로 생략되는 것을 볼 수 있음)

 

parameterType은 입력으로 들어가는 값이 담긴 클래스 객체를 의미함.

해당 클래스의 getter 메서드를 이용하기 때문에, 클래스 객체를 주면 필요한 값들을 자동으로 입력하게 된다.

(예를 들어 update문을 보면, #{bno}는 parameter인 BoardDto의 getBno 메서드(getter)를 호출한다)  

 

id="select"의 경우 parameterType이 int(원래는 Integer인데 별명을 int로 기본 설정한 것)인데,

위 예제의 경우 게시물 번호를 의미하기 때문에 게시물 번호를 입력하면 BoardDto의 해당 select 값들을 얻을 수 있음.

 

 

- <typeAliases>로 이름 짧게 하기

(등록된 기본 별명에 관해 자세히 보려면 링크 참조)

https://mybatis.org/mybatis-3/configuration.html#typeAliases

 

mybatis – MyBatis 3 | Configuration

Configuration The MyBatis configuration contains settings and properties that have a dramatic effect on how MyBatis behaves. The high level structure of the document is as follows: configuration properties These are externalizable, substitutable properties

mybatis.org

 

mybatis-config.xml 파일에 다음과 같은 코드를 추가하여 커스텀 별명을 설정할 수 있다.

<typeAliases>
    <typeAlias alias="BoardDto" type="com.fastcampus.ch4.domain.BoardDto"/>
<typeAliases>

Integer를 int라는 별명으로 사용하듯이

별명을 직접 만들어서 사용할 수 있음.

별명은 대소문자 구별이 없음. 대문자나 소문자나 같음.

 

 

MyBatis로 DAO작성하기

 

- BoardDao의 작성 순서

1) DB테이블 생성

2) Mapper XML & DTO 작성

3) DAO 인터페이스 작성

4) DAO 인터페이스 구현 & 테스트

 

- DTO

Data Transfer Object.

계층간의 데이터를 주고 받기 위해 사용되는 객체이다.

DB에 데이터를 저장할 때 이 객체를 통해 데이터를 전달하고,

DB에서 데이터를 꺼내올 때 이 객체를 통해 데이터를 전달받는다.

 

- 각 계층의 역할

@Controller : 요청과 응답을 처리, 데이터 유효성 검증, 실행 흐름을 제어

@Service : 비지니스 로직 담당, 트랜잭션 처리

@Repository : 순수 Data Access 기능을 담당, [조회, 등록, 수정, 삭제]

 

- 예외 처리

Transaction에 관한 예외는 Service에서 처리하며,

나머지 예외는 보통 Controller에서 처리한다.

 

- Mapper.xml에서 #{}와 ${}의 차이

 

#{} 는 PreparedStatement를 쓰고 (VALUES의 ? 값에만 사용할 수 있음)

${} 는 Statement를 씀 (sql문 전체에 사용 가능)

 

- XML의 특수 문자 처리

XML내의 특수 문자 (<, >, &, ...)는 &lt; &gt;로 변환이 필요하다(태그로 오인받을 수 있기 때문에)

또는 특수문자가 포함된 쿼리를 <![CDATA[  내용  ]]> 으로 감싼다


 

출처 : 스프링의 정석 : 남궁성과 끝까지 간다

+ Recent posts