Full Stack Web Developer.
Syaku (샤쿠)

Java, JS (ES6+), Spring, Spring security, jQuery, Reactjs, Bootstrap.

        

07-12 08:23


Spring Boot 2 보기 Front-end 보기 DevOps 보기 Spring 3 보기 Spring Security 3 보기

#5 스프링 트랜잭션 - 스프링 프레임워크 게시판 : Spring Framework Transaction

written by Seok Kyun. Choi. 최석균


스프링 프레임워크 연재 포스팅

2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #1 STS 설치 및 스프링 프로젝트 만들기 : Spring Framework Hello, World!!!
2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #2 스프링 프로젝트 만들기 : Spring Framework Create Project
2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #3 스프링 MyBatis 설정하기 및 로그출력 : Spring Framework MyBatis Log4jdbc
2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #4 스프링 XML , 스프링 유효성검사 : Spring Framework Hibernate Validator XML Marshaller
2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #5 스프링 트랜잭션 : Spring Framework Transaction
2014/07/21 - [개발노트/Spring] - 스프링 프레임워크 게시판 #6 스프링 파일업로드 : Spring Framework FileUpload
2014/07/28 - [개발노트/Spring] - 스프링 프레임워크 게시판 #부록 스프링 검색 및 조회수 올리기 , 스프링 한글깨짐: Spring Framework Cookie


개발 환경

Mac OS X 10.9.4
JAVA 1.6
Apache Tomcat 7.x
MySQL 5.x
Spring 3.1.1
Spring Tool Suite 3.5.1
Maven 2.5.1
myBatis 3.2.7
jQuery 1.11.0

2014.07.19 Written by 최석균 (Syaku)


소스 파일 :  source-5.zip

5. 스프링 트랜잭션

스프링 트랜잭션은 개발자의 편의를 최대한 존중해주는 작업이라 할 수 있다. 흔히 트랜잭션을 사용할때… try catch 문 사이에 커밋과 롤백을 이용하여 트랜잭션을 처리한다. 모든 소스에 동일한 트랜잭션 소스코드를 넣는 불편한 방식에도 의심조차하지 않았다. 하지만 스프링 프레임워크를 선택했다면 이런 반복적인 작업을 한번에 해결하고 개발자는 더 이상 트랜잭션을 신경쓰지 않아도 된다.

스프링에서는 두가지 방식의 트랜잭션을 사용한다. 하나는 선언에 의한 트랜잭션이고 또 하나는 프로그램에 의한 트랜잭션이다. 후자는 우리가 흔히 쓰던 방식이고 전자는 AOP를 사용하여 프로그램 외부에서 선언하는 방식이다. 프로그램에 의한 방식은 중복적인 트랜잭션 소스를 삽입해야 하므로 특별한 경우가 아니면 선언적인 방식을 사용하는 것이 바람직하다.

선언적인 트랜잭션에는 AOP를 이용한 방식과 어노테이션을 선언하는 방식이 있다. 두가지를 먼저 설명 후 프로그램에 의한 방식을 설명한다.

AOP를 이용한 선언적인 방식의 트랜잭션

AOP를 사용할때 서블릿 컨텍스트 설정을 어떻게 구분하였는 지에 따라 문제점이 다양하게 발생한다. 처음부터 스프링을 이해하고 개발하는 사람은 없을 것이다. 이문제는 이론으로 이해하기 보다 직접 문제점을 곁어보고 해결하는 것이 가장 좋을 것 같다.

context-datasource.xml 설정 파일을 열어 트랜잭션 설정을 추가한다.

@소스 context-datasource.xml

<?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.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    ">

   … 생략 …

    <!-- Transaction Manager -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" />
        <tx:method name="delete*" />
    </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="transactionPointcut" expression="execution(* com.syaku.bbs.dao.BbsDao.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut" />
    </aop:config>

    <bean id="bbsDao" class="com.syaku.bbs.dao.BbsDao"/>

</beans>

transactionManager 에는 dataSource 정의를 참조한다.
기존에 jdbc 로그를 출력하기 위해 생성한 dataSource 를 참조한다. 만약 로그가 없다면 연결을 위해 생성한 dataSource 를 참조하면 된다.

tx 는 스프링 트랜잭션을 담당한다. tx 를 사용하기 위해 상단에 네임스페이스를 추가하였다.
트랜잭션을 적용할 때 사용할 어드바스를 생성한다. <tx:advice> 어드바이스 속성과 값에 대한 설명은 아래와 같다.

<tx:method> 속성 설명

name : 트랜잭션이 적용될 메서드 이름을 명시하며 필수 속성이다. get*,delete*,* 설정이 가능하다.
propagation : 트랜잭션 동작 설정하며 기본값 REQUIRED 이다.
isolation : 트랜잭션의 격리 수준을 설정하며 기본값은 DEFAULT 이다.
timeout : 트랜잭션 시간 초과 값을 설정하며 기본값은 -1 이다. 초단위로 입력한다.
read-only : 읽기 전용 여부를 설정한다. 기본값은 false 이다.
rollback-for : 롤백을 할 예외를 설정한다. 여러개를 입력할 경우 , 로 구분한다. 기본값은 RuntimeException 이다. Exception, com.syaku.MyException 설정이 가능하다.
no-rollback-for : 롤백하지 않을 예외를 설정한다. 여러개를 입력할 경우 , 로 구분한다. Exception, com.syaku.MyException 설정이 가능하다.

propagation - 전파옵션 (기본값 : REQUIRED)

REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성한다.
REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성한다.
SUPPORT : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 nontransactionally로 실행된다.
MANDATORY : 부모 트랜잭션 내에서 실행되며 부모 트랜잭션이 없을 경우 예외가 발생된다.
NOT_SUPPORT : nontransactionally로 실행하며 부모 트랜잭션 내에서 실행될 경우 일시 정지된다.
NEVER : nontransactionally로 실행되며 부모 트랜잭션이 존재한다면 예외가 발생한다.
NESTED : 해당 메서드가 부모 트랜잭션에서 진행될 경우 별개로 커밋되거나 롤백될 수 있다. 둘러싼 트랜잭션이 없을 경우 REQUIRED와 동일하게 작동한다.

isolation - 격리수준 (기본값 : DEFAULT)

DEFAULT : DB에서 설정된 기본 격리 수준을 따른다.
SERIALIZABLE : 가장 높은 격리수준을 가지며 사용시 성능 저하가 있을 수 있다.
READ_UNCOMMITTED : 커밋되지 않은 데이터에 대한 읽기를 허용한다.
READ_COMMITTED : 커밋된 트랜잭션에 대해 읽기를 허용한다.
REPEATABLE_READ : 동일한 필드에 대한 다중 접근 시 동일한 결과를 얻을 수 잇는 것을 보장한다.

자세한 설명은 아래 링크를 통해 참고한다.

전자정부프레임워크 트랜잭션
애니프레임 트랜잭션

설명을 토대로 위 소스를 해석한다면 get으로 시작하는 모든 메서드는 읽기전용으로 설정하고, delete으로 시작하는 모든 메서드는 RuntimeException이 발생했을 때 트랜잭션을 통해 롤백을 처리한다.

<aop:config> 에는 트랜잭션을 적용할 대생을 설정한다. 대상 범위는 Aspectj 표현식으로 입력한다. * com.syaku.bbs.dao.BbsDao.*(..)* 라고 입력한 부분을 Aspectj 표현식이라 한다.

Aspectj 표현식 예제

execution(접근자제어 리턴타입 패키지 메서드이름 (인자))
* 모두를 의미함.
.. 0개 이상을 의미함.

execution(public void set*(..)) public 지시자로 시작하며, 리턴 값이 없으며, set으로 시작하는 메서드 중에 인자값은 0개 이상이 메서드를 호출

execution(* com.syaku.bbs.*.*()) bbs 패키지에 인자값이 없는 모든 메서드 호출

execution(* com.syaku.bbs..*.*(..)) bbs 패키지와 하위 패키지에 있는 인자값이 0개 이상인 메서드 호출

기본적인 트랜잭션 설정은 마쳤지만, 이대로 실행할 경우 오류가 발생한다. 우선 빌드후 재시작하면 아래와 같은 유형의 오류가 발생한다.

aspectjweaver 관한 라이브러리가 없어 오류가 발생한다. Maven 빌더에 라이브러리를 추가한다.

오류 내용 : java.lang.NoClassDefFoundError: org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldException

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>${org.aspectj-version}</version>
</dependency>

그리고 소스 마지막 줄에 <bean id="bbsDao" class="com.syaku.bbs.dao.BbsDao"/> 의해 오류가 발생한다.
인터페이스 없이 트랜잭션을 사용할 경우 오류가 발생하게 된다. 그래서 CGLib 라이브러리를 사용해야 한다. CGLib는 동적으로 자바 클래스의 프록시를 생성해주는 기능을 제공한다. Maven 빌더에 CGLib를 추가한다.

오류 내용 : ‘Initialization of bean failed; nested exception is org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces.’

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>

필요한 라이브러리도 모두 설치했다만 트랜잭션을 테스트해보기로 한다.
트랜잭션 대상인 BbsDao 클래스 메서드인 delete 에 RuntimeException 을 강제로 발생시켜본다.

@소스 BbsDao.java

public void delete(int idx) {
    this.bbsMapper.delete(idx);
    throw new RuntimeException("강제로 오류를 발생시켜봄!!");
}

그리고 컨트롤러 delete 메서드에 예외를 받아서 처리하는 작업을 추가한다.

@소스 ViewController.java

try {
     this.bbsDao.delete(idx);
     xml.setMessage("삭제되었습니다.");
     xml.setError(false);
} catch (Exception e) {
     xml.setMessage(e.toString());
     xml.setError(true);
}

목록 페이지에서 삭제 버튼을 눌러본다. 예외는 발생하지만 트랜잭션에 의해 롤백되지 않고 게시물이 삭제되는 문제가 발생한다.
서블릿 컨텍스트에서 컴포넌트 스캔을 할때 <context:component-scan base-package="com.syaku.bbs" /> 설정되어 있어 제대로 처리할 수 없는 것이다.

기본적으로 스프링에서 생성되는 컨텍스트는 RootContext 와 ServletContext 두가지가 있다. Root에서 생성한 빈은 모든 컨텍스트에서 공유되지만 Servlet 에서 생성한 빈은 다른 컨텍스트와 공유되지 않는 다. 그래서 ServletContext 에서 컴포넌트 스캔을 했기때문에 다른 컨텍스트(datasource 컨텍스트)에서 bbsDao 를 찾지 못해서 발생하는 문제점이다.
또한 RootContext 와 ServletContext 두 컨텍스트 모두 동일한 빈이 존재하면 ServletContext 가 우선권을 가지기 때문에 다른 컨텍스트에서 빈을 찾지못한다.

그래서 Controller 계층만 ServletContext 에서 스캔되도록 아래와 같이 수정한다.

이것과 관련된 자료는 아래의 링크를 참조한다.

http://www.mungchung.com/xe/spring/21220
http://toby.epril.com/?p=934
http://bumsgy-innori.tistory.com/205

@소스 root-context.xml

<?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:context="http://www.springframework.org/schema/context"
     xsi:schemaLocation="
     http://www.springframework.org/schema/beans
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context
     http://www.springframework.org/schema/context/spring-context.xsd
     ">

     <context:component-scan base-package="com.syaku.bbs" use-default-filters="false">
     <context:exclude-filter expression="org.springframework.stereotype.Controller" type="annotation" />
     </context:component-scan>

     <!-- Root Context: defines shared resources visible to all other web components -->
     <import resource="classpath:config/spring/context/context-datasource.xml"/>
    <import resource="classpath:config/spring/context/context-mybatis.xml" />
</beans>

component-scan 이 추가하고, 컨트롤러 어노테이션을 스캔에서 제외하였다.
use-default-filters="false" 은 @Component, @Service, @Repository와 같은 어노테이션을 자동으로 탐지되지 않게 하겠다는 의미이다.

@소스 servlet-context.xml

<!-- <context:component-scan base-package="com.syaku.bbs" /> -->
<context:component-scan base-package="com.syaku.bbs" use-default-filters="false">
<context:include-filter expression="org.springframework.stereotype.Controller" type="annotation" />
</context:component-scan>

기존에 있던 컴포넌트 스캔을 제거하고 컨트롤러 어노테이션만 스캔되도록 수정하였다.
다시 게시물을 삭제하면 익셉션에 의해 롤백되어 삭제되지 않는 것을 확인할 수 있다.

context-datasource.xml


BbsDao.java


STS 에서 소스를 확인해보면 왼쪽 번호줄에 빨간색 마킹이 보일것이다. 트랜잭션이 적용된 곳을 표시하고 있다.

그런데 context-datasource.xml 설정은 추후 다른 Dao가 추가될 경우 매번 설정을 추가해줘야하는 불편함이 있다. 그래서 아래와 같이 유동성있게 소스를 수정한다.

@소스 context-datasource.xml

<aop:config>
     <aop:pointcut id="transactionPointcut" expression="execution(* com.syaku.bbs..*Dao.*(..))"/>
     <aop:advisor advice-ref="txAdvice" pointcut-ref="transactionPointcut" />
</aop:config>

<!-- <bean id="bbsDao" class="com.syaku.bbs.dao.BbsDao"/> -->

expression Aspectj 표현식을 수정하고, bbsDao bean 을 제거한다.

@소스 root-context.xml

<context:component-scan base-package="com.syaku..." use-default-filters="false">
    <context:include-filter expression="org.springframework.stereotype.Service" type="annotation" />
    <context:include-filter expression="org.springframework.stereotype.Repository" type="annotation" />
</context:component-scan>

ROOT 컨텍스트에 컨트롤러 어노테이션을 제외한 어노테이션을 추가한다. 그리고 모든 컨텍스트 설정파일에 컴포넌트 스캔 베이스패킷을 context:component-scan base-package="com.syaku…" 로 변경한다. (servlet-context.xml 파일의 베이스패킷을 변경한다.)

@어노테이션에 의한 트랜잭션

AOP를 통한 트랜잭션보다 더 간결하게 처리할 수 있는 것이 어노테이션 방식이다. 이전에 AOP를 이용한 선언적인 방식의 트랜잭션 설정을 모두 제거하거나 주석처리한다. 단 bean transactionManager 는 남겨둔다. 완전한 소스는 아래와 같다.

@소스 context-datasource.xml

<?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.xsd
     http://www.springframework.org/schema/tx
     http://www.springframework.org/schema/tx/spring-tx.xsd
     ">

      <bean id="jdbcProp" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="location" value="classpath:jdbc.properties" />
      </bean>

      <bean id="dataSourceSpied" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
           <property name="driverClassName" value="${jdbc.driver}" />
           <property name="url" value="${jdbc.url}" />
           <property name="username" value="${jdbc.username}" />
           <property name="password" value="${jdbc.password}" />
     </bean>

     <bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource">
      <constructor-arg ref="dataSourceSpied" />
      <property name="logFormatter">
       <bean class="net.sf.log4jdbc.tools.Log4JdbcCustomFormatter">
        <property name="loggingType" value="MULTI_LINE" />
        <property name="sqlPrefix" value="SQL:::" />
       </bean>
      </property>
     </bean>

     <!-- Transaction Manager -->
     <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource" />
     </bean>

     <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

[주의] datasourceSpid 와 dataSource 그리고 transactionManager 의 dataSource 이름은 위 소스 내용과 일치해야 한다. 하나라도 틀리면 트랜잭션이 안되거나 로그가 출력되지 않는 다.

위 소스를 수정하고 서비스 재시작후 테스트로 게시글을 삭제한다.
예외는 발생하지만 게시글이 삭제된다. 당연히 어노테이션 트랜잭션을 설정하지 않았기 때문이다.
소스를 수정하기전에 트랜잭션에 사용되는 어노테이션에 대한설명은 아래와 같이 참고한다. 기능 설명은 이전에 했기때문에 소스 코딩만 표시하였다.

isolation : @Transactional(isolation=Isolation.DEFAULT)
noRollbackFor : @Transactional(noRollbackFor=NoRoleBackTx.class)
noRollbackForClassName :  @Transactional(noRollbackForClassName="NoRoleBackTx”)
propagation : @Transactional(propagation=Propagation.REQUIRED)
readOnly : @Transactional(readOnly = true)
rollbackFor : @Transactional(rollbackFor=RoleBackTx.class)
rollbackForClassName : @Transactional(rollbackForClassName="RoleBackTx”)
timeout : @Transactional(timeout=10)

BbsDao 에 트랜잭션을 위한 어노테이션 추가한다.

@소스 BbsDao.java

package com.syaku.bbs.dao;

import java.util.List;
import java.util.Map;

import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service(value = "bbsDao")
@Transactional(readOnly=true)
public class BbsDao {
    @Resource(name = "bbsMapper")
    private BbsMapper bbsMapper;

    public List<BbsVo> getSelect(Map map) {
        return this.bbsMapper.select(map);
    }

    public BbsVo getSelectOne(int idx) {
        return this.bbsMapper.selectOne(idx);
    }

    public void insert(BbsVo bbsVo) {
         this.bbsMapper.insert(bbsVo);
    }

    public void update(BbsVo bbsVo) {
         this.bbsMapper.update(bbsVo);
    }

    @Transactional
    public void delete(int idx) {
         this.bbsMapper.delete(idx);
         throw new RuntimeException("강제로 오류를 발생시켜봄!!");
    }

    public void updateReadCount(int idx) {
         this.bbsMapper.updateReadCount(idx);
    }
}

클래스의 모든 메서드에 readOnly 읽기전용을 적용하였고 delete 메서드에 트랜잭션을 적용하였다. 테스트로 게시글을 삭제해보면 삭제되지 않는 다.

readOnly 기본값은 false 이다
스프링 트랜잭션은 서비스계층(@Service)에 적용하는 것이 바람직하다.

트랜잭션 참고 자료

스프링 2.x 한글 메뉴얼 : http://openframework.or.kr/framework_reference/spring/ver2.x/html/transaction.html
전자정부프레임워크 트랜잭션 : http://www.egovframe.go.kr/wiki/doku.php?id=egovframework:rte:psl:transaction:declarative_transaction_management
애니프레임 트랜잭션 : http://dev.anyframejava.org/docs/anyframe/plugin/foundation/4.6.1/reference/html/ch08.html

프로그램에 의한 트랜잭션

어쩔수없이 프로그램에 의한 트랜잭션을 사용해야할 경우를 제외하고, 최대한 선언적인 트랜잭션 방식으로 프로그램을 설계해야 한다. 그리고 트랜잭션 방식을 꼭 한가지만 사용해야 하는 것은 아니다. 설명한 3가지 방식 모두 설정해서 사용해도 된다. 하지만 프로그램의 통일성을 갖는 것이 좋을 것이다.

프로그램에 의한 트랜잭션을 사용하려면 context-datasource.xml 에 아래의 소스만 추가되면 된다.

<!-- Transaction Manager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

그리고 트랜잭션을 적용할 대상에 아래 소스처럼 수정한다. 이번에는 게시글이 저장되는 insert 메서드에 트랜잭션을 적용하였다.

@소스 BbsDao.java

@Resource(name = "transactionManager")
protected DataSourceTransactionManager txManager;

public void insert(BbsVo bbsVo) {
    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus txStatus= txManager.getTransaction(def);

    try {
         this.bbsMapper.insert(bbsVo);
         int a = 1 / 0;
         txManager.commit(txStatus);
    } catch(Exception e) {
         txManager.rollback(txStatus);
    }
}

context-datasource.xml에 transactionManager를 BbsoDao에 선언하였다. try catch문 사이에 txManager 객체를 이용하여 커밋과 롤백을 사용하면 된다.
강제적인 예외를 사용할 수 없기때문에 정수에 소스를 넣어 예외를 발생시켰다.
테스트로 게시물을 등록한다. 메세지는 추가되었습니다 라고 경고창이 출력되지만 실제로는 저장되지 않는 다. 그래서 오류 메세지를 출력할 수 있게 아래와 같이 수정한다.

@소스 BbsDao.java

public void insert(BbsVo bbsVo) throws Exception{
     DefaultTransactionDefinition def = new DefaultTransactionDefinition();
    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus txStatus= txManager.getTransaction(def);

    try {
         this.bbsMapper.insert(bbsVo);
         int a = 0 / 1;
         txManager.commit(txStatus);
    } catch(Exception e) {
         txManager.rollback(txStatus);
         throw new Exception(e.getMessage());
    }
}

상위 클래스에서 예외를 받을수 있게 수정하였다. 그리고 ViewController 를 수정한다.

@소스 ViewController.java

try {
     if (idx == null || idx == 0) {
          this.bbsDao.insert(bbsVo);
          xml.setMessage("추가되었습니다.");
          xml.setError(false);
     } else {
          this.bbsDao.update(bbsVo);
          xml.setMessage("수정되었습니다.");
          xml.setError(false);
     }
} catch(Exception e) {
     xml.setMessage(e.getMessage());
     xml.setError(true);
}

procBbsWrite 메서드에 저장 및 수정부분을 위와 가티 수정하면 된다. 그리고 테스트로 게시글을 등록해본다.




posted syaku blog

Syaku Blog by Seok Kyun. Choi. 최석균.

http://syaku.tistory.com


  • 익명회원 at 2014.08.28 05:19

    궁금한게 있어 질문좀 할게요..context-datasource.xml 트랜잭션 설정을 하였고, root-context.xml에서 import하여 resource=context-datasource.xml 가져왔는데.. 롤백처리가 안되고 정상적으로 삭제가 되는부분이
    이해가 잘 안가네요.만약 root-context.xml에 트랜잭션을 설정하면 롤백이 정상적으로 작동을 하게되는건가요?
    root-context.xml , servlet.xml에 스캔기능을 exclude, nclude한 부분이 이해가 잘되질 않아서요..

    • 샤쿠 syaku at 2014.08.28 09:45 신고

      서블릿 영역과 컨텍스트 영역이 있다고 줄여표현하겠습니다.
      web.xml 에서 context-param 에 설정된 xml을 컨텍스트 영역이고 servlet 에 설정된 xml을 서블릿 영역이라고 가정하면 본문 내용과 같이 이미 서블릿 영역에서 모든 mvc 계층을 스캔해버리는 설정이 있어요...

      그렇게 되면 컨텍스트 영역에서 @Service 계층를 읽을 수 없게됩니다. 더 쉽게 말해 서블릿에서 먼저 읽으면 컨텍스트에서 읽지 않고 생략해버리죠 그런데 문제는 서블릿에서 스캔된 계층들은 다른 서블릿이나 컨텍스트와 공유할 수없어요... 그러니 컨텍스트에서 트랜잭션을 해도 서비스계층을 알수없이니 트랜잭션이 이루어지지 않는 거죠!!!

      하지만 컨텍스트에서 스캔된 서비스들은 다른 서블릿이나 컨텍스트와도 공유할 수 있지요!! 그래서 간단한 프로그램이라면 서블릿 영역에 모든 설정을 해버리면 쉽게 해결되겠지만!! 프로그램이 커지면 그럴수가 없게 됩니다.

      그래서 서블릿과 컨텍스트로 나눠 설정하는 데 공유해야 할 계층은 컨텍스트 영역에서 스캔하고 서블릿 영역에서 컨트롤러만 스캔하게 설정하는 것이지요~

      include 는 스캔해겠다 exclude 는 스캔하지 않겠다란 의미와 같습니다.


  • 익명회원 at 2014.08.29 03:58

    네 그부분은 잘 알겠는데요. 콘텍스트영역이 최상위고(부모고) 서블릿영역이 하위(자식)이고 서블릿에 설정된게 우선시 되는부분은 잘 알겠어요. 콘텍스트영역에 지금 @Service계층 등록햇지만 서블릿영역의 <context:component-scan ...> 을 하여 콘텍스트영역의 @Service가 적용이 안되서 쓸수 없단
    얘긴거 같은데..근데 제가 궁금한건 포인트컷에 expression="execution(* com.syaku.bbs..*Dao.*(..))"/>
    하면
    1.@Serviece 스캔을 하지 않아도 트랜잭션 설정이 들어가지나요?
    2.@Service명시 해야지만 트랜잭션 처리가 이루어 지는건가요?

    • 샤쿠 syaku at 2014.08.29 09:43 신고

      1. 에 대한 답변
      "AOP를 이용한 선언적인 방식의 트랜잭션" 부분에 @소스 context-datasource.xml 보시면 bbsDao 를 주입한 부분이 있습니다. 그래서 포인컷이 적용되는것입니다 예) <bean id="bbsDao" class="com.syaku.bbs.dao.BbsDao"/>

      그래서 스캔하지 않아도 주입했기때문에 적용되는 것입니다.

      주입했기때문에 스캔된 효과와 같다는 말이죠~


      2. 에대한 답변

      본문을 보시면 처음에는 <bean id="bbsDao" class="com.syaku.bbs.dao.BbsDao"/> 삽입했지만 나중에는 주석처리한 합니다. 이유는 다른 새로운 Dao 가 추가될경우 매번 설정을 추가해야하는 불편함 때문에 자동으로 주입될수 있게 root-context.xml 에 컴포넌트 스캔을 하는 설정을 추가합니다. 그래서 @Service 어노테이션을 명시해야 스캔되어 트랜잭션 처리가 되는 거죠
      "@어노테이션에 의한 트랜잭션" 영역 위 부분에 내용이 있습니다.

      그래서 @Service 를 명시해야 이루어지는 것입니다.

      ==========================================
      본문 내용이 처음은 직접 bbsDao 를 주입하는 설정을 하는 부분이고 그아래에는 bbsDao 를 직접 주입하지 않고 자동으로 스캔하도록 설정을 수정한것입니다.

      본문에 여러 예제를 사용하다보니 혼란을 줄수있는 흐름이네요;;; 답변이 도움이 되셨으면 합니다~

  • 만유 at 2014.09.11 18:21

    트랜잭션을 따라 해보았습니다.
    AOP를 이용한 방법과 어노테이션을 이용하는 방법 둘 다 사용해봤는데, 에러메시지까지는 뜨지만, 롤백되지 않고 삭제되네요..
    포스팅 된 순서대로 쭈욱따라 했습니다. 안되는 이유가 따로 있는걸까요?..

    • 샤쿠 syaku at 2014.09.11 18:26 신고

      포스팅 내용에서 @소스 root-context.xml 되어있는 부분부터
      컴포넌트 스캔을 설정하는 부분인데요~ 설정이 정확하게 잘되었는 지 다시 확인해보세요~

    • 만유 at 2014.09.15 18:25

      여러번 다시 따라해봤습니다만,,
      트랜잭션 어느하나 제대로 되지 않네요....ㅠㅠ
      혹시 첨부해주신 소스를 실행해보는 방법은 없을까요??
      maven project로 import 하니, 500에러가 발생하더군요,,,

      Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException

    • 샤쿠 syaku at 2014.09.15 20:02 신고

      님 소스를 압축해서 syaku@네이버닷컴으로 보내주세요 확인하고 알려드리죠~ 트랜잭션 작업한 모든 소스를 주시요

  • at 2015.04.10 10:40

    프로젝트를 임포트하려면 어떻게 해야되나요?

  • 덕이 at 2015.09.30 20:10

    안녕하세요..
    전자정부 프레임워크에서도 이 블로그를 알려주네요 ㅎㅎ(답변 받기전에 제가 먼저 찾긴 했지만요 ㅎㅎ)

    디테일하게 설명 잘되어 있고 좋은 정보 감사합니다..

    블로그에 출처 남기고 퍼가겠습니다..
    나중에 시간 날 때 보면서 설정해보려구요..

  • 에리 at 2017.02.14 20:29

    잘 보고 갑니다~

  • 니콜 at 2017.07.10 16:37

    덕분에 너무 좋은 경험을 하고 있습니다.
    다중DB접속을 고민하고 있는데...

    AOP방식으로 다중DB로 접속하게 될 경우
    트랜젝션 처리는 어떻게 해야하는지요?
    (xml에 transaction2로 추가한뒤에 bean을 dao나 mapper에서 주입을 해줘야할 것 같은데 예제로는 주입부분이 없어서요..혹시 답글 부탁드려도 될까요?)

댓글 남기기
◀ PREV 1···293031323334353637 NEXT ▶