📢회원정보를 저장하는 테이블
create table pointuser(id varchar2(20) primary key, name varchar2(30), point number);
📃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; }
📃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); }
📃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); } }
📢게시글을 저장하기 위한 테이블
create table pointboard(num number primary key, writer varchar2(20), subject varchar2(100)); create sequence pointboard_seq;
📃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; }
📃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();//다중행 }
📃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(); } }
📃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()); } }
📃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()); } }
📃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(); }
📃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(); } }
📃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()); } */ }
①빌드 처리
📢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> -->