📌 트랜잭션(Transaction)
✅ 두 쿼리가 실행되어야 하는 상황이 있다.
첫 번째 쿼리는 문제없이 실행됐지만 두 번째 쿼리에서 문제가 발생하였다.
그렇다면 데이터가 잘못된 상태로 저장된다.
두 쿼리가 모두 정상적으로 실행되어야 데이터의 무결성이 유지되기 때문이다.
그래서 한 개 이상의 쿼리가 모두 성공적으로 실행되어야 데이터가 정상적으로 처리되는 경우
DBMS 트랜잭션(transaction)을 이용해서 한 개 이상의 쿼리를 마치 한 개의 쿼리 처럼 처리할 수 있다.
✅ JDBC에서는 모든 DML 작업이 자동으로 auto commit으로 처리된다.
하지만 트랜잭션으로 처리해야만 하는 작업에서는 명시적으로 auto commit을 비활성 시킨 후에 작업해야 된다.
💡 둘 다 성공해서 실제 DB에 반영 => COMMIT
또는
둘 중에 하나라도 실패하면 모두 취소 => ROLLBACK
✅ 다음은 JDBC에서 트랜잭션 처리를 위한 가장 기본적인 코드로서,
Service클래스에서 Connection API의 setAutoCommit(false) 메서드를 사용하여 비활성화 시키고
commit() 또는 rollback() 메서드를 사용하여 트랜잭션 처리를 할 수 있다.
✅ dao.insert()와 dao.delete() 메서드가 모두 성공하면 commit()하고
만약 하나라도 예외가 발생되면 rollback()으로 처리한다.
DeptMain.java
import java.util.List;
import java.util.Scanner;
import com.dto.DeptDTO;
import com.exception.DuplicatedDeptnoException;
import com.service.DeptService;
import com.service.DeptServiceImpl;
public class DeptMain {
public static void main(String[] args) {
// 화면처리 추가
while(true) {
Scanner scan = new Scanner(System.in);
System.out.println("5. 삭제 및 수정하기"); // 여러개의 작업이 하나처럼 : 트랜잭션
// 문자열로 읽기
String num = scan.nextLine(); // 한 줄 읽기
if("5".equals(num)) {
// 5. 삭제 및 수정
// 수정할 데이터
System.out.println("수정할 부서번호를 입력하시오");
String deptno = scan.next();
System.out.println("수정할 부서명을 입력하시오");
String dname = scan.next();
System.out.println("수정할 부서위치를 입력하시오");
String loc = scan.next();
DeptDTO dto = // 위에 세가지를 dto에 담기
new DeptDTO(Integer.parseInt(deptno), dname, loc);
// 삭제할 데이터
System.out.println("삭제할 부서번호를 입력하시오");
String deptno2 = scan.next();
// Service 연동
DeptService service = new DeptServiceImpl();
int n = service.updateAndDelete(dto, Integer.parseInt(deptno2));
}else if("0".equals(num)) {
System.out.println("프로그램 종료");
System.exit(0); // exit : 프로그램 정상종료
}
}//end while
}//end main
}//end class
DeptService.java
package com.service;
import java.util.List;
import com.dto.DeptDTO;
import com.exception.DuplicatedDeptnoException;
// dept 테이블의 데이터를 가공하는 역할 ==> 비즈니스 로직 처리 및 트랜잭션 처리 담당.
public interface DeptService {
public List<DeptDTO> findAll();
// 5. 수정 및 삭제 처리 하는 메서드
// DTO와 deptno2를 받아주는 메서드
public int updateAndDelete(DeptDTO dto, int deptno);
}
DeptServiceImpl.java
package com.service;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import com.dao.DeptDAO;
import com.dto.DeptDTO;
import com.exception.DuplicatedDeptnoException;
// 오라클 데이터베이스와 연결하기 위해 Connection을 얻는 작업을 하기 위한 클래스
// Connection 얻는 이유? 트랜잭션을 처리하려고
// 얻은 Connection을 실제 데이터베이스와 연동하는 DeptDAO 클래스에 인자로 전달하여 사용된다.
public class DeptServiceImpl implements DeptService {
String driver = "oracle.jdbc.driver.OracleDriver";
String url = "jdbc:oracle:thin:@localhost:1521:xe";
String userid = "SCOTT";
String passwd = "TIGER";
public DeptServiceImpl() {
try {
Class.forName(driver); // 드라이버 로딩
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// ▣ select 기능하는 메서드
// Connection 까지만 얻는다. 나머지 작업은 DAO에서 처리
@Override
public List<DeptDTO> findAll(){
List<DeptDTO> list = null; // ★
Connection con = null;
// Connection 맺기
try{
con = DriverManager.getConnection(url, userid, passwd);
// DAO 접근
DeptDAO dao = new DeptDAO(); // ★
list = dao.findAll(con); // ★
}catch(SQLException e){
e.printStackTrace();
}finally { // close()
try {
if(con != null)con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return list; // ★ 리턴
}
// ▣ insert 기능하는 메서드
// Connection 까지만 얻는다. 나머지 작업은 DAO에서 처리
@Override
public int insert(DeptDTO dto) throws DuplicatedDeptnoException {
int n = 0;
Connection con = null;
try {
con = DriverManager.getConnection(url, userid, passwd);
// DAO 연동
DeptDAO dao = new DeptDAO();
n = dao.insert(con, dto);
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return n;
}
// ▣ update 기능하는 메서드
// Connection 까지만 얻는다. 나머지 작업은 DAO에서 처리
@Override
public int update(DeptDTO dto) {
int n = 0;
Connection con = null;
try {
con = DriverManager.getConnection(url, userid, passwd);
// DAO 연동
DeptDAO dao = new DeptDAO();
n = dao.update(con, dto);
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (con != null)
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return n;
}
// ▣ DELETE 작업
@Override
public int delete(int deptno) {
// Connection때문에 try~catch 필요
// close() 해야되는 것들은 finally에 쓰기 위해 다 바깥에
int n = 0;
Connection con = null;
try {
con = DriverManager.getConnection(url, userid, passwd);
// DAO 연동
DeptDAO dao = new DeptDAO();
n = dao.delete(con, deptno);
}catch(SQLException e) {
e.printStackTrace();
}finally {
try {
if(con != null)con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return n;
}
// ▣ updateAndDelete 작업 (트랜잭션 처리)
@Override
public int updateAndDelete(DeptDTO dto, int deptno) {
int n = 0;
Connection con = null;
try {
con = DriverManager.getConnection(url, userid, passwd);
// DAO 연동
DeptDAO dao = new DeptDAO();
// ----------------트랜잭션-------------------
// 여러 개의 작업을 하나의 작업으로 처리할 때 씀
// 둘 다 성공해서 실제 DB에 반영 => COMMIT
// 또는
// 둘 중에 하나라도 실패하면 모두 취소 => ROLLBACK
// AutoCommit을 비활성화
con.setAutoCommit(false);
// 수정
n = dao.update(con, dto);
// 삭제
n = dao.delete(con, deptno);
// -------------------------------------------
con.commit(); // 아무 문제가 없어서 커밋한다.
}catch(SQLException e) {
//catch에 잡혔다면 둘 중에 하나는 에러가 있다는 거니까 rollback
try {
if(con != null)con.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
e.printStackTrace();
}finally {
try {
if(con != null)con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return n;
}
private void rollback() {
// TODO Auto-generated method stub
}
}
DeptDAO.java
package com.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.dto.DeptDTO;
import com.exception.DuplicatedDeptnoException;
// 실제 데이터베이스 (Oracle의 dept테이블)와 연동하는 클래스
// dept 테이블의 하나의 레코드는 DeptDTO에 저장
// 누적하기 위하여 ArrayList<DeptDTO>를 사용
// 주의할 점 : Connection을 DAO 클래스에서 close()하면 안되고 반드시 Service 클래스에서 close() 시켜야한다.
public class DeptDAO {
// ▣ select 작업
public List<DeptDTO> findAll(Connection con){ // DAO는 service에서 Connection을 꼭 전달받는다!
// 다형성 통해 DeptDTO 누적용
List<DeptDTO> list = new ArrayList<DeptDTO>();
// 블록 밖에서 쓰기 위해 바깥에서 선언을 해준다.
PreparedStatement pstmt=null;
ResultSet rs = null;
try{
String sql = "select deptno as no, dname, loc from dept";
pstmt = con.prepareStatement(sql); //에러발생시
rs = pstmt.executeQuery();
// 결과값인 ResultSet에서 데이터를 얻기
while(rs.next()) {
int deptno = rs.getInt("no"); // getInt(1) 가능
String dname = rs.getString("dname"); // getString(2)
String loc = rs.getString("loc"); // // getString(3)
// 생성자 통해 DTO에 저장. 한 번 돌때마다 DTO 생성.
DeptDTO dto = new DeptDTO(deptno, dname, loc);
// 누적
list.add(dto);
}
}catch(SQLException e){
e.printStackTrace();
}finally {
//finally
try {
//역순
// null 오류는 조건문(if)으로 처리해야함
// rs나 pstmt가 null일 수도 있기 때문에 확인하고 close() 하는 작업
if(rs != null)rs.close();
if(pstmt != null)pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
// System.out.println(e.getMessage()); 이것도 가능
}
}
return list;
}//
// ▣ insert 작업
public int insert(Connection con, DeptDTO dto ) throws DuplicatedDeptnoException {
int num = 0;
PreparedStatement pstmt=null;
try{
String sql = "insert into dept ( deptno, dname, loc) "
+ " values( ?, ?, ? )";
pstmt = con.prepareStatement(sql); //에러발생시
pstmt.setInt(1, dto.getDeptno()); // deptno 값은 중복되지 않도록 확인할 것.
pstmt.setString(2, dto.getDname());
pstmt.setString(3, dto.getLoc());
num = pstmt.executeUpdate();
}catch(SQLException e){
// e.printStackTrace();
throw new DuplicatedDeptnoException(dto.getDeptno()+" 값이 중복됨");
}finally {
try {
if(pstmt != null)pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return num;
}
// ▣ UPDATE 작업
public int update(Connection con, DeptDTO dto) {
int num = 0;
PreparedStatement pstmt=null;
try{
String sql = "update dept set dname=?, loc=? where deptno=?";
pstmt = con.prepareStatement(sql); //에러발생시
pstmt.setInt(3, dto.getDeptno()); // deptno 값은 중복되지 않도록 확인할것.
pstmt.setString(1, dto.getDname());
pstmt.setString(2, dto.getLoc());
num = pstmt.executeUpdate();
}catch(SQLException e){
e.printStackTrace();
}finally {
try {
if(pstmt != null)pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return num;
}
// ▣ DELETE 작업
public int delete(Connection con, int deptno) {
// return 위해 밖에서 선언해주기
int n = 0;
// finally에 쓰기위해 밖에서 선언해주기
PreparedStatement pstmt = null;
try {
String sql = "delete from dept where deptno=?";
pstmt = con.prepareStatement(sql);
pstmt.setInt(1, deptno); // ?에 값 넣어주기
n = pstmt.executeUpdate();
}catch(SQLException e) {
e.printStackTrace();
}finally { // close()작업
try {
if(pstmt != null) pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return 0;
}
}
'DB > MyBatis' 카테고리의 다른 글
Mybatis - 조건문 (0) | 2023.08.07 |
---|---|
Mybatis - Dynamic SQL (0) | 2023.08.07 |
Mybatis - 환경설정 및 SELECT 예제 (0) | 2023.08.04 |
DAO(Data Access Object) Pattern (0) | 2023.08.02 |
JDBC (0) | 2023.08.01 |