목차
- Transaction
1-1. getAutoCommit()
1-2. setAutoCommit(false)
1-3. commit(con);과 rollback(con); 수동 처리 - Service
2-1. DAO클래스 세팅(+DTO 클래스, XML 쿼리문)
2-2. Service 클래스
1. Transaction
1-1. getAutoCommit()
Connection con = getConnection();
try {
System.out.println("autoCommit의 현재 설정 값 : " + con.AutoCommit());
==============
autoCommit의 현재 설정 값 : true
- 별도 커밋(commit) 처리 없이도 DML 구문이 오라클의 테이블에까지 최종적으로 추가/수정/삭제되는 모습을 확인할 수 있다.
- Connection 객체를 생성하고, 현재 설정 값에 대해 물으면 boolean형으로 true 또는 false가 반환된다.
- 결과로서 기본 설정 값인 true가 반환된다면, 이는 DML 구문 수행 시 최종 반영까지 이루어지도록 autoCommit 설정돼 있다는 뜻이다.
1-2. setAutoCommit(false)
- 여러 테이블에 걸쳐 INSERT 작업을 수행한다고 가정하자. 이때 개중 하나라도 실패한 구문이 있을 때는 전체적인 논리적 흐름에 어긋나므로 ROLLBACK이 필요하게 마련이다.
- 따라서 사용자(개발자)가 판단해 커밋하도록 autoCommit 설정을 변경하는 것이 필요하다.
public static Connection getConnection() { //객체 생성 없이 사용할 수 있도록 static메소드로 선언
Connection con = null;
Properties prop = new Properties();
try {
prop.load(new FileReader("config/connection-info.properties"));
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
Class.forName(driver);
con = DriverManager.getConnection(url, prop);
con.setAutoCommit(false);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return con;
}
- 기본값인 true로 돼 있는 autoCommit 여부를 바꾸는 설정은 Connection 생성 시에 다뤄야 한다.
- 여기서는 getConnection()이라는 메소드 통해 Connection 객체를 만들고 호출해 왔으므로, 해당 메소드 하단에 수동 커밋으로 변경하는 구문을 추가한다: con.setAutoCommit(false);
❗ close(con); 만나면 커밋(commit) 된다.
수동 커밋으로 변경하는 구문을 con.setAutoCommit(false); 적용한 뒤, autoCommit의 현재 설정 값을 출력하면 false로 의도한 값이 알맞게 반환된다. 하지만 그 상태에서 그대로 DML 구문을 실행하면, 여전히 사용자(개발자) 의사에 상관 없이 테이블에까지 최종 반영됨을 확인할 수 있다.
close(con); 구문에서 Connection이 닫히며 커밋(commit) 처리가 되는 것이 그 이유이다.
따라서 close(con); 구문에 도달하기 전에 수동 커밋(commit)/롤백(rollback) 선언이 필요하다.
1-3. commit(con);과 rollback(con); 수동 처리
public static void commit(Connection con) {
try {
if(con != null && !con.isClosed()) {
con.commit();
}
} catch (SQLException e) {
e.printStrackTrace();
}
}
- close() 구문과 같이 계속 쓰일 것에 대비해 수동 commit(); 을 수행하는 별도 메소드를 작성한다.
public static void rollback(Connection con) {
try {
if(con != null && !con.isClosed()) {
con.rollback();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
- 수동 rollback(); 구문도 마찬가지로 별도 메소드로 템플릿을 만들어둔다.
-- Application.class
} finally {
close(pstmtB);
close(pstmtA);
if(resultA > 0 && resultB > 0) {
System.out.println("신규 카테고리와 메뉴 등록에 성공하였습니다.");
commit(con);
} else {
System.out.println("신규 카테고리와 메뉴 등록에 실패하였습니다.");
rollback(con);
}
close(con);
}
- 두 개의 테이블에 각각 카테고리와 메뉴를 INSERT하는 상황을 가정한다. 결과로서 resultA, resultB는 삽입될 행의 개수를 반환할 것이다.
- finally 구문에서 if문을 활용해 모든 결과값이 존재한다면 문구 출력 후 commit()을, 그렇지 않다면 실패 문구 출력한 뒤 rollback()을 수행하도록 만든다: if(resultA > 0 && resultB > 0)
- Connection 객체에 대한 반환은 가장 마지막에 치러진다: close(con);
2. Service
MVC 패턴은 Model, View, Controller를 일컫는다.
View ↔ Controller ↔ Service ↔ DAO ↔ DB로 흐름이 이어진다.
- DTO는 Data Transfer Object의 약어로서, 프로그램 내에서 객체로 활용할 수 있게끔 해주는 클래스를 말한다: CategoryDTO.class, MenuDTO.class 등
- DAO는 Data Access Object로서 DB와 관련된 CRUD 작업을 처리한다. 테이블로부터 데이터를 읽어 자바 객체로 변환하거나, 자바 객체의 값을 테이블에 저장해주는 역할을 한다. 따라서 DAO 클래스는 테이블 컬럼과 매핑되는 프로퍼티(Properties)를 가진다.
- Service 클래스들은 데이터를 DB에서 읽어오거나, 반대로 DB에 데이터를 저장하기 위해 DAO 클래스를 거친다: Service ↔ DAO ↔ DB
- 프로젝트의 Model 폴더 하위에 dao, dto, service가 자리한다.
- 이때 Service 클래스는 하나의 논리적 기능 단위들을 정의한 클래스로서, ① Connection을 만들어 ② DAO 메소드를 필요한 만큼 호출하고 ③ DML의 트랜잭션(transaction) 관리한 뒤 ④ Connection 반납 순서로 치러진다.
2-1. DAO 클래스 세팅(+DTO 클래스, XML 쿼리문)
A. 신규 카테고리 등록
-- MenuDAO.class
private Properties prop = new Properties();
public MenuDAO() {
try {
prop.loadFromXML(new FileInputStream("mapper/menu-query.xml"));
} catch (IOException e) {
e.printStackTrace();
}
}
- 같은 클래스 내 여러 메소드에서 사용할 객체이므로 프로퍼티(Properties)를 private 필드로 선언하고, 생성자 통해 설정한다.
-- menu-query.xml
<entry key="insertCategory">
INSERT
INTO TBL_CATEGORY A
(
A.CATEGORY_CODE
, A.CATEGORY_NAME
, A.REF_CATEGORY_CODE
)
VALUES
(
SEQ_CATEGORY_CODE.NEXTVAL
, ?
, ?
)
</entry>
-- MenuDAO.class
-- 신규 카테고리 등록용 메소드
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.getRefCategoryCode()); //Integer 타입을 set하기 위해 Object로 설정
result = pstmt.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(pstmt);
}
return result;
}
- 사용할 PreparedStatement 객체와 결과값을 담을 int형 result를 선언한다.
- XML 파일에서 위치홀더로 남겨두었던 데 대해 CategoryDTO 타입의 접근자(getter) 메소드로 데이터를 설정한다.
- 이때 REF_CATEGORY_CODE는 해당 테이블의 외래키(FOREIGN KEY)로 설정돼 있다. 컬럼 데이터 타입은 NUMBER이지만, 전달하는 값으로서 null이 올 수도 숫자가 올 수도 있기에 복합적으로 다루는 Integer 타입으로 취급될 수 있도록 setObject()로 선언한다.
- finally 블럭에서 close()는 사용한 객체인 PreparedStatement에 대해서만 작성한다. Connection은 나중까지 쓰여 Service 클래스에서 반납될 것이기 때문이다.
- 결과값을 담은 result 변수를 최종 반환하도록 명시한다.
B. 신규 메뉴 등록
-- menu-qeury.xml
<entry key="getCurrentSequence">
SELECT
SEQ_CATEGORY_CODE.CURRVAL
FROM DUAL //가상 테이블 DUAL로부터 조회
</entry>
-- MenuDAO.class
-- 현재 카테고리 코드 조회
public int selectLastCategoryCode(Connection con) {
PreparedStatement stmt = 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(rset);
close(pstmt);
}
return newCategoryCode;
}
- A. 신규 카테고리 등록에서 생성된 마지막 카테고리 코드를 조회할 별도 쿼리문을 작성한다. 이때 조회는 가상 테이블인 DUAL을 활용한다.
- "CURRVAL"라는 임의의 컬럼명을 사용해 마지막 카테고리를 불러오고, 이를 newCategoryCode라는 임시 변수에 저장해 결과로서 반환한다.
-- menu-query.xml
<entry key="insertMenu">
INSERT
INTO TBL_MENU A
(
A.MENU_CODE
, A.MENU_NAME
, A.MENU_PRICE
, A.CATEGORY_CODE
, A.ORDERABLE_STATUS
)
VALUES
(
SEQ_MENU_CODE.NEXTVAL
, ?
, ?
, ?
, ?
)
</entry>
-- MenuDAO.class
-- 신규 메뉴 등록용 메소드
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;
}
- 메뉴 정보를 뭉쳐서 일괄적으로 전달하기 위해 MenuDTO 클래스를 인자로 받아 활용한다.
- DTO 클래스는 ① private 필드, ② 기본 생성자 및 매개변수 있는 생성자, ③ 설정자(setter)와 접근자(getter), ④ 오버라이딩 된 toString() 메소드로 구성한다.
2-2. Service 클래스
❗ Service의 역할 및 수행 순서
① Connection 생성
② DAO 클래스의 메소드 호출
③ 트랜잭션 제어(=commit 또는 rollback에 대한 처리)
④ Connection 반납
A. 신규 카테고리 등록
-- MenuService.class
public void registerNewMenu() {
Connection con = getConnection(); //① Connection 객체 생성
MenuDAO menuDAO = new MenuDAO(); //② DAO클래스 객체 생성
CategoryDTO newCategory = new CategoryDTO(); //CategoryDTO 객체 생성
newCategory.setName("기타");
newCategory.setRefCategoryCode(null); //setObject()로 잡아두었기에 null값 취급 가능
int resultA = menuDAO.insertNewCategory(con, newCategory);
- 논리적 트랜잭션 단위로 작동해야 하기 때문에 인자에 Connection 객체를 넘긴다.
B. 신규 메뉴 등록
int newCategoryCode = menuDAO.selectLastCategoryCode(con);
- 방금 입력한 마지막 카테고리 번호를 조회해 newCategoryCode 변수에 담는다.
MenuDTO newMenu = new MenuDTO(); //MenuDTO 객체 생성
newMenu.setName("메롱메롱스튜");
newMenu.setPrice(40000);
newMenu.setCategoryCode(newCategoryCode); //먼저 조회했던 마지막 카테고리 번호를 전달
newMenu.setOrderableStatus("Y");
int resultB = menuDAO.insertNewMenu(con, newMenu);
if(resultA > 0 && resultB > 0) { //③ 트랜잭션 제어
System.out.println("신규 카테고리와 메뉴를 추가하는 데 성공하였습니다.");
commit(con);
} else {
System.out.println("신규 카테고리와 메뉴를 추가하는 데 실패하였습니다.");
rollback(con);
}
close(con); //④ Connection 반납
}
- Connection 자원 반납 전 트랜잭션 제어 처리한다.
-- Application.class
public static void main(String[] args) {
new MenuService().registNewMenu();
}
- 실행을 위해 Application 클래스에서 Service 클래스와 메소드를 호출한다.
✅ service 계층을 분리할 수 있다.
✅ service 계층의 의미를 이해할 수 있다.
✅ 공통 모듈에 트랜잭션 관련 close() 메소드를 작성할 수 있다.
✅ transaction의 의미를 설명할 수 있다.
✅ 수동 커밋 모드를 설정할 수 있다.
✅ transaction 처리를 해야 할 위치를 이해할 수 있다.
'Database' 카테고리의 다른 글
[Oracle/수업 과제 practice] 도서 관리 (0) | 2022.02.06 |
---|---|
[JDBC] VIEW | MVC 패턴 | CRUD (0) | 2022.02.05 |
[Oracle/2nd Review] Part 3. 오라클 객체 및 권한 (0) | 2022.02.04 |
[JDBC] DAO | MVC 패턴 | CRUD | Query (0) | 2022.02.04 |
[JDBC] CRUD | INSERT | UPDATE | DELETE (0) | 2022.02.03 |