[spring] Transaction & Annotation

sang·2024년 4월 18일

Spring Transaction

트랜잭션

여러 개의 DML 명령문을 하나의 논리적인 작업 단위로 묶어서 관리하는 것
작업들을 한꺼번에 등록(ALL, commit)하거나 취소함(Nothing, rollback)

사용 방법

XML 파일에서 설정
애너테이션 이용

속성

propagation: 트랜잭션 전파 규칙
isolation: 트랜잭션 격리 레벨
realOnly: 읽기 전용 여부
rollbackFor: rollback 예외 타입
timeout: 타임아웃 시간

  • propagation 하위 속성

    • REQUIRED: 진행 중인 트랜잭션 사용 또는 새 트랜잭션 생성
    • MANDATORY: 진행 중인 트랜잭션이 없으면 예외 발생
    • REQUIRED_NEW: 기존 트랜잭션 일시 중지 - 새 트랜잭션 생성 및 작업 - 다시 진행
    • SUPPORTS: 진행 중인 트랜잭션이 있으면 사용
    • NOT_SUPPORTED: 기존 트랜잭션 일지 중지 - 메소드 실행 - 다시 진행
    • NEVER: 진행 중인 트랜잭션이 있으면 예외 발생
    • NESTED: 기존 트랜잭션에 중첩된 트랜잭션에서 메소드 실행 또는 새 트랜잭션 생성
  • isolation 하위 속성

    • DEFAULT: 기본 설정 사용
    • READ_UNCOMMITED: 커밋하지 않은 데이터 읽기 가능
    • READ_COMMITED: 커밋 데이터만 읽기 가능
    • REPEATABLE_READ: 현재 트랜잭션 데이터 미 수정 시 처음 데이터 == 두번째 데이터
    • SERIALIZABLE: 데이터 당 1 트랜잭션


은행 계좌 transaction 실습

계좌 테이블 생성 코드

create table cust_account (
  accountNo varchar2(20) primary key,
  custName varchar2(50),
  balance number(20,4)
);

insert into cust_account(accountNo,custName,balance)
values('70-490-930','홍길동',10000000);

insert into cust_account(accountNo,custName,balance)
values('70-490-911','김유신',10000000);

commit;

select * from cust_account;

pro25/WebContent/WEB-INF/action-servlet.xml

  ...
  <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/views/account/" />
    <property name="suffix" value=".jsp"/>
  </bean>

  <bean id="accController class="com.spring.account.AccountController">
    <property name="methodNameResolver"> <ref local="methodResolver"/> </property>
    <property name="accService" ref="accService"/> <!-- accService bean 주입 -->
  </bean>

  <bean id="methodResolver" class="org.springframework.web.servlet.mvc.multiaction.PropertiesMethodNameResolver">
    <property name="mappings" >
      <props>
        <prop key="/account/sendMoney.do">sendMoney</prop> <!-- sendMoney 메소드 호출 -->
      </props>
    </property>
  </bean>
  
  <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
      <props>
        <prop key="/account/*.do">accController</prop> <!-- accController bean 실행 -->
      </props>
    </property>
  </bean>
</beans>

pro25/WebContent/WEB-INF/config/action-mybatis.xml

  ...
  <bean id="accDAO" class="com.spring.account.AccountDAO">
    <property name="sqlSession" ref="sqlSession" />
  </bean>
  
  <!-- DataSourceTransactionManager: dataSource bean 트랜잭션 적용 -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
  </bean>
  
  <tx:annotation-driven transaction-manager="txManager" /> <!-- 애너테이션으로 transaction 사용 시 필요 -->
</beans>

pro25/WebContent/WEB-INF/config/action-service.xml

  ...
  <!-- accDAO bean 주입 -->
  <bean id="accService" class="com.spring.account.AccountService">
    <property name="accDAO" ref="accDAO"/>
  </bean>
 </beans>

pro25/src/mybatis/mappers/account.xml

...
<mapper namespace="mapper.account">
  <update id="updateBalance1">
  <![CDATA[
    update cust_account
    set balance=balance-5000000

    where
      accountNo = '70-490-930'
  ]]>
  </update>

  <update id="updateBalance2">
    <![CDATA[
      update cust_account
      set balance=balance+5000000

      where
        accountNo = '70-490-911'
      ]]>
  </update>
</mapper>

pro25/src/com/spring/account/AccountController.java

package com.spring.account;
...
public class AccountController extends MultiActionController {
  private AccountService accService;
  
  public void setAccService(AccountService accService) { //setter
    this.accService = accService;
  }
  
  public ModelAndView sendMoney(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mav=new ModelAndView();
    accService.sendMoney();
    mav.setViewName("result");
    
    return mav;
  }
}

pro25/src/com/spring/account/AccountService.java

package com.spring.account;
...

@Transactional(propagation=Propagation.REQUIRED) //모든 메소드에 트랜잭션 적용
public class AccountService {
  private AccountDAO accDAO;
  
  public void setAccDAO(AccountDAO accDAO) { //setter
    this.accDAO = accDAO;
  }
 
  public void sendMoney() throws Exception {
    accDAO.updateBalance1(); //sql1 실행
    accDAO.updateBalance2(); //sql2 실행
  }
}

pro25/src/com/spring/account/AccountDAO.java

package com.spring.account;
...
public class AccountDAO {
  private SqlSession sqlSession;
  
  public void setSqlSession(SqlSession sqlSession) { //setter
    this.sqlSession = sqlSession;
  }
 
  public void updateBalance1() throws DataAccessException {
    sqlSession.update("mapper.account.updateBalance1"); //송금하는 계좌 금액 차감
  }
 
  public void updateBalance2() throws DataAccessException {
    sqlSession.update("mapper.account.updateBalance2"); //송금받는 계좌 금액 증액
  }
}


Spring Annotation

애너테이션

자바 코드 관련 설정 바로 가능

애너테이션 관련 클래스

XML 설정 파일에서 bean 설정 필요

  • DefaultAnnotationHandlerMapping: 클래스 레벨에서 @RequestMapping 처리
  • AnnotationMethodHandlerAdapter: 메소드 레벨에서 @RequestMapping 처리

<context:component-scan> 태그

@Controller: 지정 클래스 Controller bean 자동 변환
@Service: 지정 클래스 Service bean 자동 변환
@Repository: 지정 클래스 DAO bean 자동 변환
@Component: 지정 클래스 bean 자동 변환

@RequestParam

매개변수를 자동으로 가져오는 애너테이션
getParameter 불필요

required 속성
true (default): 해당 매개변수 전달 필수, 없으면 예외 발생
false: 해당 매개변수 값이 없으면 null

pro26/WebContent/WEB-INF/action-servlet.xml

...
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
  <property name="prefix" value="/WEB-INF/views/test/" />
  <property name="suffix" value=".jsp"/>
</bean>

<!-- DefaultAnnotationHandlerMapping -->
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>

<!-- AnnotationMethodHandlerAdapter -->
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
  <context:component-scan base-package="com.spring"/> <!-- 애너테이션 적용 범위 설정 -->
</beans>

pro26/src/com/spring/ex01/MainController.java

package com.spring.ex01; //<component-scan>에서 설정한 패키지 또는 하위 패키지
...

/* 요청 단계: 1단계 */
@Controller("mainController") //bean 자동 변환
@RequestMapping("/test") //'/test' 요청 시 mainController bean에 전달
public class MainController {

  /* 요청 단계: 2단계 */
  @RequestMapping(value="/main1.do", method=RequestMethod.GET) //요청 구분
  public ModelAndView main1(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mav=new ModelAndView();
    mav.addObject("msg","main1"); //전달할 데이터 값
    mav.setViewName("main");
    
    return mav;
  }  

  /* 요청 단계: 2단계 */
  @RequestMapping(value="/main2.do", method = RequestMethod.GET) //요청 구분
  public ModelAndView main2(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mav=new ModelAndView();
    mav.addObject("msg","main2"); //전달할 데이터 값
    mav.setViewName("main");
    
    return mav;
  }
}

pro26/WebContent/WEB-INF/views/test/main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<% request.setCharacterEncoding("UTF-8"); %>

<html>
<head>
  <meta charset="UTF-8">
  <title>결과창</title>
</head>

<body>
  <h1>안녕하세요!!</h1>
  <h1>${msg <!-- 컨트롤러에서 넘어온 데이터 -->} 페이지입니다!!</h1>
</body>
</html>


로그인 annotation 실습

pro26/WebContent/WEB-INF/web.xml

    ...
    <filter>
      <filter-name>encodingFilter</filter-name>
      <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
      <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
      </init-param>
    </filter>
    
    <filter-mapping>
      <filter-name>encodingFilter</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...

pro26/src/com/spring/ex02/LoginController.java

package com.spring.ex02;
...

@Controller("loginController")
public class LoginController {

  @RequestMapping(value = { "/test/loginForm.do", "/test/loginForm2.do" }, method={RequestMethod.GET})
  public ModelAndView loginForm(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ModelAndView mav=new ModelAndView();
    mav.setViewName("loginForm");
    
    return mav;
  }

  @RequestMapping(value = "/test/login.do", method {RequestMethod.GET, RequestMethod.POST})
  public ModelAndView login(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("utf-8");
    ModelAndView mav=new ModelAndView();
    mav.setViewName("result");
    String userID=request.getParameter("userID");
    String userName =request.getParameter("userName");
    mav.addObject("userID", userID);
    mav.addObject("userName", userName);
    
    return mav;
  }
}

pro26/WebContent/WEB-INF/test/loginForm.jsp

...
  <form method="post" action="${contextPath}/test/login.do">
  <table width="400">
  <tr>
    <td>아이디 <input type="text" name="userID" size="10"></td>
  </tr>
  <tr>
    <td>이름 <input type="text" name="userName" size="10"></td>
  </tr>
...

pro26/WebContent/WEB-INF/test/result.jsp

...
  <h1>아이디 : ${userID}</h1>
  <h1>이름 : ${userName}</h1>
  ...

pro26/src/com/spring/ex02/LoginController.java

package com.spring.ex02;
...

@Controller("loginController")
public class LoginController {
...
  @RequestMapping(value = "/test/login.do", method = {RequestMethod.GET, RequestMethod.POST})
  public ModelAndView login (
    @RequestParam("userID") String userID, //매개변수가 userID일 경우 변수 userID 설정
    @RequestParam(value="userName", required=true) String userName, //매개변수가 userName일 경우 변수 userName 설정
    @RequestParam(value="email", required=false) String email, //매개변수가 email일 경우 변수 userName 설정
    HttpServletRequest request, HttpServletResponse response
  ) throws Exception {
    
    request.setCharacterEncoding("utf-8");
    ModelAndView mav = new ModelAndView();
    mav.setViewName("result");
    
    System.out.println("userID: "+userID);
    System.out.println("userName: "+userName);
    System.out.println("email: "+email);
    mav.addObject("userID", userID);
    mav.addObject("userName", userName);
    
    return mav;
  }
}

pro26/WebContent/WEB-INF/test/loginForm.jsp

...
<form method="post" action="${contextPath}/test/login2.do">
  <input type="hidden" name="email" value="hong@test.com" /> <!-- @RequestParam email -->
  
  <table width="400">
    <tr>
      <td>아이디 <input type="text" name="userID" size="10"></td> <!-- @RequestParam userID -->
    </tr>
    <tr>
      <td>이름 <input type="text" name="userName" size="10"></td> <!-- @RequestParam userName -->
    </tr>
...

Map 매개변수

pro26/src/com/spring/ex02/LoginController.java

package com.spring.ex02;
...

@Controller("loginController")
public class LoginController {
...
  @RequestMapping(value = "/test/login.do", method = {RequestMethod.GET, RequestMethod.POST})
  public ModelAndView login (
    @RequestParam Map<String, String> info, HttpServletRequest request, HttpServletResponse response
  ) throws Exception {
    
    /* 테스트용 */
    String userID = info.get("userID");
    String userName = info.get("userName");
    System.out.println("userID: "+userID);
    System.out.println("userName: "+userName);
    
    mav.addObject("info", info);
    mav.setViewName("result");
    
    return mav;
  }
}

pro26/WebContent/WEB-INF/views/test/result.jsp

...
<h1>아이디 : ${info.userID} </h1>
<h1>이름 : ${info.userName} </h1>
...

VO 매개변수

pro26/src/com/spring/ex02/LoginController.java

package com.spring.ex02;
...
@Controller("loginController")
public class LoginController {
  ...
  @RequestMapping(value = "/test/login.do", method = { RequestMethod.GET, RequestMethod.POST })
  public ModelAndView login (  @ModelAttribute("info") LoginVO loginVO, HttpServletRequest request, HttpServletResponse response  ) throws Exception {
    //info만으로 jsp에서 바로 접근 가능
    
    request.setCharacterEncoding("utf-8");
    
    ModelAndView mav = new ModelAndView();
    System.out.println("userID: "+loginVO.getUserID());
    System.out.println("userName: "+loginVO.getUserName());
    mav.setViewName("result");
    
    return mav;
  }
}

pro26/src/com/spring/ex02/LoginVO.java

package com.spring.ex02;

public class LoginVO {
  private String userID;
  private String userName;
  
  //각 속성에 대한 getter/setter 메서드
  ...
}

pro26/WebContent/WEB-INF/views/test/result.jsp

 ...
  <h1>아이디 : ${info.userID} </h1> <!-- ModelAttribute info -->
  <h1>이름 : ${info.userName} </h1>
  ...


Model 바인딩

별도의 뷰 정보를 전달할 필요가 없을 때 사용

pro26/src/com/spring/ex02/LoginController.java

package com.spring.ex02;
import org.springframework.ui.Model;
...

@Controller("loginController")
public class LoginController {
  ...
  @RequestMapping(value = "/test/login5.do", method = {RequestMethod.GET, RequestMethod.POST})
  public String login(Model model, HttpServletRequest request, HttpServletResponse response) throws Exception {
    //메소드 호출 시 Model 클래스 객체 생성
    
    request.setCharacterEncoding("utf-8");
    
    /* model에 데이터 바인딩 */
    model.addAttribute("userID", "hong");
    model.addAttribute("userName", "홍길동");
    
    return "result";
  }
}

pro26/WebContent/WEB-INF/views/test/result.jsp

...
<h1>아이디 : ${userID}</h1>
<h1>이름 : ${userName}</h1>
...

@Autowired

클래스 bean을 자바 코드에서 직접 생성할 수 있도록 하는 bean 자동 주입 애너테이션

bean 자동 생성 애너테이션

@Controller: controller bean
@Service: service bean
@Repository: DAO bean
@Component: VO bean


pro26/WebContent/WEB-INF/web.xml

...
  <listener>
    <listener-class> org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>
      /WEB-INF/config/action-mybatis.xml
    </param-value>
  </context-param>
...

pro26/WebContent/WEB-INF/action-servlet.xml

  ...
  <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/member/" /> <!-- jsp 경로 설정 -->
    <property name="suffix" value=".jsp"/>
  </bean>

  <bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
  <bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
  <context:component-scan base-package="com.spring"/>
</beans>

pro26/WebContent/WEB-INF/config/action-mybatis.xml

스프링에서 제공하는 클래스 bean의 경우 xml 설정 필요

...
  <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource">
    <property name="driver" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
  </bean>

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis/models/modelConfig.xml" />
    <property name="mapperLocations" value="classpath:mybatis/mappers/*.xml" />
  </bean>

  <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
  </bean>
  
  <!-- MemberDAO 설정 불필요 -->
</beans>

pro26/src/com/spring/member/controller/MemberControllerImpl.java

package com.spring.member.controller;
...

@Controller("memberController") //memberController bean 자동 생성
public class MemberControllerImpl implements MemberController {
  /* memberService bean 자동 주입 */
  @Autowired
  private MemberService memberService;
  
  /* memberVO bean 자동 주입 */
  @Autowired
  private MemberVO memberVO;

  @Override
  @RequestMapping(value="/member/listMembers.do", method = RequestMethod.GET)
  public ModelAndView listMembers(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String viewName = getViewName(request);
    List membersList = memberService.listMembers();
    ModelAndView mav = new ModelAndView(viewName);
    mav.addObject("membersList", membersList);
    
    return mav;
  }
  
  @Override
  @RequestMapping(value="/member/addMember.do", method = RequestMethod.POST)
  public ModelAndView addMember(@ModelAttribute("member") MemberVO member, HttpServletRequest request, HttpServletResponse response) throws Exception { //MemberVO 객체 사용
    request.setCharacterEncoding("utf-8");
    int result = 0;
    result = memberService.addMember(member); //memberVO 객체 전달 및 회원 등록
    ModelAndView mav = new ModelAndView("redirect:/member/listMembers.do");
    
    return mav;
  }
  
  @Override
  @RequestMapping(value="/member/removeMember.do", method = RequestMethod.GET)
  public ModelAndView removeMember(@RequestParam("id") String id, HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("utf-8");
    memberService.removeMember(id);
    ModelAndView mav = new ModelAndView("redirect:/member/listMembers.do");
    
    return mav;
  }
  
  @RequestMapping(value = "/member/*Form.do", method = RequestMethod.GET) //요청명 정규식 검사
  public ModelAndView form(HttpServletRequest request, HttpServletResponse response) throws Exception {
    String viewName = getViewName(request);
    ModelAndView mav = new ModelAndView();
    mav.setViewName(viewName);
    
    return mav;
  }
...

pro26/src/com/spring/member/service/MemberServiceImpl.java

package com.spring.member.service;
...

@Service("memberService") //memberService bean 자동 생성
@Transactional(propagation = Propagation.REQUIRED)
public class MemberServiceImpl implements MemberService {
  /* memberDAO bean 자동 주입 */
  @Autowired
  private MemberDAO memberDAO;
  
  @Override
  public List listMembers() throws DataAccessException {
    List membersList = null;
    membersList = memberDAO.selectAllMemberList();
    return membersList;
  }
  
  @Override
  public int addMember(MemberVO member) throws DataAccessException {
    return memberDAO.insertMember(member);
  }

  @Override
  public int removeMember(String id) throws DataAccessException {
    return memberDAO.deleteMember(id);
  }
}

pro26/src/com/spring/member/dao/MemberDAOImpl.java

package com.spring.member.dao;
...
@Repository("memberDAO") //memberDAO bean 자동 생성
public class MemberDAOImpl implements MemberDAO {
  /* sqlSession bean 자동 주입 */
  @Autowired
  private SqlSession sqlSession;
  
  @Override
  public List selectAllMemberList() throws DataAccessException {
    List<MemberVO> membersList = null;
    membersList = sqlSession.selectList("mapper.member.selectAllMemberList");
    return membersList;
  }

  @Override
  public int insertMember(MemberVO memberVO) throws DataAccessException {
    int result = sqlSession.insert("mapper.member.insertMember", memberVO);
    return result;
  }

  @Override
  public int deleteMember(String id) throws DataAccessException {
    int result = sqlSession.delete("mapper.member.deleteMember", id);
    return result;
  }
}

pro26/src/com/spring/member/vo/MemberVO.java

package com.spring.member.vo;

import org.springframework.stereotype.Component;

@Component("memberVO") //memberVO bean 자동 생성
public class MemberVO {
  private String id;
  private String pwd;
  private String name;
  private String email;
  private Date joinDate;

  public MemberVO() { }
  
  public MemberVO(String id, String pwd, String name, String email) {
    this.id = id;
    this.pwd = pwd;
    this.name = name;
    this.email = email;
  }
  
  //getter, setter
  ...


*자바 웹을 다루는 기술

profile
CS 메모장

0개의 댓글