TransactionManager를 사용하여 트렌젝션 처리 방법

woom·2023년 3월 2일
0

Framework

목록 보기
17/20
post-thumbnail

🌼 웹프로그램 작성 순서

1. table
2. DTO Class
3. XML Mapper
4. Interface Mapper
5. DAO Interface
6. DAO Class
7. Service Interface
8. Service Class
9. JUint 테스트 프로그램 작성 (service)

10. Controller 클래스
11. 테스트 프로그램(JUint)
12. 단위 프로그램(모듈) 테스트
13. HTML 문서를 JSP 문서로 변환
14. 브라우저를 이용한 통합 프로그램 테스트


📕 1. table 생성

  1. pointuser table
create table pointuser(id varchar2(20) primary key, name varchar2(30), point number);

  1. pointboard table
create table pointboard(num number primary key, writer varchar2(20), subject varchar2(100));
create sequence pointboard_seq;


📕 2. DTO Class

  1. pointuser DTO Class
package xyz.itwill10.dto;

import lombok.Data;

@Data
public class PointUser {
	private String id;
	private String name;
	private int point;
}


  1. pointboard DTO Class
package xyz.itwill10.dto;

import lombok.Data;

@Data
public class PointBoard {
	private int num;
	private String writer;
	private String subject;
}



📙 3. XML Mapper

  1. pointuser XML Mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill10.mapper.PointUserMapper">
	<insert id="insertPointUser">
		insert into pointsuer values (#{id}, #{name}, 0)
	</insert>
	
	<update id="updatePlusPointUser">
		update pointuser set point=point+100 where id=#{id}
	</update>
	
	<update id="updateMinusPointUser">
		update pointuser set point=point-100 where id=#{id}
	</update>
	
	<select id="selectPointUser" resultType="PointUser">
		select * from pointuser where id=#{id}
	</select>
</mapper>


  1. pointboard XML Mapper
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.itwill10.mapper.PointBoardMapper">

	<insert id="insertPointBoard">
		<selectKey resultType="int" keyProperty="num" order="BEFORE">
			select pointboard_seq.nextval from dual
		</selectKey>
		insert into pointboard values(#{num}, #{writer}, #{subject})
	</insert>
	
	<delete id="deletePointBoard">
		delete from pointboard where num=#{num}
	</delete>
	
	<select id="selectPointBoard" resultType="PointBoard">
		select * from pointboard where num=#{num}
	</select>
	
	<select id="selectPointBoardList" resultType="PointBoard">
		select * from pointboard order by num desc
	</select>
	
</mapper>





📙 4. Interface Mapper

  1. pointuser Interface Mapper

package xyz.itwill10.mapper;

import xyz.itwill10.dto.PointUser;

public interface PointUserMapper {
	int insertPointUser (PointUser user);
	int updatePlusPointUser (String id);
	int updateMinusPointUser(String id);
	PointUser selectPointUser(String id);
}

  1. pointboard Interface Mapper
package xyz.itwill10.mapper;

import java.util.List;

import xyz.itwill10.dto.PointBoard;

public interface PointBoardMapper {
	int insertPointBoard(PointBoard board);
	int deletePointBoard(int num);
	PointBoard selectPointBoard(int num);
	List<PointBoard> selectPointBoardList();
}





📒 5. DAO Interface

  • Interface Mapper와 동일하게 작성 권장
  1. pointuser DAO Interface
package xyz.itwill10.dao;

import xyz.itwill10.dto.PointUser;

public interface PointUserDAO {
	int insertPointUser (PointUser user);
	int updatePlusPointUser (String id);
	int updateMinusPointUser(String id);
	PointUser selectPointUser(String id);
}

  1. pointboard DAO Interface
package xyz.itwill10.dao;

import java.util.List;

import xyz.itwill10.dto.PointBoard;

public interface PointBoardDAO {
	int insertPointBoard(PointBoard board);
	int deletePointBoard(int num);
	PointBoard selectPointBoard(int num);
	List<PointBoard> selectPointBoardList();
}





📒 6. DAO Class

  • @Repository : DAO 클래스를 스프링 컨테이너가 관리할 수 있는 Spring Bean으로 등록

  • @RequiredArgsConstructor : 클래스에 작성된 필드 중 final 키워드를 사용하여 선언된 필드의 초기화를 위한 매개변수가 선언된 생성자를 자동 생성 (Lombok 라이브러리가 제공하는 어노테이션)

  • SqlSession : 원하는 매퍼의 SQL 명령을 제공받아 사용

  1. pointuser DAO Class
package xyz.itwill10.dao;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import xyz.itwill10.dto.PointUser;
import xyz.itwill10.mapper.PointUserMapper;

@Repository
@RequiredArgsConstructor
public class PointUserDAOImpl implements PointUserDAO {
	
	private final SqlSession sqlSession;
	
	@Override
	public int insertPointUser(PointUser user) {
		return sqlSession.getMapper(PointUserMapper.class).insertPointUser(user);
	}

	@Override
	public int updatePlusPointUser(String id) {
		return sqlSession.getMapper(PointUserMapper.class).updatePlusPointUser(id);
	}

	@Override
	public int updateMinusPointUser(String id) {
		return sqlSession.getMapper(PointUserMapper.class).updateMinusPointUser(id);
	}

	@Override
	public PointUser selectPointUser(String id) {
		return sqlSession.getMapper(PointUserMapper.class).selectPointUser(id);
	}

}

  1. PointBoard DAO Class
package xyz.itwill10.dao;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.stereotype.Repository;

import lombok.RequiredArgsConstructor;
import xyz.itwill10.dto.PointBoard;
import xyz.itwill10.mapper.PointBoardMapper;

@Repository
@RequiredArgsConstructor
public class PointBoardImpl implements PointBoardDAO {

	private final SqlSession sqlSession;
	
	@Override
	public int insertPointBoard(PointBoard board) {
		return sqlSession.getMapper(PointBoardMapper.class).insertPointBoard(board);
	}

	@Override
	public int deletePointBoard(int num) {
		return sqlSession.getMapper(PointBoardMapper.class).deletePointBoard(num);
	}

	@Override
	public PointBoard selectPointBoard(int num) {
		return sqlSession.getMapper(PointBoardMapper.class).selectPointBoard(num);
	}

	@Override
	public List<PointBoard> selectPointBoardList() {
		return sqlSession.getMapper(PointBoardMapper.class).selectPointBoardList();
	}

}





📗 7. Service interface

  1. pointuser Service interface
package xyz.itwill10.service;

import xyz.itwill10.dto.PointUser;

public interface PointUserService {
	PointUser addPointUser(PointUser user) throws Exception;
}

  1. PointBoard Service interface
package xyz.itwill10.service;

import java.util.List;

import xyz.itwill10.dto.PointBoard;
import xyz.itwill10.dto.PointUser;

public interface PointBoardService {
	PointUser addPointBoard(PointBoard board) throws Exception;
	PointUser removePointBoard(int num) throws Exception;
	List<PointBoard> getPointBoardList();
}





📗 8. Service class

  1. pointuser Service class
  • 회원정보를 전달받아 POINTUSER 테이블에 삽입하고 삽입한 회원정보를 검색하여 반환하는 메소드

    • 전달받은 회원정보의 아이디가 중복될 경우 인위적 예외 발생
package xyz.itwill10.service;

import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import xyz.itwill10.dao.PointUserDAO;
import xyz.itwill10.dto.PointUser;

@Service
@RequiredArgsConstructor
public class PointUserServiceImpl implements PointUserService {
	private final PointUserDAO pointUserDAO;

	//회원정보를 전달받아 POINTUSER 테이블에 삽입하고 삽입한 회원정보를 검색하여 반환하는 메소드
	@Override
	public PointUser addPointUser(PointUser user) throws Exception {
		//전달받은 회원정보의 아이디가 중복될 경우 인위적 예외 발생
		if(pointUserDAO.selectPointUser(user.getId())!=null) {
			throw new Exception("이미 사용중인 아이디입니다.");
		}
		pointUserDAO.insertPointUser(user);
		return pointUserDAO.selectPointUser(user.getId());
	}
}

  1. PointBoard Service class
  • 의존성 주입을 위해 @Autowired 사용 시 필드 마다 작성 필요 (@RequiredArgsConstructor 생성자를 이용한 의존성 주입을 위한 어노테이션을 사용할 경우에는 불필요)

  • 게시글을 전달받아 POINTBOARD 테이블에 삽입하고 게시글 작성자에 대한 회원정보를 POINTUSER 테이블에서 검색하여 반환하는 메소드

    • 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 포인트가 증가되도록 변경
  • 게시글 번호를 전달받아 POINTBOARD 테이블에 저장된 게시글을 삭제하고 게시글 작성자에 대한 회원정보를 POINTUSER 테이블에서 검색하여 반환하는 메소드

    • 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 포인트가 감소되도록 변경
  • POINTBOARD 테이블에 저장된 모든 게시글을 검색하여 반환하는 메소드


package xyz.itwill10.service;

@Service
@RequiredArgsConstructor
public class PointBoardServiceImpl implements PointBoardService {
	//@Autowired
	//private PointUserDAO pointUserDAO;
	//@Autowired
	//private PointBoardDAO pointBoardDAO;
	
	private final PointUserDAO pointUserDAO;
	private final PointBoardDAO pointBoardDAO;
	
	//게시글을 전달받아 POINTBOARD 테이블에 삽입하고 게시글 작성자에 대한 회원정보를 POINTUSER
	//테이블에서 검색하여 반환하는 메소드
	// => 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 포인트가 증가되도록 변경
	@Transactional
	@Override
	public PointUser addPointBoard(PointBoard board) throws Exception {
		pointBoardDAO.insertPointBoard(board);//게시글 삽입
		//게시글 작성자에 대한 회원정보를 없는 경우 인위적 예외 발생
		// => 예외가 발생되면 하단에 작성된 명령은 실행되지 않고 메소드 강제 종료
		if(pointUserDAO.selectPointUser(board.getWriter())==null) {
			throw new Exception("게시글 작성자가 존재하지 않습니다.");
		}
		pointUserDAO.updatePlusPointUser(board.getWriter());//회원의 포인트 증가
		return pointUserDAO.selectPointUser(board.getWriter());//회원정보 검색
	}

	//게시글 번호를 전달받아 POINTBOARD 테이블에 저장된 게시글을 삭제하고 게시글 작성자에 대한
	//회원정보를 POINTUSER 테이블에서 검색하여 반환하는 메소드
	// => 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 포인트가 감소되도록 변경
	@Transactional
	@Override
	public PointUser removePointBoard(int num) throws Exception {
		PointBoard board=pointBoardDAO.selectPointBoard(num);//게시글 검색
		//게시글 번호에 대한 게시글이 없는 경우 인위적 예외 발생
		if(board==null) {
			throw new Exception("게시글이 존재하지 않습니다.");
		}
		
		pointBoardDAO.deletePointBoard(num);//게시글 삭제
		
		//삭제된 게시글의 작성자에 대한 회원정보를 없는 경우 인위적 예외 발생
		if(pointUserDAO.selectPointUser(board.getWriter())==null) {
			throw new Exception("게시글 작성자가 존재하지 않습니다.");
		}
		
		pointUserDAO.updateMinusPointUser(board.getWriter());//회원의 포인트 감소
				
		return pointUserDAO.selectPointUser(board.getWriter());//회원정보 검색
	}

	//POINTBOARD 테이블에 저장된 모든 게시글을 검색하여 반환하는 메소드
	@Override
	public List<PointBoard> getPointBoardList() {
		return pointBoardDAO.selectPointBoardList();
	}
}





🌼 트렌젝션을 처리하는 방법

  • Spring Framework의 TransactionManager를 사용하여 트렌젝션을 처리하는 방법
  1. spring-tx 라이브러리를 프로젝트에 빌드 처리 (메이븐 : pom.xml)

    • spring-jdbc 라이브러리를 빌드 처리하면 의존 관계에 의해 자동으로 빌드 처리
  2. SpringMVC 프로그램의 Spring Bean Configuration File(root-context.xml)에 TransactionManager 관련 클래스를 Spring Bean으로 등록

  3. 트렌젝션 처리를 위한 SpringAOP 설정 또는 메소드에 @Transactional 어노테이션을 사용하여 자동으로 롤백 처리

    3-1. SpringMVC 프로그램의 Spring Bean Configuration File(servlet-context.xml)에 트렌젝션 처리를 위한 SpringAOP 설정

    3-2. 트렌젝션 처리 기능을 제공받고 싶은 service 클래스 메소드에 @Transactional 어노테이션을 사용하여 작성하면 메소드의 명령 실행시 예외(Exception)가 되면 자동으로 롤백 처리

    • @Transactional 어노테이션을 사용할 경우 Spring Bean Configuration File(root-context.xml)에 spring-tx.xsd 파일이 제공하는 annotation-driven 엘리먼트를 반드시 설정
    • 테스트 클래스의 테스트 메소드에 @Transactional 어노테이션을 사용할 경우 명령 실행 후 무조건 롤백 처리
    • test만을 목적으로 진행 후 트랜잭션 처리를 적용하지 않음

🌸 root-context.xml

  • TransactionManager 관련 클래스를 Spring Bean으로 등록

    • Spring Bean의 식별자(beanName)을 반드시 [transactionManager]로 설정
    • dataSource 필드에 DataSource 관련 클래스의 Spring Bean을 인젝션 처리
  • annotation-driven : @Transactional 어노테이션을 사용하여 TransactionManager 객체로 트렌젝션 처리 기능을 제공하기 위한 엘리먼트

    • tx 네임스페이스로 spring-tx.xsd 파일을 제공받아 엘리먼트 작성

	<!-- TransactionManager 관련 클래스를 Spring Bean으로 등록 -->
	<!-- => Spring Bean의 식별자(beanName)을 반드시 [transactionManager]로 설정 -->
	<!-- => dataSource 필드에 DataSource 관련 클래스의 Spring Bean을 인젝션 처리  -->
	<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<!-- annotation-driven : @Transactional 어노테이션을 사용하여 TransactionManager 객체로
	트렌젝션 처리 기능을 제공하기 위한 엘리먼트 -->
	<!-- => tx 네임스페이스로 spring-tx.xsd 파일을 제공받아 엘리먼트 작성 -->
	<tx:annotation-driven/>





🌸 servlet-context.xml

  • TranscationManager를 사용하여 트렌젝션 처리를 하기 위해 tx 네임스페이스로 spring-tx.xsd 파일의 엘리먼트 사용할 수 있도록 설정
  • advice : TranscationManager 관련 Spring Bean를 Advisor로 설정하기 위한 엘리먼트
    • Advisor : 삽입위치(JoinPoint)가 정해져 있는 횡단관심코드의 메소드가 작성된 Advice 클래스로 생성된 객체(Spring Bean)
    • id 속성 : advice 엘리먼트를 구분하기 위한 식별자를 속성값으로 설정
  • transaction-manager 속성 : TranscationManager 관련 클래스의 Spring Bean의 식별자(beanName)를 속성값으로 설정
    • 롤백처리와 커밋처리에 대한 설정이 저장된 TranscationManager 객체 제공
    • <tx:advice id="txAdvisor" transaction-manager="transactionManager">
  • attributes : TranscationManager 객체에 의해 처리될 메소드 목록을 설정하기 위한 엘리먼트
    • method : TranscationManager 객체에 의해 트렌젝션 처리될 메소드와 방식을 설정하기 위한 엘리먼트
    • name 속성 : TranscationManager 객체에 의해 트렌젝션 처리될 메소드의 이름을 속성값으로 설정
    • 메소드 이름에 [*] 패턴문자를 사용하여 설정 가능
    • rollback-for 속성 : ROLLBACK 명령이 실행될 예외를 속성값으로 설정
    • read-only 속성 : false(기본) 또는 true(읽기) 중 하나를 속성값으로 설정
    • <tx:method name="add*" rollback-for="Exception"/>

	<tx:advice id="txAdvisor" transaction-manager="transactionManager">
		<tx:attributes>
			<tx:method name="add*" rollback-for="Exception"/>
			<tx:method name="modify*" rollback-for="Exception"/>
			<tx:method name="remove*" rollback-for="Exception"/>
			<tx:method name="get*" read-only="true"/>
		</tx:attributes>
	</tx:advice>


  • SpringAOP 기능을 사용하여 타겟메소드 호출시 TranscationManager 객체가 동작되도록 설정
    • SpringAOP 기능을 사용하기 위해 aop 네임스페이스로 spring-aop.xsd 파일의 엘리먼트 사용할 수 있도록 설정 (Namespaces → aop → 최상위 버전 선택)
    • advisor : 삽입위치가 지정된 Advice 클래스의 객체를 제공받기 위한 엘리먼트
    • advice-ref : advice 엘리먼트의 식별자를 속성값으로 설정
	
	<aop:config>
		<aop:advisor advice-ref="txAdvisor" pointcut="execution(* xyz.itwill10.service..*(..))"/>
	</aop:config>





📘 9. 테스트 프로그램 (pointuser Service)

  • 총 세번의 메소드 실행

  • @RunWith : 테스트 프로그램의 클래스를 객체로 생성하여 테스트 메소드를 호출하기 위한 클래스 설정

  • @WebAppConfiguration : ApplicationContext 객체가 아닌 WebApplicationContext 객체로 스프링 컨테이너 역활을 제공하도록 설정하기 위한 어노테이션

  • @ContextConfiguration : 테스트 클래스에서 사용할 수 있는 Spring Bean를 제공하기 위한 Spring Bean Configuration File을 설정하는 어노테이션


package xyz.itwill.controller;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
public class PointUserServiceTest {
	private static final Logger logger=LoggerFactory.getLogger(PointUserServiceTest.class);
	
	@Autowired
	private PointUserService pointUserService;
	
	/*
	@Test
	public void testAddPointUser() throws Exception {
		PointUser user=new PointUser();
		user.setId("abc123");
		user.setName("홍길동");
		
		PointUser addUser=null;
		try {
			addUser=pointUserService.addPointUser(user);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		
		logger.info(addUser.toString());
	}
	*/
	
	@Transactional
	@Test
	public void testAddPointUser() throws Exception {
		PointUser user=new PointUser();
		user.setId("xyz789");
		user.setName("임꺽정");
		
		PointUser addUser=null;
		try {
			addUser=pointUserService.addPointUser(user);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		
		logger.info(addUser.toString());
	}
}




  • TransactionManager를 사용하여 트렌젝션 처리 전

  • @Transactional 어노테이션을 사용하여 트렌젝션 처리 후 (명령 실행 후 무조건 롤백 처리)


📘 9. 테스트 프로그램 (PointBoard Service)

  • 게시글 작성자가 없는 경우 예외 발생
    • 문제점) 예외 발생전에 실행된 게시글 삽입에 대한 SQL 명령은 이미 전달되어 실행된 상태로 POINTBOARD 테이블에 게시글 저장
    • 게시글 작성자가 존재하지 않는 게시글 → 게시글을 검색하여 출력할 경우 문제가 발생
    • 해결법) 예외 발생전 실행된 모든 SQL 명령은 취소되도록 롤백 처리
    • Spring Framework에서는 TransactionManager 관련 클래스를 사용하여 일관성 있는 트렌젝션 처리 기능 제공 → SpringAOP


package xyz.itwill.controller;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/*.xml"})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PointBoardServiceTest {
	private static final Logger logger=LoggerFactory.getLogger(PointUserServiceTest.class);

	@Autowired
	private PointBoardService pointBoardService;
	
	@Transactional
	@Test
	public void test1() {
		PointBoard board=new PointBoard();
		//board.setWriter("abc123");
		board.setWriter("xyz789");//게시글 작성자가 회원정보에 존재하지 않도록 작성
		board.setSubject("테스트");
		
		PointUser user=null;
		try {
			//게시글 작성자가 없는 경우 예외 발생
			//문제점)예외 발생전에 실행된 게시글 삽입에 대한 SQL 명령은 이미 전달되어 실행된 
			//상태로 POINTBOARD 테이블에 게시글 저장
			// => 게시글 작성자가 존재하지 않는 게시글 - 게시글을 검색하여 출력할 경우 문제가 발생
			//해결법)예외 발생전 실행된 모든 SQL 명령은 취소되도록 롤백 처리
			// => Spring Framework에서는 TransactionManager 관련 클래스를 사용하여 일관성 있는
			//트렌젝션 처리 기능 제공 - SpringAOP
			user=pointBoardService.addPointBoard(board);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		
		logger.info(pointBoardService.getPointBoardList().toString());
		logger.info(user.toString());
	}
	
	/*
	@Transactional
	@Test
	public void test2() {
		PointUser user=null;
		try {
			user=pointBoardService.removePointBoard(1);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		
		logger.info(pointBoardService.getPointBoardList().toString());
		logger.info(user.toString());
	}
	*/
}




  • 트렌젝션 처리 전 (게시글 작성자가 존재하지 않으나 삽입처리 후 커밋 완료됨)

  • @Transactional 어노테이션을 사용하여 트렌젝션 처리 후 (명령 실행 후 무조건 롤백 처리)


profile
Study Log 📂

0개의 댓글