JDBC에 포함되어 있는 클래스와 인터페이스들의 연관 관계는 아래와 같다
DriverManager 클래스는 JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체를 생성한다.
Connection 인터페이스는 Statement, PreparedStatement, CallableStatement 구현 객체를 생성하며, 트랙잭션 처리 및 DB 연결을 끊을 때 사용한다.
Statement 인터페이스는 SQL의 DDL(Data Definition Language) 과 DML(Data Manipuilation Language)을 실행할 때 사용한다. 주로 변경되지 않는 정적 SQL 문을 실행할 때 사용한다.
PreparedStatement는 Statement와 동일하게 SQL의 DDL, DML 문을 실행할 때 사용한다. 차이점은 매개변수화된 SQL 문을 사용할 수 있기 때문에 편리성과 보안성이 좋다. 그래서 Statement 보다는 PreparedStatement를 주로 사용한다.
CallableStatement는 DB에 저장되어 있는 프로시저(Procuder)와 함수를 호출할 때 사용한다.
ResultSet은 DB에서 가져온 데이터를 읽을 때 사용한다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class ConnectionExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
System.out.println("연결 성공");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
//연결 끊기
conn.close();
System.out.println("연결 끊기");
} catch (SQLException e) {}
}
}
}
}
연결 성공
연결 끊기
users 테이블에 새로운 사용자 정보를 저장하는 INSERT 문
INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES ('winter', '한겨울', '12345', 25, 'winter@mycompany.com')
값을 ?로 대체한 매개변수화된 INSERT 문으로 변경
INSERT INTO users (userid, username, userpassword, userage, useremail)
VALUES(?, ?, ?, ?, ?)
그리고 INSERT 문을 String 타입 변수 sql에 문자열로 대입
String sql = new StringBuilder()
.append("INSERT INTO users (userid, username, userpassword, userage, useremail) ")
.append("VALUES (?, ?, ?, ?, ?)")
.toString();
매개변수화된 SQL문을 실행하려면 PreparedStatement가 필요.
그리고 ?에 들어갈 값을 지정해주는데, ?는 순서에 따라 1번부터 번호가 부여된다. 값의 타입에 따라 Setter 메소드를 선택한 후 첫 번째에는 ? 순번, 두 번째에는 값을 지정해 준다.
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
pstmt.setString(2, "한겨울");
pstmt.setString(3, "12345");
pstmt.setInt(4, 25);
pstmt.setString(5, "winter@mycompany.com");
users 테이블에 사용자 정보를 저장하는 전체 코드
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UserInsertExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
//매개변수화된 SQL문 작성
String sql = "" +
"INSERT INTO users (userid, username, userpassword, userage, useremail) " +
"VALUES (?, ?, ?, ?, ?)";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
pstmt.setString(2, "한겨울");
pstmt.setString(3, "12345");
pstmt.setInt(4, 25);
pstmt.setString(5, "winter@mycompany.com");
//SQL문 실행
int rows = pstmt.executeUpdate();
System.out.println("저장된 행 수: " + rows);
//PreparedStatement 닫기
pstmt.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
}
}
저장된 행 수: 1
boards 테이블에 게시물 정보를 저장하는 예제 코드.
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class BoardInsertExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
//매개변수화된 SQL문 작성
String sql = "" +
"INSERT INTO boards (btitle, bcontent, bwriter, bdate, bfilename, bfiledata) " +
"VALUES (?, ?, ?, now(), ?, ?)";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(
sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, "눈오는 날");
pstmt.setString(2, "함박눈이 내려요.");
pstmt.setString(3, "winter");
pstmt.setString(4, "snow.jpg");
pstmt.setBlob(5, new FileInputStream("src/ch20/mysql/sec06/snow.jpg"));
//SQL문 실행
int rows = pstmt.executeUpdate();
System.out.println("저장된 행 수: " + rows);
//bno 값 얻기
if(rows == 1) {
ResultSet rs = pstmt.getGeneratedKeys();
if(rs.next()) {
int bno = rs.getInt(1);
System.out.println("저장된 bno: " + bno);
}
rs.close();
}
//PreparedStatement 닫기
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
}
}
저장된 행 수 : 1
저장된 bno: 1 (실행한 횟수에 따라 bno 값은 다를 수 있음)
위 예제 코드를 수정한다면 UPDATE 문 실행을 위해 executeUpdate() 메소드를 호출. 그러면 수정된 행의 수가 리턴
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BoardUpdateExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
//매개변수화된 SQL문 작성
String sql = new StringBuilder()
.append("UPDATE boards SET ")
.append("btitle=?, ")
.append("bcontent=?, ")
.append("bfilename=?, ")
.append("bfiledata=? ")
.append("WHERE bno=?")
.toString();
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "눈사람");
pstmt.setString(2, "눈으로 만든 사람");
pstmt.setString(3, "snowman.jpg");
pstmt.setBlob(4, new FileInputStream("src/ch20/mysql/sec07/snowman.jpg"));
pstmt.setInt(5, 3); //boards 테이블에 있는 게시물 번호(bno) 지정
//SQL문 실행
int rows = pstmt.executeUpdate();
System.out.println("수정된 행 수: " + rows);
//PreparedStatement 닫기
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
}
}
수정된 행 수: 1
2회독 정리
결국 DELETE문으로 데이터를 삭제하게 된다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BoardDeleteExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
//매개변수화된 SQL문 작성
String sql = "DELETE FROM boards WHERE bwriter=?";
//PreparedStatement 얻기 및 값 지정
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");
//SQL문 실행
int rows = pstmt.executeUpdate();
System.out.println("삭제된 행 수: " + rows);
//PreparedStatement 닫기
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if(conn != null) {
try {
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
}
}
삭제된 행 수 : 10 (winter가 작성한 게시물 개수에 따라 삭제된 행 수는 다를 수 있음)
PreparedStatement를 생성할 때 SQL 문이 INSERT, UPDATE, DELETE일 경우에는 executeUpdate() 메소드를 호출하지만, 데이터를 가져오는 SELECT 문일 경우에는 executeQuery() 메소드를 호출해야 한다. excuteQuery() 메소드는 가져온 데이터를 ResultSet에 저장하고 리턴한다.
Resultset rs = pstmt.executeQuery();
SELECT userid, username, userage FROM users
//SELECT 문은 userid, username, usergage 컬럼으로 구성된 ResultSet을 리턴
ResultSet의 특징은 커서가 있는 행의 데이터만 읽을 수 있다는 것이다. 여기서 커서는 행을 가리키는 포인터를 말한다. ResultSet은 실제 가져온 데이터 행의 앞과 뒤에 beforeFirst 행과 afterLast 행이 붙는데, 최초 커서는 beforeFirst를 가리킨다. 따라서 첫 번째 데이터 행인 first 행을 읽으려면 커서를 이동시켜야 한다. 이때 next() 메소드를 사용한다.
boolean result = rs.next();
next) 메소드는 커서를 다음 행으로 이동시키는데, 이동한 행에 데이터가 있으면 true를, 없으 면 false를 리턴한다. 앞의 그림을 보면 last 행까지는 true를 리턴하고 afterLast 행으로 이동하면 false를 리턴하는 것을 볼 수 있다.
만약 SELECT 문으로 가져온 데이터 행이 없다면 beforeFirst 행과 afterLast 행이 붙어 있기 때문 에 첫 번째 next) 결과는 false가 된다. 다음은 SELECT 문으로 가져온 행의 수에 따라서 커서를 이동시키는 코드이다.
//1 개의 데이터 행만 가져올 경우
ResultSet rs = pstmt.executeQuery();
if(rs.next()) {
//첫 번째 데이터 행 처리
} else {
//afterLast 행으로 이동했을 경우
}
//n 개의 데이터 행을 가져올 경우
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
//last 행까지 이동하면서 데이터 행 처리
}
//afterLast 행으로 이동했을 경우
계좌 이체는 두개의 UPDATE문이 필요하다. 똑같이 모두 성공하거나 둘다 실패해야 한다.
//출금
UPDATE accounts SET balance = balance-이체금액 WHERE ano=출금계좌번호
//입금
UPDATE accounts SET balance = balance+이체금액 WHERE ano=입금계좌번호
DB는 트랜잭션을 처리하기 위해 커밋과 롤백을 제공한다. 커밋은 내부 작업을 모두 성공 처리하고, 롤백은 실행 전으로 돌아간다는 의미에서 모두 실패 처리한다.
자동 커밋 기능을 끌 수 있고 꺼지면 아래 코드로 커밋과 롤백 제어가 가능
conn.comit(); //커밋
conn.rollback(); //롤백
트랜잭션을 위한 일반적 코드 작성 패턴
Connection conn = null;
try {
//트랜잭션 시작 ----------------------------------------------------
//자동 커밋 기능 끄기
conn.setAutoCommit(false);
//소작업 처리
...
//소작업 처리
...
//커밋 -> 모두 성공 처리
conn.commit();
//트랜잭션 종료 ----------------------------------------------------
} catch (Exception e) {
try {
//롤백 -> 모두 실패 처리
conn.rollback();
} catch (SQLException e1) {}
} finally {
if(conn != null) {
try {
//원래대로 자동 커밋 기능 켜기
conn.setAutoCommit(true);
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
아래는 accounts 테이블에서 111-111-1111 계좌에서 222-222-2222 계좌로 10,000원을 이체하기 위해 트랜잭션 처리이다.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TransactionExample {
public static void main(String[] args) {
Connection conn = null;
try {
//JDBC Driver 등록
Class.forName("com.mysql.cj.jdbc.Driver");
//연결하기
conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/thisisjava",
"java",
"mysql"
);
//트랜잭션 시작 ----------------------------------------------------
//자동 커밋 기능 끄기
conn.setAutoCommit(false);
//출금 작업
String sql1 = "UPDATE accounts SET balance=balance-? WHERE ano=?";
PreparedStatement pstmt1 = conn.prepareStatement(sql1);
pstmt1.setInt(1, 10000);
pstmt1.setString(2, "111-111-1111");
int rows1 = pstmt1.executeUpdate();
if(rows1 == 0) throw new Exception("출금되지 않았음");
pstmt1.close();
//입금 작업
String sql2 = "UPDATE accounts SET balance=balance+? WHERE ano=?";
PreparedStatement pstmt2 = conn.prepareStatement(sql2);
pstmt2.setInt(1, 10000);
pstmt2.setString(2, "333-222-2222");
int rows2 = pstmt2.executeUpdate();
if(rows2 == 0) throw new Exception("입금되지 않았음");
pstmt2.close();
//커밋 -> 모두 성공 처리
conn.commit();
System.out.println("계좌 이체 성공");
//트랜잭션 종료 ----------------------------------------------------
} catch (Exception e) {
try {
//롤백 -> 모두 실패 처리
conn.rollback();
//원래대로 자동 커밋 기능 켜기
conn.setAutoCommit(true);
} catch (SQLException e1) {}
System.out.println("계좌 이체 실패");
e.printStackTrace();
} finally {
if(conn != null) {
try {
//원래대로 자동 커밋 기능 켜기
conn.setAutoCommit(true);
//연결 끊기
conn.close();
} catch (SQLException e) {}
}
}
}
}
계좌 이체 성공
import java.util.Date;
import lombok.Data;
@Data
public class Board {
private int bno;
private String btitle;
private String bcontent;
private String bwriter;
private Date bdate;
}
import lombok.Data;
@Data
public class User {
private String userId;
private String userName;
정답
private String userPassword;
private int userAge;
private String userEmail;
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class BoardExample {
private Scanner scanner = new Scanner(System.in);
private Connection conn;
public BoardExample() {
try {
Class.forName("oracle.jdbc.OracleDriver");
conn = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521/orcl", "java", "oracle");
} catch (Exception e) {
e.printStackTrace();
exit();
}
}
public void list() {
System.out.println();
System.out.println("[게시물 목록]");
System.out.println("-------------------------------------------------------------------");
System.out.printf("%-6s%-12s%-16s%-40s\n", "no", "writer", "date", "title");
System.out.println("-------------------------------------------------------------------");
try {
String sql = "" + "SELECT bno, btitle, bcontent, bwriter, bdate " + "FROM boards " + "ORDER BY bno DESC";
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Board board = new Board();
board.setBno(rs.getInt("bno"));
board.setBtitle(rs.getString("btitle"));
board.setBcontent(rs.getString("bcontent"));
board.setBwriter(rs.getString("bwriter"));
board.setBdate(rs.getDate("bdate"));
System.out.printf("%-6s%-12s%-16s%-40s \n", board.getBno(), board.getBwriter(), board.getBdate(), board.getBtitle());
}
rs.close();
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
exit();
}
mainMenu();
}
public void mainMenu() {
System.out.println();
System.out.println("-------------------------------------------------------------------");
System.out.println("메인 메뉴: 1.Create | 2.Read | 3.Clear | 4.Join | 5.Exit");
System.out.print("메뉴 선택: ");
정답 String menuNo = scanner.nextLine();
System.out.println();
switch (menuNo) {
case "1" -> create();
case "2" -> read();
case "3" -> clear();
case "4" -> join();
case "5" -> exit();
}
}
public void create() {
Board board = new Board();
System.out.println("[새 게시물 입력]");
System.out.print("제목: ");
board.setBtitle(scanner.nextLine());
System.out.print("내용: ");
board.setBcontent(scanner.nextLine());
System.out.print("작성자: ");
board.setBwriter(scanner.nextLine());
System.out.println("-------------------------------------------------------------------");
System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
System.out.print("메뉴 선택: ");
String menuNo = scanner.nextLine();
if (menuNo.equals("1")) {
try {
String sql = "" + "INSERT INTO boards (bno, btitle, bcontent, bwriter, bdate) " + "VALUES (SEQ_BNO.NEXTVAL, ?, ?, ?, SYSDATE)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, board.getBtitle());
pstmt.setString(2, board.getBcontent());
pstmt.setString(3, board.getBwriter());
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
정답 e.printStackTrace();
exit();
}
} list();
}
public void read() {
System.out.println("[게시물 읽기]");
System.out.print("bno: ");
int bno = Integer.parseInt(scanner.nextLine());
try {
String sql = "" + "SELECT bno, btitle, bcontent, bwriter, bdate " + "FROM boards " + "WHERE bno= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, bno);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
Board board = new Board();
board.setBno(rs.getInt("bno"));
board.setBtitle(rs.getString("btitle"));
board.setBcontent(rs.getString("bcontent"));
board.setBwriter(rs.getString("bwriter"));
board.setBdate(rs.getDate("bdate"));
System.out.println("#############");
System.out.println("번호: " + board.getBno());
System.out.println("제목: " + board.getBtitle());
System.out.println("내용: " + board.getBcontent());
System.out.println("작성자: " + board.getBwriter());
System.out.println("날짜: " + board.getBdate());
System.out.println("--------------------------------------------------------------");
System.out.println("보조 메뉴: 1.Update | 2.Delete | 3.List");
System.out.print("메뉴 선택: ");
String menuNo = scanner.nextLine();
정답 System.out.println();
if (menuNo.equals("1")) {
update(board);
} else if (menuNo.equals("2")) {
delete(board);
}
} rs.close();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
exit();
} list();
}
public void update(Board board) {
System.out.println("[수정 내용 입력]");
System.out.print("제목: ");
board.setBtitle(scanner.nextLine());
System.out.print("내용: ");
board.setBcontent(scanner.nextLine());
System.out.print("작성자: ");
board.setBwriter(scanner.nextLine());
System.out.println("-------------------------------------------------------------------");
System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
System.out.print("메뉴 선택: ");
String menuNo = scanner.nextLine();
if (menuNo.equals("1")) {
try {
String sql = "" + "UPDATE boards SET btitle= ?, bcontent= ?, bwriter= ? " + "WHERE bno= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, board.getBtitle());
정답 pstmt.setString(2, board.getBcontent());
pstmt.setString(3, board.getBwriter());
pstmt.setInt(4, board.getBno());
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
exit();
}
} list();
}
public void delete(Board board) {
try {
String sql = "DELETE FROM boards WHERE bno= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, board.getBno());
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
exit();
}
list();
}
public void clear() {
System.out.println("[게시물 전체 삭제]");
System.out.println("-------------------------------------------------------------------");
System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
System.out.print("메뉴 선택: ");
String menuNo = scanner.nextLine();
if (menuNo.equals("1")) {
try {
String sql = "TRUNCATE TABLE boards";
정답 PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
exit();
}
} list();
}
public void join() {
User user = new User();
System.out.println("[새 사용자 입력]");
System.out.print("아이디: ");
user.setUserId(scanner.nextLine());
System.out.print("이름: ");
user.setUserName(scanner.nextLine());
System.out.print("비밀번호: ");
user.setUserPassword(scanner.nextLine());
System.out.print("나이: ");
user.setUserAge(Integer.parseInt(scanner.nextLine()));
System.out.print("이메일: ");
user.setUserEmail(scanner.nextLine());
System.out.println("-------------------------------------------------------------------");
System.out.println("보조 메뉴: 1.Ok | 2.Cancel");
System.out.print("메뉴 선택: ");
String menuNo = scanner.nextLine();
if (menuNo.equals("1")) {
try {
String sql = "" + "INSERT INTO users (userid, username, userpassword, userage, useremail) " + "VALUES (?, ?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, user.getUserId());
정답 pstmt.setString(2, user.getUserName());
pstmt.setString(3, user.getUserPassword());
pstmt.setInt(4, user.getUserAge());
pstmt.setString(5, user.getUserEmail());
pstmt.executeUpdate();
pstmt.close();
} catch (Exception e) {
e.printStackTrace();
exit();
}
} list();
}
public void exit() {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
}
}
System.out.println("** 게시판 종료 **");
System.exit(0);
}
public static void main(String[] args) {
BoardExample boardExample = new BoardExample();
boardExample.list();
}
}
진짜 sql 오류 왜캐 나는지 모르겠는데 2회독 할 생각에 혈압오름