소프트웨어 개발 보안
x_board ← insert/commit
x_board_file ← insert/commit
x_board_file ← insert/commit ← 오류 발생
x_board_file ← insert/commit
오류 발생 시, 예외가 발생하여 나머지 쿼리가 실행되지 않는다.
⇒ 이전에 입려한 데이터는 그대로 유지된다.
⇒ executeUpdate()를 실행할 때마다 자동으로 commit하기 때문이다.
commit을 하게되면, 실제 테이블에 변경(insert/update/delete) 내용을 적용한다.
→ 오류가 발생하기 전에 수행한 데이터 변경(I/U/O)작업을 그래도 유지해야하는 경우가 있고,
→ 모든 작업이 정상적으로 수행되었을 때만 commit 해야하는 경우가 있다.
예) 주문(주문할 제품 등록, 결제 등록) → 둘 다 성공해야만 주문이 완성된다.
x_board ← insert
x_board_file ← insert
x_board_file ← insert ← 오류 발생 → 그 전에 수행한 작업도 취소한다. ⇒ Rollback
x_board_file ← insert
x_board_file ← commit ← 모든 작업이 성공했을 때 테이블에 적용한다.
여러 개의 데이터 변경작업(I/U/O)을 한 단위로 묶은 것
→ JDBC API(프로그래밍)에서는?
con.setAutocommit(false);
insert
insert → 예외 발생시, con.rollback();
update
.
.
.
→ 정상 수행시,
con.commit();
질의할 때마다 Connection을 생성
→ connection을 쓰고 버린다.
⇒ 실행속도 느리고, 메모리를 많이 사용한다.
⇒ 트랜잭션 작업이 종료될 때, 커넥션을 끊기 때문에 커넥션을 끊으면 임시 DB에 보관된 작업 결과가 자동 삭제된다.
⇒ rollback()을 명시적으로 호출할 필요가 없다!
어차피 임시 DB 작업이 다 사라져서 아무런 작업도 실행하지 않은 상태로 돌아간다.
T1(Transaction1)을 실행하다가 예외가 발생했을 때 명시적으로 rollback
을 수행하지 않으면,
T2에서 commit
할 때 기존에 남아있던 T1작업까지 commit
되는 문제가 발생한다!
⇒ connection
을 공유하는 경우 주의하여야 한다!
예외가 발생하면 명시적으로 rollback
하라!
try (Connection con = DriverManager.getConnection( //
"jdbc:mariadb://localhost:3306/studydb?user=study&password=1111");
Statement stmt = con.createStatement()) {
// => 예를 들어 이 예제를 실행할 때 다음과 같이 입력해 본다면 의도와 다른 결과를 얻게 된다!
// 번호? 1
// 제목? okok
// 내용? test', view_count = 300, created_date = '2019-3-3
int count = stmt.executeUpdate(
"update x_board set title = '" + title +
"', contents = '" + contents +
"' where board_id = " + no);
// 위에서 사용자가 입력한 값을 가지고 SQL 문장을 만들면 다음과 같다.
//
// update x_board set title = 'okok',
// contents = 'test', view_count = 300, created_date = '2019-3-3'
// where board_id = 1
//
if (count == 0) {
System.out.println("해당 번호의 게시물이 존재하지 않습니다.");
} else {
System.out.println("변경하였습니다.");
}
}
값을 가지고 문자열로 직접 SQL 문을 만들기 때문에 작성하거나 읽기 힘들다.
SQL 문장과 값이 분리되어 있기 때문에 작성하거나 읽기 쉽다.
사용자가 입력한 값을 가지고 SQL 문장을 만들기 때문에 해킹되기 쉽다.
SQL 문장과 값이 분리되어 다뤄지기 때문에 해킹할 수 없다.
문자열로 SQL 문장을 만들기 때문에 바이너리 타입의 컬럼 값을 설정할 수 없다.
setXxx() 메서드를 호출하여 값을 설정하기 때문에 바이너리 타입의 컬럼 값을 설정할 수 있다.
prepareStatement 예시
try (Connection con = DriverManager.getConnection(
"jdbc:mariadb://localhost:3306/studydb?user=study&password=1111");
// 값이 들어갈 자리에 in-parameter(?)를 지정한다.
// => 데이터 타입에 상관없이 ?를 넣는다.
PreparedStatement stmt =
con.prepareStatement("insert into x_board(title,contents) values(?,?)");) {
// in-parameter에 값을 설정한다.
// => 설정하는 순서는 상관없다. 하지만 유지보수를 위해 순서대로 나열해야한다!
stmt.setString(1, title);
stmt.setString(2, contents);
// 실행할 때는 SQL문을 파라미터로 넘길 필요가 없다.
int count = stmt.executeUpdate();
System.out.printf("%d 개 입력 성공!", count);
}
pk를 꺼내오는 도구: ResultSet
입력 후 PK 값을 리턴 받고 싶다면,
PreparedStatement 객체를 얻을 때 다음과 같은 옵션을 지정하라!
⇒ prepareStatement(sql, 자동생성된 PK 값 리턴 여부)
getGeneratedKeys().getInt(1)
은 PK 값을 리턴한다.
try (Connection con = DriverManager.getConnection(
"jdbc:mariadb://localhost:3306/studydb?user=study&password=1111");
// 입력 후 PK 값을 리턴 받고 싶다면,
// PreparedStatement 객체를 얻을 때 다음과 같은 옵션을 지정하라!
// => prepareStatement(sql, 자동생성된 PK 값 리턴 여부)
//
PreparedStatement stmt = con.prepareStatement(
"insert into x_board(title,contents) values(?,?)",
Statement.RETURN_GENERATED_KEYS);) { // 자동으로 생성된 프라이머리 키 값이 있다면, 나는 리턴받겠다.
stmt.setString(1, title);
stmt.setString(2, contents);
int count = stmt.executeUpdate();
System.out.printf("%d 개 입력 성공!\n", count);
// insert 수행 후 자동 생성된 PK 값은 따로 요구해야 한다.
try (ResultSet rs = stmt.getGeneratedKeys()) {
// insert를 한 개만 했기 때문에 PK도 한 개만 생성되었다.
// 따라서 ResultSet에 대해 여러 번 반복을 할 필요가 없다.
rs.next();
// 자동 생성된 PK 값을 꺼낼 때는 컬럼 이름이나 PK 컬럼의 인덱스로 꺼낸다.
// int no = rs.getInt("board_id"); // MariaDB JDBC Driver에서는 오류 발생!
int no2 = rs.getInt(1);
System.out.printf("입력된 게시글 번호: %d\n", no2);
}
}