반응형
- 트랜잭션 매니저를 이용하면 사용하지 않으면 다수의 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 |