TIL/JDBC

[JDBC] Transaction, Auto Commit, Service, View

yndev 2022. 2. 5. 23:21

Auto Commit

출력문에서 autoCommit 기본 설정 값을 실행해보니 true로 확인된다.

public class Application1 {

	public static void main(String[] args) {

		Connection con = getConnection();
		
		try {
			System.out.println("autoCommit의 현재 설정 값 : " + con.getAutoCommit());
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
	}

}

 

insertMenu쿼리문 작성해놓음

 

위의 실행 화면에서 insertMenu 실행해주기

public class Application1 {

	public static void main(String[] args) {

		Connection con = getConnection();
		
		try {
			System.out.println("autoCommit의 현재 설정 값 : " + con.getAutoCommit());
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		PreparedStatement pstmt = null;
		int result = 0;
		
		Properties prop = new Properties();
		
		try {
			prop.loadFromXML(new FileInputStream("mapper/menu-query.xml"));
			
			String query = prop.getProperty("insertMenu");
			
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, "성게알비빔밥");
			pstmt.setInt(2, 25000);
			pstmt.setInt(3, 4);
			pstmt.setString(4, "Y");
			
			result = pstmt.executeUpdate();
			
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
			close(con);
		}
		
		if(result > 0 ) {
			System.out.println("메뉴 등록 성공!");
		} else {
			System.out.println("메뉴 등록 실패!");
		}
	}

}

 

getConnection()메소드가 있는 JDBCTemplate에서 

프로그램 내에서 commit과 rollback을 판단하여 수행하고자 하므로 setAutoCommit(false)로 설정해준다.

 

이후 실행해주면 AutoCommit 기본설정 false로 된 것 확인됨

 

pstmt1, 2 두 개의 구문을 같이 실행하기 때문에 insertCategory 쿼리문 하나 더 추가 

 

pstmt1에는 insertCategory, pstmt2에는 위에서 작성한 insertMenu를 실행한다.

현재는 입력한 값들이 오류날게 없어서 정상적으로 작동된다.

 

TBL_CATEGORY에 존재하지 않는 CATEGORY_CODE를 TBL_MENU 테이블의 CATEGORY_CODE 값으로 삽입하려고 하면 부모 키를 찾지 못하는 외래키 제약조건 위반 오류가 발생하도록 일부러 값을 틀리게 작성했다.

 

여기서 reusult1은 1이 되고, result2는 0이 되니까 둘 중 한 가지만 제대로 수행이 되고 하나는 insert가 안되는 상황이다.

 

트랜잭션(논리적인 기능 수행 단위) 관리를 위해 2개의 insert가 모두 잘 수행됐는지 판단하여

잘 수행됐을 경우 commit, 둘 중 하나라도 잘 동작하지 않았을 경우 rollback을 수행한다.

 

여기서 commit과 rollback은 이전에 만들어준 적이 없기 때문에 JDBCTemplate에서 추가로 메소드를 작성한다.

자동 commit에서 수동 commit으로 설정 변경 후 코드 추가

 

오류가 나는 콘솔창이 뜨고, 오라클에서 확인해보면 두 개의 값 모두 insert되지 않았다.


Service의 역할

1. Connection 생성

2. DAO의 메소드 호출

3. 트랜잭션의 제어

4. Connection 닫기

 

MenuService 클래스에서 위의 순서처럼 Connection 생성, DAO메소트 호출(카테고리 등록, 메뉴등록),

그 이후에 트랜잭션 제어, Connection닫기까지 작성해준다.

public class MenuService {
	
	//신규 메뉴 등록용 서비스 메소드
	public void registNewMenu() {
		
		//1. Connection 생성
		Connection con = getConnection();
		
		//2. DAO 메소드 호출
		MenuDAO menuDAO = new MenuDAO();
		
		//2-1. 카테고리 등록
		CategoryDTO newCategory = new CategoryDTO();
		newCategory.setName("기타");
		newCategory.setRefCategory(null);
		
		int result1 = menuDAO.insertNewCategory(con, newCategory);
		
		//방금 입력한 마지막 카테고리 번호 조회
		int newCategoryCode = menuDAO.selectLastCategoryCode(con);
		
		//2-2. 메뉴 등록
		MenuDTO newMenu = new MenuDTO();
		newMenu.setName("비프스튜");
		newMenu.setPrice(40000);
		newMenu.setCategoryCode(newCategoryCode);
		newMenu.setOrderableStatus("Y"); 
		
		int result2 = menuDAO.insertNewMenu(con, newMenu);
		
		//3. 트랜잭션 제어
		if(result1 > 0 && result2 > 0) {
			System.out.println("신규 카테고리와 메뉴를 추가하였습니다.");
			commit(con);
		} else {
			System.out.println("신규 카테고리와 메뉴를 추가하지 못했습니다.");
			rollback(con);
		}
		
		//4. Connection 반납
		close(con);
	}

}

 

MenuDAO 클래스

public class MenuDAO {
	
	private Properties prop = new Properties();
	
	public MenuDAO() {
		try {
			prop.loadFromXML(new FileInputStream("mapper/menu-query.xml"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//신규 카테고리 등록용 메소드
	public int insertNewCategory(Connection con, CategoryDTO newCategory) {

		PreparedStatement pstmt = null;
		int result = 0;
		String query = prop.getProperty("insertCategory");
		
		try {
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, newCategory.getName());
			pstmt.setObject(2, newCategory.getRefCategory());
			
			result = pstmt.executeUpdate();
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		
		return result;
	}

	public int selectLastCategoryCode(Connection con) {
		
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		int newCategoryCode = 0;
		
		String query = prop.getProperty("getCurrentSequence");
		
		try {
			pstmt = con.prepareStatement(query);
			rset = pstmt.executeQuery();
			
			if(rset.next()) {
				newCategoryCode = rset.getInt("CURRVAL");
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
			close(rset);
		}
		
		
		return newCategoryCode;
	}

	//신규 메뉴 등록용 메소드
	public int insertNewMenu(Connection con, MenuDTO newMenu) {

		PreparedStatement pstmt = null;
		int result = 0;
		String query = prop.getProperty("insertMenu");
		
		try {
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, newMenu.getName());
			pstmt.setInt(2, newMenu.getPrice());
			pstmt.setInt(3, newMenu.getCategoryCode());
			pstmt.setString(4, newMenu.getOrderableStatus());
			
			result = pstmt.executeUpdate();
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		
		
		return result;
	}
	
}

 

쿼리문 작성하는 부분에서 이해가 안 간 부분이 "getCurrenSequence"에서 FROM TBL_CATEGORY를 쓰면 안되나?

라는 생각이 들었는데  강사님께 여쭤보니

SEQ_CATEGORY_CODE 라는 시퀀스 객체 자체가 TBL_CATEGORY 테이블과는 무관한 별도의 객체이고,

해당 테이블의 PK 컬럼에 고유값을 부여하는 용도로 쓸 뿐이다.

만약 FROM TBL_CATEGORY 라고 실행한다면 값은 CURRVAL에 해당하는 값이 나오긴 하지만

결과 행의 갯수가 TBL_CATEGORY의 갯수만큼 나오기 때문에 안됨 !!

여러 행을 조회해서 첫 번째 행만 가져가는 행위이니까..

 

FROM DUAL로 작성해야 하나의 행으로 얻을 수 있다.까먹을 것 같으니 블로그에도 적어야지

 

그 후 실행 클래스에서 MenuService의 registMenu 메소드를 실행해주면,

 

카테고리에 42번 기타가 INSERT가 되었고, 이 INSERT된 42번을 참조한 비프스튜가 INSERT된걸 확인할 수 있다.

위에서 한 내용을 정리해보자.

 

MVC(Model, View, Controller) 중에서 

 

model에 대해서 알아보자면

DTO(Data Transfer Object)

- 카테고리 테이블, 메뉴 테이블 이것들을 매칭시켜서 클래스를 정의해놓음. 데이터 타입을 사용자 정의 데이터타입을 만든 것. 메뉴에 필요한 값들을 필드로 선언해서 프로그램 내에서 객체로 들고다닐 수 있게끔 해주는 그런 클래스들

DAO(Data Access Object)

- 하위의 메소드 하나하나가 데이터에 대해서 어떤 구문들을 수행하는 메소드들이다.

service

- 하나의 논리적인 기능 단위를 전개하는 것.

1. Connection생성 - 2. DAO method - 3. transaction-commit, rollback - 4. Connection close


이젠 MVC에서 View에 대해서 알아보자.

 

디버깅을 확인할 용도로 log4jdbc를 썼는데 출력문이 너무 길어서 지워줬다.

 

반복 
----------
1. 카테고리 조회
2. 해당 카테고리의 메뉴 조회
3. 사용자에게 어떤 메뉴를 주문 받을 것인지 입력
4. 주문할 수량 입력
----------
5. 주문

 

이 순서로 작업을 해줄 것이다.

 

OrderService클래스

1. 카테고리 전체 조회용 메소드

2. 입력한 카테고리별 메뉴 조회용 메소드

이렇게 두 가지 메소드를 입력했다.

public class OrderService {
	
	private OrderDAO orderDAO = new OrderDAO();

	//카테고리 전체 조회용 메소드
	public List<CategoryDTO> selectAllCategory() {
		
		//1. Connection 생성
		Connection con = getConnection();
		
		//2. DAO의 모든 카테고리 조회용 메소드를 호출하여 결과 리턴 받기
		List<CategoryDTO> categoryList = orderDAO.selectAllCategory(con);
		
		//3. Connection 닫기 
		close(con);
		
		//4. 반환 받은 값 리턴하기
		return categoryList;
	}

	//카테고리별 메뉴 조회용 메소드
	public List<MenuDTO> selectMenuByCategory(int categoryCode) {

		//1.Connection 생성
		Connection con = getConnection();
		
		//2. DAO의 모든 카테고리 조회용 메소드를 호출하여 결과 리턴 받기
		List<MenuDTO> menuList = orderDAO.selectMenyByCategory(categoryCode, con);
		
		//3. Connection 닫기
		close(con);
		
		//4. 반환 받은 값 리턴하기
		return menuList;
	}

}

 

OrderDAO클래스

public class OrderDAO {
	
	private Properties prop = new Properties();

	public OrderDAO() {
		try {
			prop.loadFromXML(new FileInputStream("mapper/order-query.xml"));
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	//모든 카테고리 조회용 메소드
	public List<CategoryDTO> selectAllCategory(Connection con) {

		PreparedStatement pstmt = null;
		ResultSet rset = null;
		
		List<CategoryDTO> categoryList = null;
		
		String query = prop.getProperty("selectAllCategory");
		
		try {
			pstmt = con.prepareStatement(query);
			rset = pstmt.executeQuery();
			
			categoryList = new ArrayList<>();
			
			while(rset.next()) {
				CategoryDTO category = new CategoryDTO();
				category.setCode(rset.getInt("CATEGORY_CODE"));
				category.setName(rset.getString("CATEGORY_NAME"));
				
				categoryList.add(category);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
			close(rset);
		}
		
		return categoryList;
	}

	//카테고리별 메뉴 조회용 메소드
	public List<MenuDTO> selectMenyByCategory(int categoryCode, Connection con) {
		
		PreparedStatement pstmt = null;
		ResultSet rset = null;
		
		List<MenuDTO> menuList = null;
		
		String query = prop.getProperty("selectMenuByCategory");
		
		try {
			pstmt = con.prepareStatement(query);
			pstmt.setInt(1, categoryCode);
			
			rset = pstmt.executeQuery();
			
			menuList = new ArrayList<>();
			
			while(rset.next()) {
				MenuDTO menu = new MenuDTO();
				menu.setCode(rset.getInt("MENU_CODE"));
				menu.setName(rset.getString("MENU_NAME"));
				menu.setPrice(rset.getInt("MENU_PRICE"));
				menu.setCategoryCode(rset.getInt("CATEGORY_CODE"));
				menu.setOrderableStatus(rset.getString("ORDERABLE_STATUS"));
				
				menuList.add(menu);
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
			close(rset);
		}
		return menuList;
		
	}
	
}

 

DAO에 들어갈 쿼리문 작성

 

OrderMenu 클래스

스캐너를 통해 음식을 주문하고 싶은 카테고리와 메뉴 조회를 한다.

public class OrderMenu {
	
	private OrderService orderService = new OrderService();

	public void displayMenu() {
		
		Scanner sc = new Scanner(System.in);
		
		do {
			System.out.println("========== 음식 주문 프로그램 ==========");
			
			List<CategoryDTO> categoryList = orderService.selectAllCategory();
			
			for(CategoryDTO category : categoryList) {
				System.out.println(category.getName());
			}
			
			System.out.println("====================================");
			System.out.print("주문하실 카테고리를 선택해주세요 : ");
			String inputCategory = sc.nextLine();
			
			int categoryCode = 0;
			for(CategoryDTO category : categoryList) {
				if(category.getName().equals(inputCategory)) {
					categoryCode = category.getCode();
				}
			}
			System.out.println("========== 주문 가능 메뉴 ==========");
			List<MenuDTO> menuList = orderService.selectMenuByCategory(categoryCode);
			for(MenuDTO menu : menuList) {
				System.out.println(menu);
			}
		} while(true);
		
	}

}

 

출력문

public class Application {
	
	public static void main(String[] args) {
		new OrderMenu().displayMenu();
		
		
	}
}

해당 카테고리를 입력하면 그 카테고리에 해당하는 메뉴들을 출력해준다. 

 

이어서 음식 주문을 해보자.

주문받은 것들을 다룰 OrderMenuDTO, OrderDTO 클래스 작성 (코드생략)

 

OrderMenu클래스

주문할 메뉴를 선택 후, 코드와 가격 변수 설정을 해줘서 DTO에서 코드와 가격을 설정해준다.

주문할 수량까지 입력한 후

주문 날짜, 시간, 토탈금액, 메뉴리스트 출력

System.out.print("주문하실 메뉴를 선택해주세요 : ");
			String inputMenu = sc.nextLine();
			
			int menuCode = 0;
			int menuPrice = 0;
			
			for(int i = 0; i < menuList.size(); i++) {
				MenuDTO menu = menuList.get(i);
				if(menu.getName().equals(inputMenu)) {
					menuCode = menu.getCode();
					menuPrice = menu.getPrice();
				}
			}
			
			System.out.print("주문하실 수량을 입력하세요 : ");
			int orderAmount = sc.nextInt();
			
			OrderMenuDTO orderMenu = new OrderMenuDTO();
			orderMenu.setMenuCode(menuCode);
			orderMenu.setOrderAmount(orderAmount);
			
			orderMenuList.add(orderMenu);
			
			totalOrderPrice += (menuPrice * orderAmount);
			
			System.out.print("계속 주문하시겠습니까? (예/아니오) : ");
			sc.nextLine();
			boolean isContinue = sc.nextLine().equals("예") ? true : false;
			
			if(!isContinue) break;
			
		} while(true);
		
		for(OrderMenuDTO orderMenu : orderMenuList) {
			System.out.println(orderMenu);
		}
    		java.util.Date orderTime = new java.util.Date();
		SimpleDateFormat dateFormat = new SimpleDateFormat("yy/MM/dd");
		SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss");
		String date = dateFormat.format(orderTime);
		String time = timeFormat.format(orderTime);
		
		OrderDTO order = new OrderDTO();
		order.setDate(date);
		order.setTime(time);
		order.setTotalOrdderPrice(totalOrderPrice);
		order.setOrderMenuList(orderMenuList);
		
		int result = orderService.registOrder(order);
		
		if(result > 0) {
			System.out.println("주문에 성공하셨습니다.");
		} else {
			System.out.println("주문에 실패하셨습니다.");
		}

 

 

OrderDAO 에 주문 정보 입력용 메소드를 입력한다.

	public int insertOrder(Connection con, OrderDTO order) {
		
		PreparedStatement pstmt = null;
		int result = 0;
		
		String query = prop.getProperty("insertOrder");
		
		try {
			pstmt = con.prepareStatement(query);
			pstmt.setString(1, order.getDate());
			pstmt.setString(2, order.getTime());
			pstmt.setInt(3, order.getTotalOrdderPrice());
			
			result = pstmt.executeUpdate();
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		return result;
	}

 

주문 메뉴별 입력용 메소드 추가

	/* 주문 메뉴별 입력용 메소드 */
	public int insertOrderMenu(Connection con, OrderMenuDTO orderMenu) {
		
		PreparedStatement pstmt = null;
		int result = 0;
		
		String query = prop.getProperty("insertOrderMenu");
		
		try {
			pstmt = con.prepareStatement(query);
			pstmt.setInt(1, orderMenu.getMenuCode());
			pstmt.setInt(2, orderMenu.getOrderAmount());
			
			result = pstmt.executeUpdate();
			
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			close(pstmt);
		}
		
		return result;
	}

 

 

OrderService 클래스 (주문 정보 등록용 메소드)

	//주문 정보 등록용 메소드
	public int registOrder(OrderDTO order) {
		/* 1. Connection 생성 */
		Connection con = getConnection();
		
		/* 2. 리턴할 값 초기화 */
		int result = 0;
		
		/* 3. DAO 메소드로 전달 받은 값 넘겨서 insert */
		/* 3-1. Order table insert */
		int orderResult = orderDAO.insertOrder(con, order);
		
		/* 3-2. Order Menu table insert */
		List<OrderMenuDTO> orderMenuList = order.getOrderMenuList();
		int orderMenuResult = 0;
		for(OrderMenuDTO orderMenu : orderMenuList) {
			orderMenuResult += orderDAO.insertOrderMenu(con, orderMenu);
		}
		
		/* 4. 성공 여부 판단 후 트랜잭션 처리 */
		if(orderResult > 0 && orderMenuResult == orderMenuList.size()) {
			commit(con);
			result = 1;
		} else {
			rollback(con);
		}
		
		/* 5. Connection 닫기 */
		close(con);
		
		/*6. 결과 값 반환 */
		return result;
	}

 

실행해보면 ORDER테이블에 주문번호, 날짜, 시간, 토탈금액이 나오고

ORDER_MENU 테이블에는 주문번호와, 내가 주문한 메뉴들의 메뉴코드, 수량이 나온다.

 

이 작업에서 그대로 controller만 추가한 것은 다음 게시글에서!!