열심히 끝까지
디바이스 융합 자바(Java) day47 - 트랜잭션(rollback(),commit()) 본문
디바이스 융합 자바(Java) day47 - 트랜잭션(rollback(),commit())
노유림 2022. 8. 17. 17:16[트랜잭션] - Model 파트
>> 트랜잭션이 어디에 쓰이고,
코드를 쓰지는 않고 블로그에 개념정리를 제대로 할 것
어디까지 적용하는 지 기억하는 것이 좋음
개념 : 하나의 작업의 처리 "단위" (단위라는 것 중요)
>> 작업의 단위
ex1 )
>> 양산형 폰게임
++++++++++++++++++++++++++++
결제 - 기능
사용자테이블 보석컬럼 +5000
++++++++++++++++++++++++++++위에까지가 1 트랜잭션[하나의 단위]
아이템 구매 - 기능
사용자테이블 보석컬럼 -1000 => T
사용자 인벤토리 +아이템 => F
++++++++++++++++++++++++++++위에까지가 1 트랜잭션[하나의 단위]
ex2 )
>> 카카오페이/배민페이 -> 10시에 열리는데 9시에 주문했다고 하면?
1. 본인 계좌 -30,000
2. 배민페이 +30,000
3. 배민페이 -19,000
4. 주문 완료
> 1, 2 가 하나의 트랜잭션
> 3, 4 가 하나의 트랜잭션
이때, 주문 취소가 되면?
5. 배민페이 +19,000
3, 4, 5가 하나의 트랜잭션이 되버림
>> 이때 내 계좌에는? 70,000
>> 트랜잭션이 복잡하면 복잡할수록 에러가 날 가능성 존재
: 잘게 쪼개서 진행하는 것이 중요
==> 기능을 만들 때에는 설계할 때 잘 설계해야 함
우리의 팀프로젝트에 접목 시킬 수 있는 것
ex )
회원탈퇴 -> 게시글이 전부 삭제되었니?(확인 작업)
댓글추가 -> 게시글의 댓글 수 + 1(+1 되는 것이 보장되어야 함)
DB 관리자들은 연관관계, FK
※ '서비스의 단위를 설정'할 때를 고려하는 것도 중요하지만
DB관리자처럼 '테이블을 설계'할 때도 이 과정 중요
하나의 트랜잭션 범위에 있는
A B C 존재
B가 잘 실행이 안되면 A도 롤백 시켜줘!
rollback(); -> 트랜잭션 처리과정중에 문제가 발생했으니,
이전에 처리했던 기능을 다시 되돌려놓아라
ex ) 하나의 트랜잭션 범위에 있는
A B C 존재
B가 잘 실행이 안되면 A도 롤백 시켜줘!
commit(); -> 하나의 트랜잭션 단위가 잘 처리되었으니,
이 변경사항을 고정해서 마무리해라.[확인]
>> 다음 예시는 이해를 돕기 위한 예시
: 잘 사용하지 않음....
MYSQL에서 잘 보이기에 MYSQL로 작업(ORACLE은 자동으로 해준다고 함)
use db;
show tables;
CREATE TABLE BANK1( // 신한은행
BID INT PRIMARY KEY,
BNAME VARCHAR(20),
BALANCE INT
);
CREATE TABLE BANK2( // 국민은행
BID INT PRIMARY KEY,
BNAME VARCHAR(20),
BALANCE INT
);
>> 최소 가져야할 것들만 설계
INSERT INTO BANK1 VALUES(101,'KIM',10000);
INSERT INTO BANK2 VALUES(222,'LEE',100);
dao 기능
1. 이 사람(계좌번호 101, 222)의 해당하는
회원정보를 select할수 있는 dao 메서드
2. U(pdate)에 해당하는 dao 메서드
신한(bank1, 101) -> 국민(bank2, 222)
"-3000" "+3000" << 1 트랜잭션
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<jsp:useBean id="b1" class="vo.Bank1" />
<jsp:useBean id="b2" class="vo.Bank2" />
<jsp:useBean id="dao1" class="dao.Bank1DAO" />
<jsp:useBean id="dao2" class="dao.Bank2DAO" />
<%
if(request.getMethod().equals("POST")){
if(dao1.transfer(Integer.parseInt(request.getParameter("balance")))){
out.print("<script>alert('성공!');</script>");
}
else{
out.print("<script>alert('실패...');</script>");
}
}
b1=dao1.selectOne(b1);
b2=dao2.selectOne(b2);
%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
신한: <%=b1.getBname()%> | <%=b1.getBalance()%>원 <br>
국민: <%=b2.getBname()%> | <%=b2.getBalance()%>원
<hr>
<form method="post">
이체할 금액: <input type="number" value="0" min="0" name="balance">원
<input type="submit" value="계좌이체">
</form>
</body>
</html>
>> V + C
-> 분리
-------------------------
1. controller에서 html 제거
2. tranjection에 controller 부분 이동시키고 지거
3. controller에 useBean 작성
4. view작업
>> 기초 작업
JDBCUtil(Oracle 버전)---------
package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCUtil {
static final String driverName = "oracle.jdbc.driver.OracleDriver";
static final String url = "jdbc:oracle:thin:@localhost:1521:xe";
static final String user = "이름 넣기";
static final String passwd = "비밀번호 넣기";
// conn 객체 반환
public static Connection connect() { // Connection 확보 로직
Connection conn = null;
try {
Class.forName(driverName);
conn = DriverManager.getConnection(url, user, passwd);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
// conn 객체 받아서 연결해제
public static void disconnect(PreparedStatement pstmt, Connection conn) {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
JDBCUtil2(MySQL 버전)---------
package util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCUtil2 {
static final String driverName="com.mysql.cj.jdbc.Driver";
static final String url="jdbc:mysql://localhost:3306/데이터베이스 이름";
static final String user="이름 넣기";
static final String passwd="비밀번호 넣기";
// conn 객체 반환
public static Connection connect() { // Connection 확보 로직
Connection conn = null;
try {
Class.forName(driverName);
conn = DriverManager.getConnection(url, user, passwd);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
// conn 객체 받아서 연결해제
public static void disconnect(PreparedStatement pstmt, Connection conn) {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Bank1Table(Oracle)---------
CREATE TABLE BANK1(
BID INT PRIMARY KEY,
BNAME VARCHAR(20),
BALANCE INT
);
Bank2Table(Oracle)---------
CREATE TABLE BANK2(
BID INT PRIMARY KEY,
BNAME VARCHAR(20),
BALANCE INT
);
Bank1VO---------
package vo;
public class Bank1 {
private int bid;
private String bname;
private int balance;
public int getBid() {
return bid;
}
public void setBid(int bid) {
this.bid = bid;
}
public String getBname() {
return bname;
}
public void setBname(String bname) {
this.bname = bname;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank1 [bid=" + bid + ", bname=" + bname + ", balance=" + balance + "]";
}
}
Bank2VO---------
package vo;
public class Bank2 {
private int bid;
private String bname;
private int balance;
public int getBid() {
return bid;
}
public void setBid(int bid) {
this.bid = bid;
}
public String getBname() {
return bname;
}
public void setBname(String bname) {
this.bname = bname;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
@Override
public String toString() {
return "Bank2 [bid=" + bid + ", bname=" + bname + ", balance=" + balance + "]";
}
}
Bank1DAO---------
package dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import util.JDBCUtil2;
import vo.Bank1;
public class Bank1DAO {
Connection conn;
PreparedStatement pstmt;
final String sql_selectOne="SELECT * FROM BANK1 WHERE BID=101";
final String sql_transfer1="UPDATE BANK1 SET BALANCE=BALANCE-? WHERE BID=101";
final String sql_transfer2="UPDATE BANK2 SET BALANCE=BALANCE+? WHERE BID=222";
public boolean transfer(int balance) {
// vo로만 받아야하나요?(유지보수가 용이하지만 자유도가 높음)
// DAO의 메서드는 일반적으로 vo
// 지금처럼 강제적으로 자료형이나, 인자의 개수를 고정할 수도 있음
conn=JDBCUtil2.connect();
try {
conn.setAutoCommit(false); // 트랜잭션 시작
// 트랜잭션의 시작을 설정하는 메서드
// 자동 commit을 해제할 수 있음(MySQL)
// +++하나의 작업 단위+++
pstmt=conn.prepareStatement(sql_transfer1); // 원래는 각 하나 당 자동 커밋
pstmt.setInt(1, balance);
pstmt.executeUpdate();
pstmt=conn.prepareStatement(sql_transfer2); // 각 하나 당 자동 커밋
pstmt.setInt(1, balance);
pstmt.executeUpdate();
// ++++++++++++++++++
pstmt=conn.prepareStatement(sql_selectOne);
ResultSet rs=pstmt.executeQuery();
// (vo.getBid) 이런 느낌으로 써야 함
rs.next();
System.out.println(rs.getString("BNAME")+" | " + rs.getInt("BALANCE"));
System.out.println("얍!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
rs.close();
pstmt=conn.prepareStatement(sql_selectOne);
rs=pstmt.executeQuery();
// (vo.getBid) 이런 느낌으로 써야 함
rs.next();
if(rs.getInt("BALANCE")<0) { // 가진 금액보다 더 많이 계좌이체를 하려고 할때,
conn.rollback();
// 지금까지 했던 것들 취소해라!
return false;
}
else {
conn.commit();
// 작업에 문제가 없으면 commit 진행
}
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
} finally {
JDBCUtil2.disconnect(pstmt, conn);
}
return true;
}
// 계좌 하나 선택========================================================
public Bank1 selectOne(Bank1 vo) {
// System.out.println("BoardDAO의 로그 : "+vo); // 로그 남기기
conn=JDBCUtil2.connect();
try {
Bank1 data=null;
pstmt=conn.prepareStatement(sql_selectOne);
// pstmt.setInt(1, vo.getBid());
ResultSet rs=pstmt.executeQuery();
if(rs.next()) {
data=new Bank1();
data.setBid(rs.getInt("BID"));
data.setBname(rs.getString("BNAME"));
data.setBalance(rs.getInt("BALANCE"));
System.out.println("Bank1DAO : selectOne(): " + data);
return data;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtil2.disconnect(pstmt, conn);
}
return null;
}
}
Bank2DAO---------
package dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import util.JDBCUtil2;
import vo.Bank2;
public class Bank2DAO {
Connection conn;
PreparedStatement pstmt;
final String sql_selectOne="SELECT * FROM BANK2 WHERE BID=222";
final String sql_update2="UPDATE BANK2 SET BALANCE=? WHERE BID=?";
// 계좌 하나 선택========================================================
public Bank2 selectOne(Bank2 vo) {
// System.out.println("BANK2의 로그 : "+vo); // 로그 남기기
Bank2 data=null;
conn=JDBCUtil2.connect();
try {
pstmt=conn.prepareStatement(sql_selectOne);
// pstmt.setInt(1, vo.getBid());
ResultSet rs=pstmt.executeQuery();
if(rs.next()) {
data=new Bank2();
data.setBid(rs.getInt("BID"));
data.setBname(rs.getString("BNAME"));
data.setBalance(rs.getInt("BALANCE"));
System.out.println("Bank2DAO : selectOne(): " + data);
// 적절한 공간에 설정해 놓은 로그는 내가 어디에 문제가 있는지 캐치할 수 있음 == 로깅기법(디버깅)
return data;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtil2.disconnect(pstmt, conn);
}
return null;
}
}
>> html 작업
index---------
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%
//초기 실행 시 index로 진행, main값 가지고 tranjection 넘어가기 위함
response.sendRedirect("controller.jsp?action=main");
%>
tranjection---------
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>tranjection 연습</title>
</head>
<body>
신한 : ${b1.bname} | ${b1.balance}원 <br>
국민 : ${b2.bname} | ${b2.balance}원
<hr>
<form method="post" action="controller.jsp">
<input type="hidden" name="action" value="send">
이제할 금액 : <input type="number" value="0" min="0" name="balance">원
<input type="submit" value="계좌이체"">
</form>
</body>
</html>
controller---------
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<jsp:useBean id="b1" class="vo.Bank1"/>
<jsp:useBean id="b2" class="vo.Bank2"/>
<jsp:useBean id="dao1" class="dao.Bank1DAO"/>
<jsp:useBean id="dao2" class="dao.Bank2DAO"/>
<%
String action = request.getParameter("action");
System.out.println("로그 : " + action);
if(action.equals("main")){
b1=dao1.selectOne(b1);
b2=dao2.selectOne(b2);
// request,session,application
session.setAttribute("b1", b1);
session.setAttribute("b2", b2);
pageContext.forward("tranjection.jsp");
// View에서 EL식으로 출력하기 위해, JSP scope 내장객체에 setAttribute() 수행함
}
else if(action.equals("send")){
int balance=Integer.parseInt(request.getParameter("balance"));
System.out.println("로그 : send 들어옴/ 금액 " + balance);
if(dao1.transfer(Integer.parseInt(request.getParameter("balance")))){
// 성공
out.print("<script>alert('성공!');location.href='controller.jsp?action=main';</script>");
}else{
// 실패
out.print("<script>alert('실패..');location.href='controller.jsp?action=main';</script>");
}
}
%>
실행 결과 사진--------