[Spring 16-1] 트렌젝션 매니저 사용방법

임승현·2023년 3월 2일

Spring

목록 보기
41/46

🌈Step_1-1 테이블 만들기

📢회원정보를 저장하는 테이블

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

🌈Step_2-1 DTO 클래스 생성

📃PointUser.java

※ xyz.itwill10.dto 패키지에 PointUser.java 클래스 생성

package xyz.itwill10.dto;
//
import lombok.Data;
//
//create table pointuser(id varchar2(20) primary key, name varchar2(30), point number);
@Data
public class PointUser {
	private String id;
	private String name;
	private int point;
}

🌈Step_3-1 매퍼 파일 생성

📃PointUserMapper.xml

※ xyz.itwill10.mapper 패키지에 PointUserMapper.xml 파일 생성

<?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 pointuser 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>

📃PointUserMapper.java(인터페이스)

※ xyz.itwill10.mapper 패키지에 PointUserMapper.java 인터페이스 파일 생성

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);
}

🌈Step_4-1 DAO 클래스 생성

📃PointUserDAO.java(인터페이스)

※ xyz.itwill10.dao 패키지에 PointUserDAO.java 인터페이스 생성

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);
}

📃PointUserDAOImpl.java

※ xyz.itwill10.dao 패키지에 PointUserDAOImpl.java 클래스 생성

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//final을 꼭 써서 인젝션 처리
public class PointUserDAOImpl implements PointUserDAO {
	//@Autowired
	//private SqlSession sqlSession;
	//위에 코드랑 다른 방법
	//
	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);
	}
}

🌈Step_1-2 테이블 만들기

📢게시글을 저장하기 위한 테이블

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

🌈Step_2-2 DTO 클래스 생성

📃PointBoard.java

※ xyz.itwill10.dto 패키지에 PointBoard.java 클래스 생성

package xyz.itwill10.dto;
//
import lombok.Data;
//
//create table pointboard(num number primary key, writer varchar2(20), subject varchar2(100));
//create sequence pointboard_seq;
@Data
public class PointBoard {
	private int num;
	private String writer;
	private String subject;
}

🌈Step_3-2 매퍼 파일 생성

📃PointBoardMapper.xml

※ xyz.itwill10.mapper 패키지에 PointBoardMapper.xml 파일 생성

<?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>

📃PointBoardMapper.java(인터페이스)

※ xyz.itwill10.mapper 패키지에 PointBoardMapper.java 인터페이스 파일 생성

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();//다중행
}

🌈Step_4-2 DAO 클래스 생성

📃PointBoardDAO.java(인터페이스)

※ xyz.itwill10.dao 패키지에 PointBoardDAO.java 인터페이스 생성

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();//다중행
}

📃PointBoardDAOImpl.java

※ xyz.itwill10.dao 패키지에 PointBoardDAOImpl.java 클래스 생성

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 PointBoardDAOImpl 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();
	}
}

🌈Step_5-1 Service 클래스 생성

📃PointUserService.java(인터페이스)

※ xyz.itwill10.service 패키지에 PointUserService.java 인터페이스 파일 생성

package xyz.itwill10.service;
//
import xyz.itwill10.dto.PointUser;
//
public interface PointUserService  {
	PointUser addPointUser(PointUser user) throws Exception;
}

📃PointUserServiceImpl.java

※ xyz.itwill10.service 패키지에 PointUserServiceImpl.java 클래스 생성

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());
	}
}

🌈Step_6-1 테스트 프로그램 작성(User Service 클래스에 대한)

📃PointUserServiceTest.java

※ xyz.itiwll.controller 패키지에 PointUserServiceTest.java 클래스 생성

package xyz.itwill.controller;
//
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import xyz.itwill10.dto.PointUser;
import xyz.itwill10.service.PointUserService;
//
@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.info(e.getMessage());
		}
		logger.info(addUser.toString());
	}
}

🌈Step_5-2 Service 클래스 생성

📃PointBoardService.java(인터페이스)

※ xyz.itwill10.service 패키지에 PointBoardService.java 인터페이스 파일 생성

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();
}

📢TransactionManager 클래스 사용

📃PointBoardServiceImpl.java

※ xyz.itwill10.service 패키지에 PointBoardServiceImpl.java 클래스 생성

package xyz.itwill10.service;
//
import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import xyz.itwill10.dao.PointBoardDAO;
import xyz.itwill10.dao.PointUserDAO;
import xyz.itwill10.dto.PointBoard;
import xyz.itwill10.dto.PointUser;
//
//Spring Framework의 TransactionManager를 사용하여 트렌젝션을 처리하는 방법 
//1. spring-tx 라이브러리를 프로젝트에 빌드 - 메이븐 : pom.xml
//→ spring-jdbc 라이브러리를 빌드 처리하면 의존 관계에 의해 자동으로 빌드 처리
//2. SpringMVC 프로그램의 Spring Bean Configuration File(root-context.xml)에 TransactionManager 관련 클래스를 Spring Bean으로 등록
//3. SpringMVC 프로그램의 Spring Bean Configuration File(servlet-context.xml)에 트렌젝션 처리를 위한 SpringAOP 설정 
//→ 트렌젝션 처리 기능을 제공받고 싶은 메소드에 @Transactional 어노테이션을 사용하여 작성하면 메소드의 명령 실행시 예외(Exception)가 되면 자동으로 롤백 처리
//→ @Transactional 어노테이션을 사용할 경우 Spring Bean Configuration File(root-context.xml)에	spring-tx.xsd 파일이 제공하는 annotation-driven 엘리먼트를 반드시 설정
//→ 테스트 클래스의 테스트 메소드에  @Transactional 어노테이션을 사용할 경우 명령 실행 후 무조건 롤백 처리
//
@Service
@RequiredArgsConstructor
public class PointBoardServiceImpl implements PointBoardService {
	/*
	@Autowired
	private  PointBoardDAO pointBoardDAO;
	@Autowired
	private  PointUserDAO pointUserDAO;
	*/
	//위에랑 다른 방식
	private final PointBoardDAO pointBoardDAO;
	private final PointUserDAO pointUserDAO;
	//
	//게시글을 전달받아 POINBOARD 테이블에 삽입하고 게시글 작성자에 대한 회원정보를 POINTUSER 테이블에서 검색하여 반환하는 메소드
	//→ 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 포인트가 증가되도록 변경
	@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 테이블에 저장된 회원정보의 포인트가 감소되도록 변경
	@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();
	}
}

🌈Step_6-2 테스트 프로그램 작성(Board Service 클래스에 대한)

📃PointBoardServiceTest.java

※ xyz.itiwll.controller 패키지에 PointBoardServiceTest.java 클래스 생성

package xyz.itwill.controller;
//
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.transaction.annotation.Transactional;
import xyz.itwill10.dto.PointBoard;
import xyz.itwill10.dto.PointUser;
import xyz.itwill10.service.PointBoardService;
//
@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());
	}
	*/
}

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

①빌드 처리

📢spring-tx 라이브러리를 프로젝트에 빌드 - 메이븐 : pom.xml
→ spring-jdbc 라이브러리를 빌드 처리하면 의존 관계에 의해 자동으로 빌드 처리

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

📢SpringMVC 프로그램의 Spring Bean Configuration File(root-context.xml)에 TransactionManager 관련 클래스를 Spring Bean으로 등록

📃root-context.xml

※ WEB-INF/spring 폴더에 root-context.xml 파일 수정

<!-- 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/>

③SpringAOP 설정

📢SpringMVC 프로그램의 Spring Bean Configuration File(servlet-context.xml)에 트렌젝션 처리를 위한 SpringAOP 설정
→ 트렌젝션 처리 기능을 제공받고 싶은 메소드에 @Transactional 어노테이션을 사용하여 작성하면 메소드의 명령 실행시 예외(Exception)가 되면 자동으로 롤백 처리
→ @Transactional 어노테이션을 사용할 경우 Spring Bean Configuration File(root-context.xml)에 spring-tx.xsd 파일이 제공하는 annotation-driven 엘리먼트를 반드시 설정
→ 테스트 클래스의 테스트 메소드에 @Transactional 어노테이션을 사용할 경우 명령 실행 후 무조건 롤백 처리

📃servlet-context.xml

※ WEB-INF/spring/appServlet 폴더에 servlet-context.xml 파일 수정

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

0개의 댓글