프로그래밍 관점에서 예외처리는 아주 중요하다고 생각합니다. 예외 처리가 안된 부분은 서버의 무리가 생기게 되고 공격의 취약점이 될 수 있습니다. 자바에서 대표적으로 예외 처리를 하는 방법에는 try-catch 문을 이용하여 적용하는 방법이 있습니다. 하지만 오늘 해볼 내용은 스프링 관점에서 예외처리를 할 예정입니다.
스프링 관점에서 예외처리를 한다고 생각해봅시다. 스프링 프로젝트 중 MVC 패턴 등 쿼리를 이용하여 데이터를 처리하고 여러가지 트렌젝션도 처리된다고 생각해봅시다. 트렌젝션을 처리하는 도중 에러가 발생한다면 Exception 구문으로 들어가게 되는데, 해당 Exception이 롤백을 지원하게 되는지, 안하는지에 따라서 데이터의 손실과 저장 유무가 달라지게 되는 것 입니다.
본 내용에서는 ExceptionHandler에 생성에 대한 부분은 포함되지 않았습니다. 포함된 내용을 보시려면 https://junhokims.tistory.com/58 을 참고해주세요
# Contents
- Transaction 구성 및 적용
- 사용법
# Transaction 구성 및 적용
저는 JNDI 에서 트랜잭션을 설정하였습니다. 트랜잭션 처리를 위한 설정을 하는 방법은 개발 환경에 따라 코드가 조금씩 다르게 적용됩니다. JNDI 의 경우에는 아래와 같은 xml 을 이용하여 dataSource에 TransactionManeger를 등록하는 방안이며, 참고만 해주시기 바랍니다.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd ">
<tx:annotation-driven proxy-target-class="true"/>
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="jdbc/JUNHO_DB"></property>
<property name="resourceRef" value="true"></property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="mapperLocations" value="classpath:query/*.xml"></property>
</bean>
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
트랜잭션의 애노테이션 구문은 아래와 같습니다.
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=false, rollbackFor=Exception.class)
아래 내용은 https://taetaetae.github.io/2016/10/08/20161008/ 을 참고하였습니다.
propagation (전파옵션)
- REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성
- REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성
- SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행
- MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생
- NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지
- NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생
- NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있음. 둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동
isolation(전파옵션)
격리수준이라는 옵션이다. 트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 말하는데 옵션은 다음과 같다.
READ_UNCOMMITTED (level 0)
- 트랜잭션에 처리중인 혹은 아직 커밋되지 않은 데이터를 다른 트랜잭션이 읽는 것을 허용
- 어떤 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 B라는 아직 완료되지 않은(Uncommitted 혹은 Dirty) 데이터 B를 읽을 수 있다.Dirty read : 위와 같이 다른 트랜잭션에서 처리하는 작업이 완료되지 않았는데도 다른 트랜잭션에서 볼 수 있는 현상을 dirty read 라고 하며, READ UNCOMMITTED 격리수준에서만 일어나는 현상
READ_COMMITTED (level 1)
- dirty read 방지 : 트랜잭션이 커밋되어 확정된 데이터만을 읽는 것을 허용
- 어떠한 사용자가 A라는 데이터를 B라는 데이터로 변경하는 동안 다른 사용자는 해당 데이터에 접근할 수 없다.
REPEATABLE_READ (level 2)
- 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정이 불가능하다.
- 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신하거나 삭제하는 것을 불허함으로써 같은 데이터를 두 번 쿼리했을 때 일관성 있는 결과를 리턴함
SERIALIZABLE (level 3)
- 완벽한 읽기 일관성 모드를 제공
- 데이터의 일관성 및 동시성을 위해 MVCC(Multi Version Concurrency Control)을 사용하지 않음(MVCC는 다중 사용자 데이터베이스 성능을 위한 기술로 데이터 조회 시 LOCK을 사용하지 않고 데이터의 버전을 관리해 데이터의 일관성 및 동시성을 높이는 기술)
- 트랜잭션이 완료될 때까지 SELECT 문장이 사용하는 모든 데이터에 shared lock이 걸리므로 다른 사용자는 그 영역에 해당되는 데이터에 대한 수정 및 입력이 불가능하다.
no-rollback-for - 예외처리 (기본값 : 없음)
- 특정 예외가 발생하더라도 롤백되지 않도록 설정
위의 내용을 참고하여 아래 테스트를 해보았습니다.
# 사용법
먼저 롤백이 없을 경우의 데이터를 insert 해보도록 하겠습니다. 여러분들은 임의의 테스트 코드를 직접 만드셔서 저처럼 해보시기 바랍니다. 시작해보도록 하겠습니다.
아래 코드는 컨트롤러입니다.
아래 코드를 참고만 해주시고, 여러분의 테스트 코드를 직접 작성해보세요.
@RequestMapping(value = "/test3", method = RequestMethod.GET)
public ModelAndView home3(HttpServletRequest request, HttpServletResponse response, @RequestParam("rollback") String param, @RequestBody(required = false) String responsebody) throws JsonGenerationException, JsonMappingException, IOException {
logger.info("Param value : " + param);
MemberVO member = new MemberVO();
member.setId("test1");
member.setPassword("1234");
if (memberService.insertMember(member, param) == 0) {
response.getWriter().write("success");
}else {
response.getWriter().write("fail");
}
return null;
}
아래 코드는 서비스입니다.
아래 코드를 참고만 해주시고, 여러분의 테스트 코드를 직접 작성해보세요.
@Transactional(propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, readOnly=false, rollbackFor=Exception.class)
public int insertMember(MemberVO member, String rollbackYN) {
memberDAO.insert(member);
if (rollbackYN.equals("Y") == true) {
throw new TestException();
}
return 0;
}
아래 코드는 DAO입니다.
아래 코드를 참고만 해주시고, 여러분의 테스트 코드를 직접 작성해보세요.
@Repository
public class MemberDAO {
@Autowired protected SqlSession sqlSession;
public List<MemberVO> select() {
return sqlSession.selectList("member.selectMember");
}
public int insert(MemberVO member) {
return sqlSession.insert("member.insertMember", member);
}
}
아래 코드는 Mybatis query입니다.
아래 코드를 참고만 해주시고, 여러분의 테스트 코드를 직접 작성해보세요.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.rog/dtd/mybatis-3-mapper.dtd">
<mapper namespace="member">
<select id="selectMember" resultType="com.mycom.myapp.vo.MemberVO">
SELECT *
FROM MEMBER
</select>
<insert id="insertMember" parameterType="com.mycom.myapp.vo.MemberVO">
INSERT INTO MEMBER(ID, PASSWORD) VALUES (#{id}, #{password})
</insert>
</mapper>
테스트 URI는 아래와 같습니다.
주소 창 : http://localhost/myapp/test3?rollback=Y 롤백
주소 창 : http://localhost/myapp/test3?rollback=N 롤백하지 않음
'오픈소스 > 스프링' 카테고리의 다른 글
[Spring] 스프링 빌드 시 Nexus 레파지토리 저장 - 메이븐과 연계 (0) | 2021.10.21 |
---|---|
[Spring] Nexus Role 생성, User 생성, Repository 생성 (0) | 2021.10.20 |
[Spring] ExceptionHandler 적용 및 사용법 (0) | 2021.10.13 |
[Spring] Cannot load JDBC driver class 'oracle.jdbc.OracleDriver' (1) | 2021.10.13 |
[Spring] Mybatis 사용 중 에러 해결 -TroubleShooting (0) | 2021.10.13 |