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내의 특수 문자 (<, >, &, ...)는 < >로 변환이 필요하다(태그로 오인받을 수 있기 때문에)
또는 특수문자가 포함된 쿼리를 <![CDATA[ 내용 ]]> 으로 감싼다
출처 : 스프링의 정석 : 남궁성과 끝까지 간다
'Spring & SpringBoot > Spring 복습' 카테고리의 다른 글
[Chapter 2] Spring MVC (1강~8강) (0) | 2022.07.26 |
---|---|
[Chapter 4] 3강~4강 (0) | 2022.07.13 |
[Chapter 3] Spring DI와 AOP 9강 ~ 17강 (0) | 2022.07.09 |
[Chapter 3] Spring DI와 AOP 1강~8강 (0) | 2022.07.08 |
[Chapter 2] Spring MVC 27강 ~ 35강 (0) | 2022.07.07 |