Spring - 11. Transactions AOP

어느 레스토랑에서 메뉴와 갯수를 주문하여 테이블에 저장하는 테이블이다

하지만 만약 저장에 실패했을경우 트랜잭션을 Rollback 처리해야한다

그것을 스프링 AOP기능 이용하여 실습하였습니다.

 

우선 테이블 목록입니다.

TBL_ORDER

 

TBL_ORDER_MENU

 

각 객체를 저장할수있는 VO 파일입니다.

Order.java

package com.memory4.javaconfig.model.vo;

import java.util.List;

public class Order {
	private int code;
	private String date;
	private String time;
	private int orderPrice;
	private List<OrderMenu> menuList;
	
	public Order() { }
	public Order(int code, String date, String time, int orderPrice) {
		super();
		this.code = code;
		this.date = date;
		this.time = time;
		this.orderPrice = orderPrice;
	}
	public int getCode() {
		return code;
	}
	public void setCode(int code) {
		this.code = code;
	}
	public String getDate() {
		return date;
	}
	public void setDate(String date) {
		this.date = date;
	}
	public String getTime() {
		return time;
	}
	public void setTime(String time) {
		this.time = time;
	}
	public int getOrderPrice() {
		return orderPrice;
	}
	public void setOrderPrice(int orderPrice) {
		this.orderPrice = orderPrice;
	}
	
	public List<OrderMenu> getMenuList() {
		return menuList;
	}
	public void setMenuList(List<OrderMenu> menuList) {
		this.menuList = menuList;
	}
	@Override
	public String toString() {
		return "Order [code=" + code + ", date=" + date + ", time=" + time + ", orderPrice=" + orderPrice
				+ ", menuList=" + menuList + "]";
	}
	
}

 

OrderMenu.java

package com.memory4.javaconfig.model.vo;

public class OrderMenu {
	private int orderCode;
	private int menuCode;
	private int amount;
	
	public OrderMenu() { 	}

	public OrderMenu(int orderCode, int menuCode, int amount) {
		super();
		this.orderCode = orderCode;
		this.menuCode = menuCode;
		this.amount = amount;
	}

	public int getOrderCode() {
		return orderCode;
	}

	public void setOrderCode(int orderCode) {
		this.orderCode = orderCode;
	}

	public int getMenuCode() {
		return menuCode;
	}

	public void setMenuCode(int menuCode) {
		this.menuCode = menuCode;
	}

	public int getAmount() {
		return amount;
	}

	public void setAmount(int amount) {
		this.amount = amount;
	}

	@Override
	public String toString() {
		return "OrderMenu [orderCode=" + orderCode + ", menuCode=" + menuCode + ", amount=" + amount + "]";
	}
	
}

 

이아래부터는 애너테이션버전과 xml버전으로 나뉩니다.

1. xml 버전

Application.java(main)

메인 클래스에서 실행시

주문 메뉴코드, 주문 수량 을 입력하여 List<OrderMenu> 에 저장하고 

사용자가 주문을 더하지않는다고 'N' 의값을 입력하게 되면 반복이 종료되어 Order 객체에 값이 필드에 저장됩니다.

 

저장한 Order 객체의값을 Service에 보내어 

비즈니스로직 실행후 1또는 0의 값이 돌아오게되고

값이 0일시 실패 하였다는 메세지가 출력되고,

값이 1일시 성공 하였다는 메세지가 출력됩니다.

package com.memory4.xmlconfig.run;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.memory4.xmlconfig.model.service.OrderService;
import com.memory4.xmlconfig.model.vo.Order;
import com.memory4.xmlconfig.model.vo.OrderMenu;

public class Application {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		ApplicationContext context = new GenericXmlApplicationContext("com/memory4/xmlconfig/config/spring-context.xml");
		
		List<OrderMenu> orderMenuList = new ArrayList<>();
		do {
			System.out.println("========== 레스토랑 음식 주문 서비스 ==========");
			System.out.print("어떤 메뉴를 주문하시겠습니까?(코드입력) : ");
			int menuCode = sc.nextInt();
			
			System.out.print("주문 수량을 입력해주세요 : ");
			int amount = sc.nextInt();
			
			sc.nextLine();
			System.out.print("다른 메뉴를 추가로 주문하시겠습니까?(Y/N) : ");
			char orderYN = sc.nextLine().toUpperCase().charAt(0);
			
			OrderMenu orderMenu = new OrderMenu();
			orderMenu.setMenuCode(menuCode);
			orderMenu.setAmount(amount);
			
			orderMenuList.add(orderMenu);

			if(orderYN != 'Y') break;
			
		}while(true);
		
		Order order = new Order();
		SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");
		SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
		
		String dateString = date.format(new Date());
		String timeString = time.format(new Date());
		
		order.setDate(dateString);
		order.setTime(timeString);
		order.setMenuList(orderMenuList);
		
		OrderService orderService = context.getBean("orderService", OrderService.class);
		
		System.out.println(order);
		
		int result = orderService.register(order);
		
		if(result > 0) {
			System.out.println("메뉴 추가에 성공하였습니다.");
		}else {
			System.out.println("메뉴 추가에 실패하였습니다.");
		}
	}

}

OrderService.interface

package com.memory4.xmlconfig.model.service;

import com.memory4.xmlconfig.model.vo.Order;

public interface OrderService {
	int register(Order order);
}

 

OrderServiceImpl.java

스프링 IOC로 관리위해 @Componet 의 하위 애너테이션인 Service로 빈을 등록하고 

빈 이름은 "orderService" 입니다.

전달받은 Order객체를 이용해 우선 TBL_ORDER 테이블먼저 INSERT 진행합니다.

그후 INSERT진행후 돌아온값이 0이상이면 그다음 INSERT작업 진행합니다.

LIST에 담겨있는 SIZE() 만큼 반복문으로 INSERT를 진행하고

만약 result1 과 result2의 값이 0이상이면 1반환하고 

값이 하나라도 0이면 0을 반환합니다.(1:성공/0:실패)

package com.memory4.xmlconfig.model.service;

import java.util.List;

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

import com.memory4.xmlconfig.model.dao.OrderMapper;
import com.memory4.xmlconfig.model.vo.Order;
import com.memory4.xmlconfig.model.vo.OrderMenu;

@Service("orderService")
public class OrderServiceImpl implements OrderService{
	
	private OrderMapper orderMapper;
	
	@Autowired
	public OrderServiceImpl(OrderMapper orderMapper) {
		this.orderMapper = orderMapper;
	}
	
	@Override
	public int register(Order order) {
		
		int result1 = orderMapper.insertMenu(order);
		
		List<OrderMenu> orderMenuList = order.getMenuList();
		
		int result2 = 0;
		for(OrderMenu orderMenu : orderMenuList) {
			result2 += orderMapper.insertOrderMenu(orderMenu);
		}
		
		int result = 0;
		if(result1 > 0 && result2 == orderMenuList.size()) {
			result = 1;
		}
		
		return result;
	}

}

 

OrderMapper.interface

package com.memory4.xmlconfig.model.dao;

import com.memory4.xmlconfig.model.vo.Order;
import com.memory4.xmlconfig.model.vo.OrderMenu;

public interface OrderMapper {
	int insertMenu(Order order);
	
	int insertOrderMenu(OrderMenu orderMenu);
}

 

OrderMapper.xml

mybatis 프레임워크 의 쿼리문을 작성한 파일입니다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.memory4.xmlconfig.model.dao.OrderMapper">
	<insert id="insertMenu" parameterType="com.memory4.xmlconfig.model.vo.Order">
		INSERT
		  INTO TBL_ORDER
		  	   (
		  	   		ORDER_CODE
		  	   	  , ORDER_DATE
		  	   	  , ORDER_TIME
		  	   	  , TOTAL_ORDER_PRICE
		  	   )
		VALUES 
			   (
			   		SEQ_ORDER_CODE.NEXTVAL
			   	  , #{date}
			   	  , #{time}
			   	  , #{orderPrice}
			   )
	</insert>
	
	<insert id="insertOrderMenu" parameterType="com.memory4.xmlconfig.model.vo.OrderMenu">
		<selectKey resultType="_int" keyProperty="orderCode" order="BEFORE">
			SELECT
				   SEQ_ORDER_CODE.CURRVAL
			  FROM DUAL
		</selectKey>
		INSERT
		  INTO TBL_ORDER_MENU
		       (
		       		ORDER_CODE
		       	  , MENU_CODE
		       	  , ORDER_AMOUNT
		       )
	    VALUES 
	    	   (
	    	   		#{orderCode}
	    	   	  , #{menuCode}
	    	   	  , #{amount}
	    	   )
	</insert>
</mapper>

 

mybatis-config.xml

mybatis 기본설정 xml파일입니다

스프링사용시 mybatis 연결설정은 이 파일에서 설정하지 않고 spring-context.xml 파일에서 설정을 진행합니다.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

 

spring-context.xml

스프링 기본설정 xml 파일입니다.

우선 커넥션 연결정보는 properties에 저장해두었고, 그 저장되어있는값을 

읽어와 BasicDataSource 객체를 빈에등록하고 프로퍼티의 값을 setter 이용해 값을 주입합니다.

SqlSessionFactoryBean객체를 빈에 등록하고 dataSource 객체를 주입하고, mybatis 설정파일 위치를 지정해줍니다.

실질적으로 커넥션할때 사용하는 SqlSessionTemplate 객체를 빈에 등록하고 생성자이용하여 위에 생성한 Bean 객체를 주입해줍니다.

1. 데이터소스 생성

2. 팩토리 빈 생성

3. 템플릿 생성 

이순서로 작업하면 편리합니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

	<context:property-placeholder location="connection-info.properties"/>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="${oracle.dev.driver}"/>
		<property name="url" value="${oracle.dev.url}"/>
		<property name="username" value="${oracle.dev.username}"/>
		<property name="password" value="${oracle.dev.password}"/>
		<property name="defaultAutoCommit" value="false"/>
	</bean>
	
	<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="com/memory4/xmlconfig/config/mybatis-config.xml"/>
	</bean>
	
	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
	</bean>
	
	<context:component-scan base-package="com.memory4.xmlconfig"/>
	
	<mybatis-spring:scan base-package="com.memory4.xmlconfig.model.dao"/>
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<aop:aspectj-autoproxy proxy-target-class="true"/>
	
	<tx:advice id="txVisor" transaction-manager="transactionManager" >
		<tx:attributes>
			<tx:method name="select*" read-only="true" rollback-for="Exception"/>
			<tx:method name="register*" propagation="REQUIRED" isolation="SERIALIZABLE" rollback-for="Exception"/>
			<tx:method name="delete*" propagation="REQUIRED" isolation="SERIALIZABLE" rollback-for="Exception"/>
			<tx:method name="modify*" propagation="REQUIRED" isolation="SERIALIZABLE" rollback-for="Exception"/>
		</tx:attributes>
	</tx:advice>
	
	<aop:config proxy-target-class="true">
		<aop:pointcut expression="execution(* com.memory4.xmlconfig.model.service..*(..))" id="serviceMethod"/>
		<aop:advisor id="transactionAdvisor" advice-ref="txVisor" pointcut-ref="serviceMethod"/>
	</aop:config>
	
	
	
	

</beans>

 

실행화면

만약 메뉴를 추가하다가 오류가나게 되면 rollback되는처리는 위에 spring-context.xml 파일의 <aop:> <tx:> 부분으로 처리하였습니다.

 

 

2. xml+애너테이션 버전

설명은 애너테이션에 작성하였습니다.

방법은비슷하지만 애너테이션이 조금 더 소스가 간단합니다.

 

Application.java(main)

package com.memory4.javaconfig.run;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Scanner;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.GenericXmlApplicationContext;

import com.memory4.javaconfig.model.service.OrderService;
import com.memory4.javaconfig.model.vo.Order;
import com.memory4.javaconfig.model.vo.OrderMenu;


public class Application {

	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		
		ApplicationContext context = new GenericXmlApplicationContext("com/memory4/javaconfig/config/spring-context.xml");
		
		List<OrderMenu> orderMenuList = new ArrayList<>();
		do {
			System.out.println("========== 레스토랑 음식 주문 서비스 ==========");
			System.out.print("어떤 메뉴를 주문하시겠습니까?(코드입력) : ");
			int menuCode = sc.nextInt();
			
			System.out.print("주문 수량을 입력해주세요 : ");
			int amount = sc.nextInt();
			
			sc.nextLine();
			System.out.print("다른 메뉴를 추가로 주문하시겠습니까?(Y/N) : ");
			char orderYN = sc.nextLine().toUpperCase().charAt(0);
			
			OrderMenu orderMenu = new OrderMenu();
			orderMenu.setMenuCode(menuCode);
			orderMenu.setAmount(amount);
			
			orderMenuList.add(orderMenu);

			if(orderYN != 'Y') break;
			
		}while(true);
		
		Order order = new Order();
		SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");
		SimpleDateFormat time = new SimpleDateFormat("HH:mm:ss");
		
		String dateString = date.format(new Date());
		String timeString = time.format(new Date());
		
		order.setDate(dateString);
		order.setTime(timeString);
		order.setMenuList(orderMenuList);
		
		OrderService orderService = context.getBean("orderService", OrderService.class);
		
		System.out.println(order);
		
		int result = orderService.register(order);
		
		if(result > 0) {
			System.out.println("메뉴 추가에 성공하였습니다.");
		}else {
			System.out.println("메뉴 추가에 실패하였습니다.");
		}
	}

}

OrderService.interface

package com.memory4.javaconfig.model.service;

import com.memory4.javaconfig.model.vo.Order;

public interface OrderService {
	int register(Order order);
}

OrderServiceImpl.java

package com.memory4.javaconfig.model.service;

import java.util.List;

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

import com.memory4.javaconfig.model.dao.OrderMapper;
import com.memory4.javaconfig.model.vo.Order;
import com.memory4.javaconfig.model.vo.OrderMenu;

@Service("orderService")
public class OrderServiceImpl implements OrderService{
	
	private OrderMapper orderMapper;
	
	@Autowired
	public OrderServiceImpl(OrderMapper orderMapper) {
		this.orderMapper = orderMapper;
	}
	
	@Override
	@Transactional(isolation = Isolation.SERIALIZABLE, propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
	public int register(Order order) {
		
		int result1 = orderMapper.insertMenu(order);
		
		List<OrderMenu> orderMenuList = order.getMenuList();
		
		int result2 = 0;
		for(OrderMenu orderMenu : orderMenuList) {
			result2 += orderMapper.insertOrderMenu(orderMenu);
		}
		
		int result = 0;
		if(result1 > 0 && result2 == orderMenuList.size()) {
			result = 1;
		}
		
		return result;
	}

}

 

OrderMapper.interface

package com.memory4.javaconfig.model.dao;

import com.memory4.javaconfig.model.vo.Order;
import com.memory4.javaconfig.model.vo.OrderMenu;

public interface OrderMapper {
	int insertMenu(Order order);
	
	int insertOrderMenu(OrderMenu orderMenu);
}

OrderMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.memory4.javaconfig.model.dao.OrderMapper">
	<insert id="insertMenu" parameterType="com.memory4.javaconfig.model.vo.Order">
		INSERT
		  INTO TBL_ORDER
		  	   (
		  	   		ORDER_CODE
		  	   	  , ORDER_DATE
		  	   	  , ORDER_TIME
		  	   	  , TOTAL_ORDER_PRICE
		  	   )
		VALUES 
			   (
			   		SEQ_ORDER_CODE.NEXTVAL
			   	  , #{date}
			   	  , #{time}
			   	  , #{orderPrice}
			   )
	</insert>
	
	<insert id="insertOrderMenu" parameterType="com.memory4.javaconfig.model.vo.OrderMenu">
		<selectKey resultType="_int" keyProperty="orderCode" order="BEFORE">
			SELECT
				   SEQ_ORDER_CODE.CURRVAL
			  FROM DUAL
		</selectKey>
		INSERT
		  INTO TBL_ORDER_MENU
		       (
		       		ORDER_CODE
		       	  , MENU_CODE
		       	  , ORDER_AMOUNT
		       )
	    VALUES 
	    	   (
	    	   		#{orderCode}
	    	   	  , #{menuCode}
	    	   	  , #{amount}
	    	   )
	</insert>
</mapper>

 

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

spring-context.xml

xml버전과 다른점은 <tx> 접두사를 애너테이션 방식으로 바꾼것이고

ServiceImpl.java에서 확인할수있습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

	<context:property-placeholder location="connection-info.properties"/>
	
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="driverClassName" value="${oracle.dev.driver}"/>
		<property name="url" value="${oracle.dev.url}"/>
		<property name="username" value="${oracle.dev.username}"/>
		<property name="password" value="${oracle.dev.password}"/>
		<property name="defaultAutoCommit" value="false"/>
	</bean>
	
	<bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource"/>
		<property name="configLocation" value="com/memory4/javaconfig/config/mybatis-config.xml"/>
	</bean>
	
	<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
		<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"/>
	</bean>
	
	<context:component-scan base-package="com.memory4.javaconfig"/>
	
	<mybatis-spring:scan base-package="com.memory4.javaconfig.model.dao"/>
	
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"/>
	</bean>
	
	<aop:aspectj-autoproxy proxy-target-class="true"/>
	
	<tx:annotation-driven transaction-manager="transactionManager"/>
	
	
	

</beans>

 

실행화면

'프로그래밍 > Spring' 카테고리의 다른 글

Spring - 12. pom.xml 기본설정  (0) 2021.10.26
Spring - 12. MAVEN  (0) 2021.10.26
Spring - 10. Transactions  (0) 2021.10.25
Spring - 9. mapperscan  (0) 2021.10.25
Spring - 6_2. Spring JDBC (XML)  (0) 2021.10.25
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유