[springMVC웹]11. 트랜잭션 매니저 이용하는 법 @Transactional

2024. 8. 5. 17:09· framework/spring mvc 웹사이트제작법
목차
  1. 01. 테이블, DTO
  2. 02. Mapper바인딩
  3. 03. @Repository
  4. 04. @Service
  5. 05. 테스트
  6. 1) 🔨 PointUserServiceTest.java
  7. 2) 🔨[테스트] PointBoardServiceTest.java
반응형
  • 트랜잭션 매니저를 이용하면 사용하지 않으면 다수의 Connection 객체를 사용함
    • 예외가 발생해도 이미 명령들이 COMMIT되어 ROLLBACK 할 수 없음
  • 트랜잭션 매니저를 이용하면 사용하는 하나의 Connection 객체를 사용함
    • 예외가 발생하면 기존에 실행된 명령들을 ROLLBACK 처리 됨

01. 테이블, DTO

🖤[테이블] POINTUSER
--포인트점수가 포함된 USER 테이블
--아이디
--이름
--포인트점수
create table pointuser(id varchar2(20) primary key, name varchar2(30), point number);

desc pointuser;
🖤[테이블] POINTBOARD
--게시글을 저장할 BOARD 테이블
--글번호
--아이디
--내용
create table pointboard(num number primary key, writer varchar2(20), subject varchar2(100));
desc pointboard;

--글번호에 자동 증가값을 제공하기 위한 시퀀스 생성
create sequence pointboard_seq;

 

🖤DTO - PointUser : 테스트를 위해서 toString() 만드는 것을 권장
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;
}
🖤DTO - PointBoard - 테스트를 위해서 toString() 만드는 것을 권장
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;
}

02. Mapper바인딩

💛[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, update, update는 resultType 이나 resultMap 사용 못함 : 무조건 int로 반환받기 때문 -->
	<!-- id와 name만 입력받고, 처음 가입한 회원의 포인트 점수는 0으로 할 것임 -->
	<insert id="insertPointUser">
		insert into pointuser values(#{id},#{name},0)
	</insert>

	<!-- 이 메소드를 호출하면 포인트 증가는 무조건 100으로 -->
	<update id="updatePlusPointUser">
		update pointuser set point=point+100 where id=#{id}
	</update>

	<!-- 이 메소드를 호출하면 포인트 감소는 무조건 100으로 -->
	<update id="updateMinusPointUser">
		update pointuser set point=point-100 where id=#{id}
	</update>

	<!-- 테이블의 컬럼명과 클래스의 필드명이 같기 때문에 자동매핑 이용할 것임 : resultType-->
	<select id="selectPointUser" resultType="PointUser">
		select * from pointuser where id=#{id}
	</select>

</mapper>
💛[Mapper] PointUserMapper.java
package xyz.itwill10.mapper;

import xyz.itwill10.dto.PointUser;

public interface PointUserMapper {
	int insertPointUser(PointUser user); //매개변수의 전달값이 2개 이상이면 객체로 전달 - PointUser객체 혹은 Map 객체 이용도 가능
	int updatePlusPointUser(String id);
	int updateMinusPointUser(String id);
	PointUser selectPointUser(String id);
}
💛[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 사용을 권장 : num 필드에 insert 하기 전에 시퀀스 값 저장해줘! -->
		<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>
💛[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();
}

03. @Repository

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

@RequiredArgsConstructor
@Repository
public class PointUserDAOImpl implements PointUserDAO {

	//방법1.
	//@Autowired
	//private SqlSession sqlSession

	//방법2.
	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);
	}
}
🖤[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();
}
🖤[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;

@Repository
@RequiredArgsConstructor
public class PointBoardDAOImpl implements PointBoardDAO{
	private final SqlSession sqlSession;

	@Override
	public int insertPointBoard(PointBoard board) {
		return sqlSession.getMapper(PointBoardDAO.class).insertPointBoard(board);
	}

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

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

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

04. @Service

🖤[Service] (부모) PointUserService.java
  • Service 클래스가 변경되어도 Controller의 영향을 최소화 할 수 있도록 인터페이스 작성 권장
package xyz.itwill10.service;

import xyz.itwill10.dto.PointUser;

//정형화된 메소드는 없음
//개발자가 생각했을 때 필요한 값을 반환받고 필요한 값을 전달받을 수 있도록 구현하면 됨
public interface PointUserService {
	PointUser addPointUser(PointUser pointUser);
}
🖤[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{
		//삽입되기 전 전달받은 회원정보의 아이디가 중복될 경우 인위적 예외 발생
		//=> 예외를 떠넘겨서 요청 처리 클래스(Controller-Model객체)에서 처리할 것임
    	//=> 그리고 원래는 발생할 예외클래스를 [xyz.itwill10.exception] 예외패키지 내에 따로 만들어야 함
		if(pointUserDAO.selectPointUser(user.getId())!=null) {
			throw new Exception("이미 사용중인 아이디입니다");
		}
		pointUserDAO.insertPointUser(user);
		return pointUserDAO.selectPointUser(user.getId()); //아이디에 해당하는 회원정보가 있는지 검색해봄
	}

	// => map으로 반환받고 싶은 경우 반환값이 다수라면 Map 사용해도 됨
	/*
	@Override
	public Map<String,Object> getPointBoardList() {

		List<PointBoard> pointBoardList = pointBoardDAO.selectPointBoardList();
		List<PointUser> pointUserList = PointUserDAO.selectPointUserList();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put("pointBoardList", pointBoardList);
		map.put("pointUserList", pointUserList);

		return map;
	}
	*/

}
🖤[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();
}
🖤[Service] (자식) PointBoardServiceImpl.java
package xyz.itwill10.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

//만든 메소드
//=> 게시글 삽입(PointBoardDAO)하면 포인트 100증가(PointUserDAO)
//=> 게시글 삭제(PointBoardDAO)하면 포인트 100감소(PointUserDAO)
//=> 존재하는 모든 게시글 검색(PointBoardDAO)

//🌈Spring Framework의 TransactionManager를 사용하여 트랜잭션을 처리하는 방법
//step 1.
//Spring-tx 라이브러리를 프로젝트에 빌드 처리 - 메이븐 : [pom.xml]
//=>Spring-jdbc 라이브러리를 빌드 처리하면 의존 관계에 의해 자동으로 빌드 처리
//step 2.
//SpringMVC 프로그램의 Spring Bean Configuration File(root-context.xml)에 TransactionManager
//관련 클래스를 Spring Bean으로 등록
//step 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 final PointUserDAO PointUserDAO;
	//@Autowired
	//private final PointBoardDAO pointBoardDAO;

	private final PointUserDAO PointUserDAO;
	private final PointBoardDAO pointBoardDAO;

	//게시글을 전달받아 POINTBOARD 테이블에 삽입하고 게시글 작성자에 대한 회원정보를 POINTUSER
	//테이블에서 검색하여 반환하는 메소드
	//=> 게시글 작성자를 전달받아 POINTUSER 테이블에 저장된 회원정보의 point가 증가되도록 변경 처리
	@Override
	@Transactional //트랜잭션 메니저에 의해 관리 받고 싶어요!
	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 테이블에 저장된 회원정보의 point가 감소되도록 변경 처리
	@Override
	@Transactional //트랜잭션 메니저에 의해 관리 받고 싶어요!
	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();
	}

}

05. 테스트

1) 🔨 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 org.springframework.transaction.annotation.Transactional;

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); //PointUser 객체 전달해서 삽입됨
		} catch (Exception e) {
			logger.info(e.getMessage());
		}

		logger.info(addUser.toString()); //아이디, 이름, 포인트 값이 출력됨
	}
	*/

	//테스트 메소드에 @Transactional 쓰고 안쓰고의 차이가 있음!! 확인하기!!
  	//=> 안쓰면 무조건 삽입됨!!
  	//=> 쓰면 삽입 안됨..? ok 났는데도 안됨ㅜ 또 내껏만 그런건가....
	@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());
	}

}

2) 🔨[테스트] 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 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;

	@Test
	public void test1() {
		PointBoard board=new PointBoard();
		//board.setWriter("abc123");
		board.setWriter("xyz789");//게시글 작성자가 회원정보에 존재하지 않도록 작성
		board.setSubject("테스트");

		PointUser user=null;
		try {
			//게시글 작성자가 없는 경우 예외 발생
			//문제점) 예외 발생 전에 실행된 게시글 삽입에 대한 SQL명령은 이미 전달 되어 실행된
			//상태로 POINTBOARD 테이블에 게시글 저장
			// => 게시글 작성자가 존재하지 않는 게시글 - 게시글을 검색하여 출력할 경우 문제가 발생됨
			//해결법) 예외 발생 전 실행된 모든 SQL 명령은 취소되도록 롤백(ROLLBACK) 처리
			// => Spring Framework에서는 Transaction Manager 관련 클래스를 사용하여 "일관성 있는(DAO를 어떤 프레임워크를 쓰든 상관없음!!)"
			//트랜잭션 처리 기능 제공 - SpringAOP(Spring은 내부적으로 Transaction Manager가 알아서 SpringAOP를 사용할 것임)
			user=pointBoardService.addPointBoard(board);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}

		logger.info(pointBoardService.getPointBoardList().toString()); //게시글 목록을 검색해서 기록해줘
		logger.info(user.toString()); //게시글 작성자의 포인트가 잘 증가되었는지 확인해줘
	}

	/*
	@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()); //게시글 작성자의 포인트가 잘 감소되었는지 확인해줘
	}
	*/

}
반응형

'framework > spring mvc 웹사이트제작법' 카테고리의 다른 글

[springMVC웹] 13. 요청 처리 메소드에서 JSON으로 응답하는 법  (0) 2024.08.05
[springMVC웹] 12. 암호화처리, 보안처리(권한처리), 예외처리하는 법  (0) 2024.08.05
[springMVC웹] 10. 모듈 테스트하는 법 (@RunWith, @ContextConfiguration, @WebAppConfiguration, @FixMethodOrder)  (0) 2024.08.05
[springMVC웹] 9. TilesView 관련 환경설정파일 - tiles.xml  (0) 2024.08.05
[springMVC웹] 8. 로그구현체 환경설정파일 - log4j.xml & log4jdbc.log4j2.properties  (0) 2024.08.05
  1. 01. 테이블, DTO
  2. 02. Mapper바인딩
  3. 03. @Repository
  4. 04. @Service
  5. 05. 테스트
  6. 1) 🔨 PointUserServiceTest.java
  7. 2) 🔨[테스트] PointBoardServiceTest.java
'framework/spring mvc 웹사이트제작법' 카테고리의 다른 글
  • [springMVC웹] 13. 요청 처리 메소드에서 JSON으로 응답하는 법
  • [springMVC웹] 12. 암호화처리, 보안처리(권한처리), 예외처리하는 법
  • [springMVC웹] 10. 모듈 테스트하는 법 (@RunWith, @ContextConfiguration, @WebAppConfiguration, @FixMethodOrder)
  • [springMVC웹] 9. TilesView 관련 환경설정파일 - tiles.xml
jeri
jeri
Weekly I learned
JERI LOGWeekly I learned
반응형
jeri
JERI LOG
jeri
전체
오늘
어제
  • 분류 전체보기 (336)
    • KNOU (0)
    • coding_test (71)
    • springboot (2)
    • work (9)
    • docker (3)
    • git (11)
    • network (12)
    • java (70)
      • java (9)
      • oop (12)
      • inheritance (12)
      • realization (7)
      • java.lang (8)
      • java.util & java.text (8)
      • java.awt & java.swing (4)
      • java.io (5)
      • java.net (5)
    • python (0)
    • sql (14)
    • jdbc (6)
    • web (32)
      • html (8)
      • css (8)
      • javascript (16)
    • servlet (9)
    • jsp (31)
    • framework (65)
      • mybatis (10)
      • spring (7)
      • spring IOC(제어의 역행) (8)
      • spring AOP(관점지향) (8)
      • spring DAO (2)
      • spring mvc (14)
      • spring mvc 웹사이트제작법 (16)
    • flutter (1)
    • 개발일지 (0)

블로그 메뉴

  • 홈

공지사항

인기 글

태그

  • 99클럽
  • TIL
  • 개발자취업
  • 오블완
  • 코딩테스트준비
  • 티스토리챌린지
  • 항해99
  • 회사

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
jeri
[springMVC웹]11. 트랜잭션 매니저 이용하는 법 @Transactional
상단으로
loading

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.