1.excuteUpdate() : 게시글 입력
- BoardDao.insert() -> PreparedStatement
2. insertSQL
- PreparedStatement -> Connection
3. insert(요청)
- Connection -> DBMS(4. 데이터입력 : 쓰레드 -> app_board)
Connection해서 생성되는 쓰레드는 같은 쓰레드를 공유한다???
첨부파일 데이터 입력 중 제약조건을 만족하지 못해 오류가 발생한다면?
제약조건 : 데이터 형, 컬럼 크기, 데이터 중복, FK 제약 등등으로 인해
=> 첨부파일 데이터 입력은 실패한다.
그러나 이전에 입력한 게시글은 그대로 유효하다.??
Transaction(트랜잭션) : 게시글 입력과 첨부파일 입력이 하나의 작업으로 묶인 경우
트랜잭션으로 묶인 경우 한개의 작업이 실패하면 이전에 성공한 작업도 취소해야한다.
예)
계좌이체
1. 내 계좌에서 출금 | 성공 | 실패
2. 상대 계좌에 입금 | 실패 | 실패
내 계좌에서 출금한경우와 상대계좌에 입금한게 둘다 성공했을때 계좌이체가 진행되어야 한다.
쿼리가 성공하면 임시데이터에 저장하고 전부다 성공했으면 commit요청을 보내 실제 데이터베이스에 저장한다.
첨부파일입력에서 오류가 발생하면 PrepredStatement에서 connection에 rollback을 요청한다.
rollback을 요청하면 임시 데이터베이스에 들어있던 데이터를 삭제한다.
// public class DefaultBoardService implements BoardService {
// BoardDao boardDao;
//
// public DefaultBoardService(BoardDao boardDao) {
// this.boardDao = boardDao;
// };
public class DefaultBoardService implements BoardService {
Connection con;
BoardDao boardDao;
public DefaultBoardService(BoardDao boardDao, Connection con) {
this.boardDao = boardDao;
this.con = con;
};
//ctx.setAttribute("boardService", new DefaultBoardService(boardDao));
ctx.setAttribute("boardService", new DefaultBoardService(boardDao, con));
DefaultBoardService 클래스파일의 add() , insert(), update(), delte() 변경
select는 트랜잭션을 사용하지 않는다??
// @Override
// public void add(Board board) throws Exception {
// if (boardDao.insert(board) == 0) {
// throw new Exception("게시글 등록 실패!");
// }
// boardDao.insertFiles(board);
// }
@Override
public void add(Board board) throws Exception {
con.setAutoCommit(false);
try {
if (boardDao.insert(board) == 0) {
throw new Exception("게시글 등록 실패!");
}
boardDao.insertFiles(board);
con.commit();
} catch (Exception e) {
con.rollback();
throw e;
} finally {
con.setAutoCommit(true);
}
}
// @Override
// public boolean update(Board board) throws Exception {
// if (boardDao.update(board) == 0) {
// return false;
// }
// boardDao.insertFiles(board);
// return true;
// }
@Override
public boolean update(Board board) throws Exception {
con.setAutoCommit(false);
try {
if (boardDao.update(board) == 0) {
return false;
}
boardDao.insertFiles(board);
con.commit();
return true;
} catch (Exception e) {
con.rollback();
throw e;
} finally {
con.setAutoCommit(true);
}
}
// @Override
// public boolean delete(int no) throws Exception {
// // 첨부파일 삭제
// // 게시글의 첨부파일을 먼저 삭제한다.
// boardDao.deleteFiles(no);
// // 게시글 삭제
// return boardDao.delete(no) > 0;
// }
@Override
public boolean delete(int no) throws Exception {
con.setAutoCommit(false);
try {
boardDao.deleteFiles(no);
boolean result = boardDao.delete(no) > 0;
con.commit();
return result;
} catch (Exception e) {
con.rollback();
throw e;
} finally {
con.setAutoCommit(true);
}
}
멀티스레드,
client1, client2의 요청이 같은 Connection객체를 사용하기때문에 발생하는 문제
스레드마다 커넥션을 리턴해줄 커넥션 관리자를 만들어야한다
커넥션 관리자
DAO <>---> DatabSource(Thread 전용 DB커넥션 제공)
DataSorurce가 Connection객체를 생성해서
DAO에 있는 메소드가
DAO ---> Connection객체를 사용
insert()
insertFiles()
update()
delete()
package com.bitcamp.sql;
import java.sql.Connection;
import java.sql.DriverManager;
// 스레드 전용 DB 커넥션을 제공하는 일을 한다.
public class DataSource {
String jdbcUrl;
String username;
String password;
// ThreadLocal을 이용하면 쓰레드 영역에 변수를 설정할 수 있기 때문에,
// 특정 쓰레드가 실행하는 모든 코드에서 그 쓰레드에 설정된 변수 값을 사용할 수 있게 된다.
//스레드 전용 DB 커넥션 보관소
ThreadLocal<Connection> conStore = new ThreadLocal<>();
public DataSource(String driver, String jdbcUrl, String username, String password)
throws Exception {
// JDBC Driver 클래스 로딩하기
// Class.forName("org.mariadb.jdbc.Driver");
Class.forName(driver);
this.jdbcUrl = jdbcUrl;
this.username = username;
this.password = password;
}
// Connection con =
// DriverManager.getConnection("jdbc:mariadb://localhost:3306/studydb", "study", "1111");
public Connection getConnection() throws Exception {
Thread currThread = Thread.currentThread();
System.out.printf("%s=> getConnection() 호출\n", currThread.getName());
// 현재 스레드의 보관소에서 DB 커넥션 객체를 꺼낸다.
Connection con = conStore.get();
if (con == null) { // 현재 스레드 보관소에 커넥션 객체가 없다면,
DriverManager.getConnection(jdbcUrl, username, password); // 새로 생성
conStore.set(con); // 새로 만든 DB 커넥션 객체를 다음에 다시 사용할 수 있도록 보관한다.
System.out.printf("%s=> Connection 생성\n", currThread.getName());
}
return con;
}
}
//@WebListener
//public class ContextLoaderListener implements ServletContextListener {
// @Override
// public void contextInitialized(ServletContextEvent sce) {
// System.out.println("공유 자원을 준비 중!!");
//
// try {
// Class.forName("org.mariadb.jdbc.Driver");
// Connection con =
// DriverManager.getConnection("jdbc:mariadb://localhost:3306/studydb", "study", "1111");
//
// // 생성자에서 getServletContext()를 호출하면 오류 발생!!
// // 왜?
// // - ServletContext는 ServletCOnfig 객체를 통해 꺼낼 수 가 있는데,
// // - ServletContext는 아직 주입되지 않은 상태이다.
// // ServletConfig 객체가 언제 주입되는가?
// // - 생성자 다음에 호출되는 init()가 호출될 때 ServletConfig 객체가 주입된다.
// // 왜? 아직 ServletContext 객체가 준비되지 않았기 때문이다.
// // 그래서 생성자 다음에 호출되는 init()에서 getServletContext()를 호출해야 한다.
// ServletContext ctx = sce.getServletContext();
//
// DataSource ds = new DataSource("org.mariadb.jdbc.Driver",
// "jdbc:mariadb://localhost:3306/studydb", "study", "1111");
//
// BoardDao boardDao = new MariaDBBoardDao(con);
// MemberDao memberDao = new MariaDBMemberDao(con);
//
// ctx.setAttribute("boardService", new DefaultBoardService(boardDao, con));
// ctx.setAttribute("memberService", new DefaultMemberService(memberDao));
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
//}
// 웹 애플리케이션이 시작되었을 때 공유할 자원을 준비시키거나 해제하는 일을 한다.
@WebListener
public class ContextLoaderListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("공유 자원을 준비 중!!");
try {
ServletContext ctx = sce.getServletContext();
DataSource ds = new DataSource("org.mariadb.jdbc.Driver",
"jdbc:mariadb://localhost:3306/studydb", "study", "1111");
System.out.println(ds.getConnection());
BoardDao boardDao = new MariaDBBoardDao(ds);
MemberDao memberDao = new MariaDBMemberDao(ds);
ctx.setAttribute("boardService", new DefaultBoardService(boardDao, ds));
ctx.setAttribute("memberService", new DefaultMemberService(memberDao));
} catch (Exception e) {
e.printStackTrace();
}
}
}
//public class MariaDBBoardDao implements BoardDao {
//
// Connection con;
//
// // DAO가 사용할 의존 객체 Connection을 생성자의 파라미터로 받는다.
// public MariaDBBoardDao(Connection con) {
// this.con = con;
// }
public class MariaDBBoardDao implements BoardDao {
DataSource ds;
// DAO가 사용할 의존 객체 Connection을 생성자의 파라미터로 받는다.
public MariaDBBoardDao(DataSource ds) {
this.ds = ds;
}
// @Override
// public int insert(Board board) throws Exception {
// try (PreparedStatement pstmt = con.prepareStatement(
// "insert into app_board(title,cont,mno) values(?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
@Override
public int insert(Board board) thimport java.sql.Connection;
public class TransactionStatus {
Connection con;
public TransactionStatus(Connection con) {
this.con = con;
}
public Connection getConnection() {
return this.con;
}
}rows Exception {
try (PreparedStatement pstmt = ds.getConnection().prepareStatement(
"insert into app_board(title,cont,mno) values(?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) {
import java.sql.Connection;
public class TransactionStatus {
Connection con;
public TransactionStatus(Connection con) {
this.con = con;
}
public Connection getConnection() {
return this.con;
}
}
import java.sql.Connection;
import com.bitcamp.sql.DataSource;
public class TransactionManager {
DataSource ds;
public TransactionManager(DataSource ds) {
this.ds = ds;
}
public TransactionStatus getTransaction() throws Exception {
//현재 스레드에서 사용할 DB 커넥션 객체를 생성한다.
Connection con = ds.getConnection();
//트랜잭션을 수행할 수 있도록 수동 커밋 상태로 변경한다.
con.setAutoCommit(false);
return new TransactionStatus(con);
}
public void commit(TransactionStatus status) {
try {
// 트랜잭션 보관소에 저장된 DB 커넥션을 사용하여 커밋을 수행한다.
status.get().commit();
} catch (Exception e) {
// 커밋하다가 발생한 예외는 무시한다.
} finally {
try {
status.get().setAutoCommit(true);
} catch (Exception e) {
// 자동 커밋 상태로 설정하는 중에 발생한 오류는 무시한다.
}
}
}
public void rollback(TransactionStatus status) {
try {
// 트랜잭션 보관소에 저장된 DB 커넥션을 사용하여 롤백을 수행한다.
status.get().rollback();
} catch (Exception e) {
// 롤백하다가 발생한 예외는 무시한다.
} finally {
try {
status.get().setAutoCommit(true);
} catch (Exception e) {
// 자동 커밋 상태로 설정하는 중에 발생한 오류는 무시한다.
}
}
}
}
public class DefaultBoardService implements BoardService {
TransactionManager txManager;
BoardDao boardDao;
public DefaultBoardService(BoardDao boardDao, TransactionManager txManager) {
this.boardDao = boardDao;
this.txManager = txManager;
};
@Override
public void add(Board board) throws Exception {
TransactionStatus status = txManager.getTransaction();
try {
if (boardDao.insert(board) == 0) {
throw new Exception("게시글 등록 실패!");
}
boardDao.insertFiles(board);
txManager.commit(status);
} catch (Exception e) {
txManager.rollback(status);
throw e;
}
}