JDBC1. JDBC란 무엇인가
개념
JDBC는 자바 프로그램이 데이터베이스와 연결되어 작업할 수 있게 해 주는 기술이다.
자바 프로그램 안에서 값을 계산하고 출력하는 일은 할 수 있지만,
데이터베이스에 들어 있는 테이블을 직접 읽거나 바꾸려면 DB와 연결하고 명령을 전달하는 방법이 따로 필요하다.
그 역할을 해 주는 것이 바로JDBC다.
즉,JDBC를 사용하면 자바 프로그램이 DB 서버에 접속해서
필요한SQL을 보내고,
그 실행 결과를 다시 받아와 사용할 수 있다.
처음에는JDBC가 어렵게 느껴질 수 있다.
그런데 큰 흐름만 먼저 잡으면 생각보다 단순하다.
- DB 서버에 연결한다.
SQL을 실행한다.- 조회 결과를 읽거나 변경 결과를 확인한다.
- 사용이 끝난 자원을 닫는다.
그래서 이번 범위는
JDBC문법을 조각조각 외우는 것이 아니라,
자바에서 DB 작업이 어떤 순서로 흘러가는지를 먼저 이해하는 구간이라고 보면 된다.
또 여기서 중요한 점은,
JDBC가 어떤 데이터를 담아 두는 객체 이름이 아니라는 것이다.
JDBC는 자바에서 DB 작업을 처리하는 전체 기술 이름이고,
실제 연결과 실행, 결과 읽기는 여러 객체가 역할을 나눠서 수행한다.
핵심 특징
- 자바에서 데이터베이스 작업을 처리하기 위한 기본 기술이다.
- 조회, 삽입, 수정, 삭제처럼 작업 종류는 달라도 큰 처리 흐름은 비슷하다.
- 핵심은 문법 암기보다 연결 → 실행 → 결과 확인 → 자원 반납 흐름을 이해하는 데 있다.
- 실제 코드에서는
Connection,Statement,PreparedStatement,ResultSet같은 객체가 역할을 나눠서 동작한다.
예제별 역할
DBConnect: 자바 프로그램이 실제로 DB에 접속되는지 확인하는 시작 예제SelectEmp: 연결 후SELECT로 조회하는 가장 기본 흐름을 보여주는 예제CreateTable,InsertData1,UpdateData,DeleteData1,DeleteData2,DropTable: 조회가 아닌 변경 작업이 어떻게 처리되는지 보여주는 예제SelectData3,InsertData2,UpdateData,DeleteData2:PreparedStatement를 사용해 입력값을 다루는 흐름을 보여주는 예제VisitorList계열 예제 : 사용한 자원을 어떻게 안전하게 닫는지 보여주는 예제
헷갈리기 쉬운 부분
JDBC자체가 데이터를 담는 객체 이름은 아니다.
JDBC는 자바에서 DB 작업을 하기 위한 전체 기술 이름이고,
실제 작업은Connection,Statement,PreparedStatement,ResultSet같은 객체가 역할을 나눠서 수행한다.
또
JDBC를 배울 때는SQL문장만 보는 것이 아니라,
연결을 만들고, 실행하고, 결과를 읽고, 마지막에 닫는 전체 흐름까지 함께 봐야 이해가 된다.
2. JDBC에서 먼저 알아야 하는 핵심 객체와 메서드
개념조회 예제를 보기 전에 먼저 알아야 하는 핵심 구성 요소들이 있다.
초보자 기준에서는 이 요소들의 역할을 먼저 이해해야 뒤에 나오는 코드가 덜 어렵다.
여기서 중요한 점은, 이들이 전부 같은 성격은 아니라는 것이다.
Connection: DB와 연결된 상태를 나타내는 객체Statement,PreparedStatement:SQL을 실행하는 객체executeQuery(),executeUpdate(): 조회와 변경을 실행하는 메서드ResultSet: 조회 결과를 한 줄씩 읽는 객체
즉, 이 장은 이름만 외우는 구간이 아니라
연결 → 실행 → 결과 확인 → 자원 반납 흐름 안에서
각 객체와 메서드가 어떤 역할을 맡는지 구분해서 보는 구간이라고 이해하면 된다.
2-1. Connection
개념
Connection은 데이터베이스와 연결된 상태를 나타내는 객체다.
이 안에 직원 정보나 학생 정보가 들어 있는 것이 아니라,
자바 프로그램이 지금 DB 서버와 연결되어 있다는 상태 자체를 의미한다.
쉽게 말하면
Connection은
DB로 들어가는 문이 열려 있는 상태라고 이해하면 된다.
그리고 이 연결이 만들어져야 그다음 단계로 넘어갈 수 있다.
즉,Statement나PreparedStatement도 이 연결을 바탕으로 만들어진다.
핵심 특징
- DB 작업의 출발점이다.
DriverManager.getConnection(...)으로 만든다.Statement,PreparedStatement를 만들기 위한 기반이 된다.- 사용이 끝나면
close()로 닫아야 한다.
예제별 역할
DBConnectSelectEmpSelectData1~5VisitorList~4
부분 예제String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; // DB 서버와 실제 연결을 만든다. Connection conn = DriverManager.getConnection(url, user, passwd);
코드 해설
Connection conn은
“데이터를 담는 변수”가 아니라 “지금 DB와 연결되었다”는 상태를 담는 변수다.
이 연결이 만들어져야만
그다음에Statement stmt = conn.createStatement();같은 코드가 가능해진다.
헷갈리기 쉬운 부분
Connection안에 조회 결과가 저장되는 것은 아니다.
조회 결과는 뒤에서ResultSet으로 따로 받는다.
또
close()는 선택 사항이 아니라,
사용이 끝난 연결을 정리하는 중요한 마무리 작업이다.
2-2. Statement
개념
Statement는 데이터베이스에게SQL문장을 보내는 객체다.
Connection이 DB와 연결된 상태라면,
Statement는 그 연결을 이용해서 실제 명령을 전달하는 도구라고 보면 된다.
즉,
Connection은 연결Statement는 그 연결 위에서SQL을 실행하는 도구이렇게 역할이 나뉜다.
연결만 되어 있다고 해서 바로 조회나 삽입이 실행되는 것은 아니다.
실제 실행을 맡는 객체가 하나 더 필요한데, 그 역할이Statement다.
이때
Statement는 보통SQL문장을 문자열로 직접 만들어 실행한다.
그래서 조건값이 자주 바뀌는 경우에는 뒤에서 볼PreparedStatement와 비교해서 이해하는 것이 좋다.
핵심 특징
Connection만으로는 조회나 삽입을 바로 하지 않는다.createStatement()로 만든다.SELECT,INSERT,UPDATE,DELETE,CREATE TABLE,DROP TABLE같은SQL실행에 사용된다.- 사용이 끝나면
close()로 닫아야 한다.
예제별 역할
SelectEmpCreateTableInsertData1DeleteData1DropTable
부분 예제// 연결을 이용해서 SQL 실행 객체를 만든다. Statement stmt = conn.createStatement();
코드 해설
Statement는
“SQL을 직접 보내는 손” 같은 역할이라고 보면 된다.
이 객체를 만든 뒤에
조회면executeQuery(), 변경이면executeUpdate()를 호출해서 작업을 실행한다.
헷갈리기 쉬운 부분
Statement가 결과를 들고 있는 객체는 아니다.
실행은Statement가 하고, 결과는ResultSet으로 받는다.
또한
Statement도 사용이 끝나면 닫아야 한다.
연결만 닫는다고 모든 정리가 자동으로 끝난다고 생각하면 안 된다.
2-3. executeQuery()
개념
executeQuery()는 조회용SQL을 실행할 때 사용하는 메서드다.
즉,SELECT문처럼 데이터를 읽어 오는 작업에서 사용한다.
이 메서드는 혼자 따로 쓰는 것이 아니라
Statement나PreparedStatement를 통해 호출된다.
조회는 결과표가 필요하므로
executeQuery()를 실행하면 그 결과가ResultSet으로 돌아온다.
핵심 특징
SELECT실행에 사용한다.Statement또는PreparedStatement에서 호출한다.- 반환값은
ResultSet이다.- 결과를 화면에 출력하려면
ResultSet을 다시 읽어야 한다.
예제별 역할
SelectEmpSelectWhereEmpSelectPatternEmpSelectPatternEmp2SelectSalEmpSelectGroupEmpSelectSubqueryEmpSelectJoinEmpSelectData1~5VisitorList~4
부분 예제// SELECT 문을 실행하고 조회 결과를 ResultSet으로 받는다. ResultSet rs = stmt.executeQuery("select ename, job, sal, deptno from emp");
코드 해설조회문을 실행했다고 바로 출력이 끝나는 것은 아니다.
executeQuery()는 결과를 가져오는 시작점이고,
그 뒤에ResultSet을 읽는 과정이 꼭 필요하다.
헷갈리기 쉬운 부분
executeQuery()가 결과를 문자열이나 숫자로 바로 돌려주는 것은 아니다.
조회 결과 전체는ResultSet으로 받고, 그 안에서 다시 필요한 값을 꺼내야 한다.
2-4. executeUpdate()
개념
executeUpdate()는 조회가 아닌 변경 작업을 실행할 때 사용하는 메서드다.
예를 들어 데이터를 넣거나 수정하거나 삭제할 때 사용한다.
또 테이블을 만들거나 삭제하는 작업도 이 메서드로 실행한다.
즉, 이름은update지만 실제 범위는 더 넓다.
이 메서드도
Statement나PreparedStatement를 통해 호출된다.
핵심 특징
INSERT,UPDATE,DELETE,CREATE TABLE,DROP TABLE에 사용한다.Statement또는PreparedStatement에서 호출한다.- 데이터 변경에서는 보통 영향받은 행 수를 반환한다.
- 성공 여부를 확인하는 기준으로 자주 사용한다.
예제별 역할
CreateTableInsertData1InsertData2UpdateDataDeleteData1DeleteData2DropTableInsertImage
부분 예제// 테이블 생성, 데이터 삽입, 수정, 삭제 같은 변경 작업에 사용한다. int rowCount = stmt.executeUpdate("delete from student where name = '둘리'");
코드 해설위 예제에서는
delete문을 실행했기 때문에
몇 행이 삭제되었는지를int값으로 확인할 수 있다.
반면
create table,drop table같은 작업도 같은 메서드로 실행하지만,
이 경우에는 “몇 명이 바뀌었는가”보다는 작업이 정상 실행되었는가 쪽으로 이해하는 것이 자연스럽다.
헷갈리기 쉬운 부분조회문
SELECT에는executeUpdate()를 사용하지 않는다.
조회는executeQuery(), 변경은executeUpdate()로 구분해서 봐야 한다.
2-5. ResultSet
개념
ResultSet은 조회 결과를 담고 있는 객체다.
하지만 결과 전체를 한 번에 꺼내 주는 상자가 아니라,
한 줄씩 이동하면서 읽게 해 주는 객체라고 보는 게 더 정확하다.
그래서 조회 코드를 보면 거의 항상 반복문이 나온다.
그 이유는ResultSet안의 결과를 한 행씩 읽어야 하기 때문이다.
이때 핵심이 되는 메서드가
next()다.
ResultSet은 처음부터 첫 번째 행 위에 서 있는 것이 아니라 첫 행 앞에 있기 때문에,
next()를 호출해서 다음 행으로 이동한 뒤에 값을 읽어야 한다.
핵심 특징
executeQuery()의 결과로 만들어진다.next()로 다음 행이 있는지 확인하고, 있으면 그 행으로 이동한다.getString(),getInt()같은 메서드로 현재 행의 열 값을 꺼낸다.- 반복문과 함께 자주 사용된다.
- 사용이 끝나면
close()로 닫아야 한다.
예제별 역할
SelectEmpSelectPatternEmp2SelectData1~5VisitorList~4
부분 예제ResultSet rs = stmt.executeQuery("select name, score from student"); while (rs.next()) { System.out.println(rs.getString("name")); System.out.println(rs.getInt("score")); }
코드 해설위 코드는
next()로 한 행씩 이동하면서,
현재 행에 있는name은getString()으로 읽고
score는getInt()로 읽는 구조다.
즉,
ResultSet은 행 이동과 열 값 읽기가 함께 이루어지는 객체다.
헷갈리기 쉬운 부분
ResultSet이 있다고 해서 바로 첫 번째 행 데이터를 읽을 수 있는 것은 아니다.
먼저next()로 이동해야 한다.
또
getString(),getInt()는 다음 행으로 이동시키는 메서드가 아니다.
이 메서드들은 현재 위치한 행의 값을 꺼내는 역할만 한다.
2-6. PreparedStatement
개념
PreparedStatement는Statement처럼SQL을 실행하는 객체지만,
값을 넣는 방식이 더 정리되어 있다.
Statement는 문자열을 직접 이어 붙여서SQL을 만들고,
PreparedStatement는?가 들어 있는SQL틀을 먼저 만든 뒤
나중에 그 자리에 값을 넣는다.
즉, 핵심은
SQL구조와 입력값이 분리된다는 점이다.
그래서 사용자 입력이 들어가는 프로그램에서는 이 방식이 훨씬 자연스럽다.
같은
SQL실행 객체라는 점에서는Statement와 비슷하지만,
값을 직접 이어 붙이지 않고setString(),setInt()같은 메서드로 넣는다는 점이 가장 큰 차이다.
핵심 특징
?자리에 값을 넣는 방식이다.setString(),setInt()같은 메서드로 값을 채운다.- 조회면
executeQuery(), 변경이면executeUpdate()를 사용한다.- 사용자 입력이 들어가는 코드에서 자주 사용된다.
- 사용이 끝나면
close()로 닫아야 한다.
예제별 역할
SelectData3SelectData4SelectData5InsertData2UpdateDataDeleteData2InsertImageSelectImage
부분 예제PreparedStatement pstmt = conn.prepareStatement("select score from student where name = ?"); // 첫 번째 ? 자리에 name 값을 넣는다. pstmt.setString(1, name);
코드 해설여기서는 먼저
select score from student where name = ?라는
SQL틀을 준비해 두고,
setString(1, name)으로 첫 번째?자리에 실제 값을 넣는다.
만약 숫자를 넣어야 한다면setInt()를 사용한다.
그 뒤 조회면
executeQuery(),
변경이면executeUpdate()를 호출해서 실행한다.
헷갈리기 쉬운 부분
PreparedStatement를 쓴다고 해서 실행 메서드까지 달라지는 것은 아니다.
조회면executeQuery(), 변경이면executeUpdate()를 사용한다는 점은 똑같다.
또
?만 써 두고 끝나는 것이 아니라,
setString(),setInt()같은 메서드로 실제 값을 넣어 줘야 한다.
3. DBConnect로 실제 연결 확인하기
사용 예제: DBConnect
개념
jar파일을 프로젝트에 연결했다고 바로 끝나는 것은 아니다.
그다음에는 자바 코드에서 실제로 데이터베이스 연결이 되는지 확인해야 한다.
DBConnect예제는 바로 그 역할을 한다.
즉, 이 코드는 지금 프로젝트가MySQL에 실제로 접속할 수 있는 상태인지 확인하는 첫 테스트 코드라고 보면 된다.
여기서 먼저 기억할 점은, 이 장의 중심이 환경 설정 자체에 있는 것이 아니라
설정이 끝난 뒤 자바 코드로 실제 접속이 되는지 확인하는 것에 있다는 점이다.
jar연결은 그 테스트를 하기 위한 준비 단계라고 이해하면 된다.
핵심 특징
Connection이 실제로 만들어지는지 확인한다.DatabaseMetaData로 접속 결과를 확인한다.- 접속 과정에서 오류가 날 수 있으므로
SQLException처리가 함께 들어간다.- 마지막에는
close()로 연결을 닫는다.
3-1. 먼저 JDBC 드라이버 jar를 프로젝트에 연결해야 한다
개념
DBConnect코드가 맞아도, 현재 프로젝트에 JDBC 드라이버가 연결되어 있지 않으면
MySQL에 접속하는 코드를 제대로 실행할 수 없다.
그래서 먼저
MySQL용JDBC드라이버jar를
현재 Eclipse 프로젝트의Build Path > Classpath에 연결해야 한다.
여기서 중요한 것은 드라이버 파일을 PC에 가지고 있는 것만으로는 부족하다는 점이다.
지금 실행할 자바 프로젝트가 그 드라이버를 실제로 사용할 수 있는 상태여야 한다.
설정 보는 순서
- 프로젝트를 우클릭하고
Properties로 들어간다.Java Build Path를 연다.- 외부 JDBC 드라이버
jar가 아직 추가되지 않은 상태인지 확인한다.Add External JARs...를 눌러mysql-connector-java-8.0.17.jar를 추가한다.- 추가 후
Classpath아래에 해당jar가 보이는지 확인한다.Apply and Close로 저장한다.
이미지처음 상태에서는 외부 JDBC 드라이버
jar가 아직 추가되지 않은 상태다.
이후
Add External JARs...로mysql-connector-java-8.0.17.jar를 선택해서 추가한다.
추가가 끝나면
Classpath아래에jar가 표시된다.
설정 해설이 단계의 핵심은
jar를 그냥 받아 두는 것이 아니라,
현재 실행할 자바 프로젝트 안에 연결해야 한다는 점이다.
즉,
- PC에
jar가 존재하는 것- Eclipse 프로젝트의
Classpath에 그jar가 연결된 것은 다르다.
DBConnect가 실행되지 않다가Build Path에서jar를 추가한 뒤 동작했다면,
이건 코드 문법 문제가 아니라 프로젝트가 JDBC 드라이버를 아직 사용하지 못하던 상태였다고 이해하면 된다.
헷갈리기 쉬운 부분
jar파일이 폴더 안에 있는 것만으로는 충분하지 않다.
반드시 현재 프로젝트의Build Path > Classpath아래에 들어가 있어야 자바 코드가 그 드라이버를 사용할 수 있다.
3-2. DBConnect 코드로 실제 연결 확인하기
코드 보는 순서
- URL, 사용자명, 비밀번호를 준비하는 부분을 본다.
DriverManager.getConnection(...)으로 연결을 얻는 부분을 본다.DatabaseMetaData로 DB 서버 정보를 꺼내는 부분을 본다.SQLException처리와finally에서의 자원 정리를 함께 본다.
예제: DBConnectpackage jdbcexam; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.SQLException; public class DBConnect { public static void main(String[] args) { Connection conn = null; try { // 1. 접속할 DB 정보(URL, 사용자명, 비밀번호)를 준비한다. String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; // 2. DriverManager를 통해 실제 데이터베이스 연결을 만든다. conn = DriverManager.getConnection(url, user, passwd); // 3. 연결이 성공하면 안내 문구를 출력한다. System.out.println("데이터베이스에 접속했습니다."); // 4. 연결된 DB의 메타 정보를 확인한다. DatabaseMetaData meta = conn.getMetaData(); System.out.println("DB 서버명: " + meta.getDatabaseProductName()); System.out.println("사용자명: " + meta.getUserName()); } catch (SQLException se1) { System.out.println(se1); } finally { try { // 5. 사용이 끝난 연결은 닫는다. if (conn != null) { conn.close(); } } catch (SQLException se2) { System.out.println(se2.getMessage()); } } } }
코드 해설이 코드에서 가장 중요한 부분은
DriverManager.getConnection(url, user, passwd);이다.
이 줄이 실행되면 접속 정보에 맞는 실제 DB 연결이 만들어지고, 그 연결 상태가Connection객체로 돌아온다.
그다음
DatabaseMetaData를 통해
지금 어떤 DB 서버에 어떤 계정으로 접속했는지 확인한다.
즉, 단순히 “연결됨”만 보는 것이 아니라 어디에 어떻게 붙었는지까지 확인하는 역할을 한다.
또 접속 정보가 틀렸거나 드라이버 연결이 잘못되었거나 DB 서버가 실행 중이 아니면
예외가 발생할 수 있다.
그래서catch(SQLException se1)로 접속 과정의 오류를 처리한다.
마지막에는
finally에서close()를 호출해
연결이 성공했든 중간에 문제가 생겼든 사용한 연결을 정리한다.
흐름을 한 줄로 정리하면 이렇다.
접속 정보 준비 →
getConnection()으로 연결 생성 → 연결 성공 메시지 출력 →DatabaseMetaData확인 → 작업 종료 후close()
오류가 발생하면SQLException으로 처리
3-3. 연결 문자열 보기
개념접속 코드를 외우는 것보다 더 중요한 것은
url한 줄이 어떤 의미를 가지고 있는지 이해하는 것이다.
아래 문자열은
어느 DB 서버의 어떤 데이터베이스에 어떤 방식으로 접속할지를 한 줄로 적어 둔 것이다.
String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC";
구성 요소 보기
jdbc:mysql::MySQL용JDBC주소 형식localhost: 내 컴퓨터에서 실행 중인 DB 서버3306:MySQL기본 포트 번호edudb: 접속할 데이터베이스 이름characterEncoding=UTF-8: 한글 인코딩 설정serverTimezone=UTC: 시간대 관련 설정
설명 해설즉, 이 문자열은
“내 컴퓨터에서 실행 중인MySQL서버의3306포트에 접속해서
edudb데이터베이스를 사용하겠다”는 뜻을 담고 있다.
그래서 접속 코드가 잘 실행되더라도
url안의 서버 주소, 포트 번호, DB 이름이 잘못되면 원하는 DB에 연결되지 않는다.
이 한 줄은 단순한 문자열이 아니라 접속 대상 자체를 정하는 정보라고 이해하면 된다.
헷갈리기 쉬운 부분
Connection안에 실제 데이터가 들어 있는 것은 아니다.
Connection은 데이터베이스와 연결되어 있는 상태를 의미한다.
또
localhost는 내 컴퓨터를 뜻하므로 다른 PC의 DB 서버에 접속해야 한다면 이 부분은 달라질 수 있다.
4. emp 테이블로 보는 조회 기본 흐름
개념이제 연결이 되었으니, 그다음은 실제로 데이터를 읽어 오는 단계다.
조회는 데이터베이스에SELECT문을 보내고, 그 결과를 자바 쪽으로 받아오는 작업이다.
여기서
emp테이블은 이미 데이터가 들어 있는 상태라서,
테이블 생성이나 샘플 데이터 입력 과정을 거치지 않고 조회 흐름 자체에 집중해서 보기 좋은 예제다.
그런데 자바에서는 조회 결과가 바로 변수에 들어오지 않는다.
먼저Statement로 조회문을 실행하고,
그 결과를ResultSet으로 받은 뒤,
그 안의 값을 한 줄씩 읽어야 한다.
그래서 조회 프로그램의 기본 흐름은 이렇다.
Connection준비Statement생성executeQuery()실행ResultSet읽기- 자원 닫기
핵심 특징
- 조회용
SQL은executeQuery()로 실행한다.- 조회 결과는
ResultSet으로 반환된다.- 결과를 출력하려면
ResultSet을 다시 읽어야 한다.- 같은 조회라도 전체 조회, 조건 조회, 정렬, 그룹 조회, 조인처럼 형태가 조금씩 달라질 수 있다.
4-1. 가장 기본적인 전체 조회
사용 예제: SelectEmp
예제: SelectEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 서버와 연결함 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체를 만듦 ResultSet rs = stmt.executeQuery("select ename, job, format(sal, 0) sal, deptno from emp"); // 3. emp 테이블 조회를 실행하고 결과를 받음 System.out.printf("%10s%10s%10s%10s\n", "성명", "직무", "급여", "부서"); System.out.println(" -----------------------------------------"); while (rs.next()) { // 4. 조회 결과를 한 행씩 이동하면서 읽음 System.out.printf("%10s%10s%10s원%10d\n", rs.getString("ename"), // 현재 행의 이름 rs.getString("job"), // 현재 행의 직무 rs.getString("sal"), // SQL에서 미리 가공한 급여 문자열 rs.getInt("deptno")); // 현재 행의 부서번호 } rs.close(); // 5. ResultSet 자원 반납 stmt.close(); // 6. Statement 자원 반납 conn.close(); // 7. Connection 자원 반납 } }
코드 해설이 코드는 직원 테이블에서 이름, 직무, 급여, 부서를 조회하는 가장 기본 예제다.
조회 프로그램이 어떤 순서로 움직이는지 한 번에 보여 주는 대표 예제라고 보면 된다.
createStatement()는SQL실행 객체를 만드는 부분이고,
executeQuery()는 실제 조회를 실행하는 부분이다.
그 결과가
ResultSet rs에 들어간다.
이후while (rs.next())로 결과를 한 행씩 이동하면서,
현재 행의ename,job,sal,deptno값을 꺼내 출력한다.
여기서
sal은SQL안에서 미리format(sal, 0)으로 가공했기 때문에
자바에서는 다시 숫자 포맷을 만들지 않고 문자열처럼 바로 출력할 수 있다.
즉, 이 예제는 기본 조회 흐름을 보여 주면서, 조회 결과를SQL쪽에서 미리 보기 좋게 가공할 수도 있다는 점도 함께 보여 준다.
코드 보는 순서
- DB 접속 정보를 준비한다.
Connection을 만든다.Statement를 만든다.executeQuery()로 조회를 실행한다.ResultSet을rs.next()로 한 줄씩 이동하면서 필요한 열 값을 읽는다.- 출력 형식에 맞게 보여 준다.
- 사용이 끝난 자원을 닫는다.
4-2. 조건을 붙여서 조회하기
사용 예제: SelectWhereEmp, SelectPatternEmp
개념조회는 모든 행을 다 가져오는 것만 있는 게 아니다.
보통은 필요한 데이터만 골라 가져온다.
예를 들어 이런 경우다.
- 커미션이 있는 직원만 보고 싶다.
- 이름에 특정 문자가 들어간 직원만 찾고 싶다.
이럴 때
WHERE,LIKE,IS NOT NULL같은 조건이 사용된다.
즉,SELECT문도 조건을 어떻게 주느냐에 따라 가져오는 범위가 달라진다는 뜻이다.
예제: SelectWhereEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectWhereEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 System.out.println(conn.getClass().getName()); // 2. 실제 생성된 Connection 구현 클래스 이름 확인 Statement stmt = conn.createStatement(); // 3. SQL 실행 객체 생성 System.out.println(stmt.getClass().getName()); // 4. 실제 생성된 Statement 구현 클래스 이름 확인 ResultSet rs = stmt.executeQuery("SELECT ename, sal, comm FROM emp WHERE comm is not null"); // 5. 커미션이 null이 아닌 직원만 조회 System.out.println(rs.getClass().getName()); // 6. 실제 생성된 ResultSet 구현 클래스 이름 확인 System.out.printf("%8s%8s%8s\n", "성명", "급여", "커미션"); System.out.println(" -----------------------------------"); while (rs.next()) { // 7. 조건에 맞는 결과를 한 행씩 읽음 System.out.printf("%10s%10d%10d\n", rs.getString("ename"), rs.getInt("sal"), rs.getInt("comm")); } rs.close(); stmt.close(); conn.close(); // 8. 사용한 자원을 순서대로 닫음 } }
예제: SelectPatternEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectPatternEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 ResultSet rs = stmt.executeQuery("select ename, job, sal, deptno from emp where ename like '%T%'"); // 3. 이름에 T가 들어간 직원만 조회 System.out.printf("%8s%8s%8s%4s\n", "성명", "직무", "급여", "부서"); System.out.println(" -----------------------------------"); while (rs.next()) { // 4. 조건에 맞는 각 행의 값을 꺼내 출력 System.out.printf("%10s%12s%8d%4d\n", rs.getString("ename"), rs.getString("job"), rs.getInt("sal"), rs.getInt("deptno")); } rs.close(); stmt.close(); conn.close(); // 5. 자원 반납 } }
코드 해설
SelectWhereEmp는comm is not null조건을 사용해서
커미션이 있는 직원만 조회하는 예제다.
이 예제에서는 조건 조회와 함께, 실제로 만들어진Connection,Statement,ResultSet객체의 구현 클래스 이름도 확인해 본다.
SelectPatternEmp는LIKE '%T%'를 사용해
이름 안에T가 들어간 직원만 조회한다.
여기서%는 앞이나 뒤에 어떤 글자가 와도 괜찮다는 뜻이므로,
이 예제는 글자 패턴으로 찾는 조건 조회라고 이해하면 된다.
즉, 두 예제는 둘 다 조건 조회이지만
하나는IS NOT NULL로 값의 존재 여부를 검사하고,
다른 하나는LIKE로 문자 패턴을 검사한다는 차이가 있다.
4-3. ResultSet.next() 때문에 생기는 실수
사용 예제: SelectPatternEmp2
개념
ResultSet은 조회 결과를 담고 있는 객체다.
그런데 처음 받았을 때부터 첫 번째 행을 바로 읽을 수 있는 상태는 아니다.
처음에는 첫 행 앞에 서 있다고 이해하면 쉽다.
그래서 실제 값을 읽으려면next()를 먼저 호출해야 한다.
여기서 중요한 점은
next()가 단순히 “행이 있나?”만 확인하는 메서드가 아니라는 것이다.
- 다음 행이 있는지 확인하고
- 있으면 그 행으로 이동한다.
즉, 확인 + 이동을 함께 하는 메서드다.
이 성질 때문에 결과가 있을 수도 있고 없을 수도 있는 조회에서는
next()를 어떻게 쓰느냐에 따라 첫 번째 행을 놓치는 실수가 생길 수 있다.
예제: SelectPatternEmp2package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Random; public class SelectPatternEmp2 { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 String munja; if(new Random().nextBoolean()) munja = "Z"; else munja = "S"; // 3. 검색에 사용할 문자를 Z 또는 S 중 하나로 결정 ResultSet rs = stmt.executeQuery( "select ename, job, sal, deptno from emp where ename like '%" + munja + "%'" ); // 4. 이름에 해당 문자가 들어간 직원 조회 if (rs.next()) { // 5. 결과가 하나 이상 있으면 true // 이 순간 이미 첫 번째 행으로 이동한 상태임 System.out.printf("%8s%8s%8s%4s\n", "성명", "직무", "급여", "부서"); System.out.println(" -----------------------------------"); do { // 6. 현재 행의 값을 먼저 출력함 System.out.printf("%10s%12s%8d%4d\n", rs.getString("ename"), rs.getString("job"), rs.getInt("sal"), rs.getInt("deptno")); } while (rs.next()); // 7. 다음 행이 있으면 이동해서 계속 반복함 } else { // 8. 결과가 없으면 안내 문구 출력 System.out.println("이름에 " + munja + "를 포함하고 있는 직원은 없어요"); } rs.close(); stmt.close(); conn.close(); // 9. 자원 반납 } }
코드 해설이 코드의 핵심은
if(rs.next())와do-while조합이다.
if(rs.next())가 참이면 적어도 한 행은 있다는 뜻이고,
그 순간 이미 첫 번째 행으로 이동했다.
즉, 첫 번째
next()호출이 이미 첫 행을 가리키는 상태를 만든 것이다.
그래서 그 안에서 다시while(rs.next())만 사용하면
반복문은 두 번째 행부터 읽게 되고, 첫 번째 행은 건너뛰게 된다.
이 문제를 막기 위해
do { ... } while(rs.next());를 사용했다.
이 구조는 첫 번째 행을 바로 출력한 뒤, 다음 행이 있으면 계속 반복한다.
동시에 이 예제는 조회 결과가 없을 수도 있다는 상황도 함께 보여 준다.
결과가 있으면if + do-while구조로 출력하고,
결과가 없으면else에서 안내 문구를 출력한다.
4-4. 조회를 조금 더 확장하면 나오는 예제들
사용 예제: SelectSalEmp, SelectGroupEmp, SelectSubqueryEmp, SelectJoinEmp
개념여기까지가 조회의 기본 구조였다면,
이제부터는 그 구조 위에서 조회를 조금 더 확장해서 사용하는 방법을 보는 단계다.
조회가 조금만 복잡해지면
단순히 “가져온다”에서 끝나지 않고 어떻게 가져올지까지 중요해진다.
정렬은 보기 좋은 순서로 바꾸고 싶을 때 사용한다.
그룹 조회는 개별 행이 아니라 요약 결과가 필요할 때 사용한다.
서브쿼리는 조건을 먼저 계산해야 할 때 사용한다.
조인은 다른 테이블의 정보와 함께 봐야 할 때 사용한다.
예제: SelectSalEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectSalEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 ResultSet rs = stmt.executeQuery( "SELECT ename, concat(format(sal, 1),'달러') fsal FROM emp ORDER BY sal DESC" ); // 3. 급여를 높은 순으로 정렬해서 조회 while (rs.next()) { // 4. 정렬된 결과를 한 행씩 출력 System.out.println(rs.getString("ename") + " " + rs.getString("fsal")); } rs.close(); stmt.close(); conn.close(); // 5. 자원 반납 } }
예제: SelectGroupEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectGroupEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 ResultSet rs = stmt.executeQuery( "SELECT ifnull(deptno,'미정') deptno, concat(count(*),'명') su FROM emp GROUP BY deptno" ); // 3. deptno별로 묶은 뒤 각 부서 인원 수를 계산 while (rs.next()) { // 4. 부서별 요약 결과를 한 행씩 출력 System.out.println(rs.getString("deptno") + " " + rs.getString("su")); } rs.close(); stmt.close(); conn.close(); // 5. 자원 반납 } }
예제: SelectSubqueryEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Scanner; public class SelectSubqueryEmp { public static void main(String[] args) throws Exception { Scanner scan = new Scanner(System.in); String departmentname = scan.nextLine(); // 1. 사용자가 찾고 싶은 부서명을 입력함 String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 2. DB 연결 생성 Statement stmt = conn.createStatement(); // 3. SQL 실행 객체 생성 ResultSet rs = stmt.executeQuery( "SELECT ename, job, sal, deptno FROM emp " + "WHERE deptno = (SELECT deptno FROM dept WHERE dname = '" + departmentname + "')" ); // 4. 먼저 dept에서 부서번호를 구하고, 그 번호로 emp를 다시 조회함 while (rs.next()) { // 5. 조건에 맞는 직원 이름 출력 System.out.println(rs.getString("ename")); } rs.close(); stmt.close(); conn.close(); scan.close(); // 6. 사용한 자원을 닫음 } }
예제: SelectJoinEmppackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class SelectJoinEmp { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 연결 생성 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 ResultSet rs = stmt.executeQuery( "SELECT ename, dname FROM emp INNER JOIN dept USING (deptno)" ); // 3. emp와 dept를 deptno로 연결해서 직원명과 부서명을 함께 조회 while (rs.next()) { // 4. 조인 결과를 한 행씩 출력 System.out.println(rs.getString("ename") + " " + rs.getString("dname")); } rs.close(); stmt.close(); conn.close(); // 5. 자원 반납 } }
코드 해설
SelectSalEmp는 급여를 높은 순으로 정렬해서 보여주는 예제다.
여기서 핵심은ORDER BY sal DESC이고,
이 조건 때문에 같은 조회라도 결과 순서가 달라진다.
concat(format(sal, 1), '달러')는 출력용 가공이고, 정렬의 핵심은 아니다.
SelectGroupEmp는 직원 한 명 한 명을 그대로 보여주는 것이 아니라,
같은deptno끼리 먼저 묶은 뒤 각 묶음의 개수를 세어
부서별 인원 수 같은 요약 결과를 만든다.
그래서 이 예제는 개별 행 조회가 아니라GROUP BY를 이용한 묶음 결과 조회라고 보면 된다.
SelectSubqueryEmp는 먼저 안쪽SELECT로 부서명에 맞는 부서번호를 구한 다음,
그 번호를 바깥 조회의 조건으로 사용한다.
즉, 조건을 바로 쓰기 어려울 때 먼저 값을 구해서 바깥쪽 조회에 넘기는 구조다.
SelectJoinEmp는emp만 봐서는 알 수 없는 부서 이름을 함께 보기 위해
dept테이블과 연결하는 예제다.
즉, 직원 이름은emp에 있고 부서 이름은dept에 있으므로,
두 테이블을deptno로 연결해야 직원 이름과 부서 이름을 한 번에 볼 수 있다.
5. student 테이블은 먼저 준비하고 나서 조회해야 한다
개념
student예제는emp처럼 원래 데이터가 들어 있는 테이블을 바로 조회하는 구조가 아니다.
그래서 이쪽은 순서를 잘 잡는 게 중요하다.
emp파트가 이미 준비된 테이블을 이용해 조회 흐름을 보는 예제였다면,
student파트는 테이블을 직접 만들고, 데이터를 넣고, 그다음에 조회와 수정, 삭제까지 이어서 확인하는 구조라고 보면 된다.
먼저 해야 하는 흐름은 이렇다.
- 테이블 생성
- 데이터 삽입
- 그다음 조회
- 수정과 추가 입력
- 삭제
- 마지막에 테이블 삭제
즉,
student파트는
테이블을 준비한 뒤에 조회 예제가 의미를 가지는 구조이면서,
동시에JDBC로 테이블을 만들고 사용하고 정리하는 전체 흐름의 출발점이라고 이해하면 된다.
5-1. student 테이블 생성
사용 예제: CreateTable
개념
student예제를 실행하려면 먼저 테이블이 있어야 한다.
이 단계는 데이터를 조회하는 단계가 아니라,
조회할 틀을 먼저 만드는 단계다.
여기서 중요한 점은
create table문이 단순히 이름만 만드는 것이 아니라,
앞으로 어떤 컬럼을 가지고 어떤 형태로 데이터를 저장할지까지 함께 정한다는 것이다.
즉, 이 단계에서는
- 테이블 이름은
student- 학생 이름을 저장할
name컬럼- 점수를 저장할
score컬럼name을 기본키로 사용한다는 규칙을 한 번에 정의한다.
예제: CreateTablepackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class CreateTable { public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; conn = DriverManager.getConnection(url, user, passwd); // 1. DB 서버와 연결함 System.out.println("데이터베이스에 접속했습니다."); stmt = conn.createStatement(); // 2. SQL 실행 객체를 만듦 stmt.executeUpdate("create table student (name varchar(8) primary key, score int)"); // 3. student 테이블을 생성함 // name 컬럼은 문자열, score 컬럼은 정수 // name은 기본키라서 중복될 수 없음 System.out.println("student 테이블 생성"); } catch (SQLException se1) { // 4. 테이블 생성 중 발생한 오류를 처리함 System.out.println(se1); } finally { try { stmt.close(); // 5. Statement 자원 반납 conn.close(); // 6. Connection 자원 반납 } catch (SQLException se2) { System.out.println(se2.getMessage()); } } } }
코드 해설이 예제는
student라는 새 테이블을 만드는 코드다.
아직 조회가 아니라 준비 단계라는 점이 중요하다.
create table student (name varchar(8) primary key, score int)이 부분이
실제로 테이블 구조를 정하는 핵심이다.
즉, 학생 이름은 최대 8글자 문자열로 저장하고, 점수는 정수로 저장하며,
name은 중복될 수 없는 기본키로 사용하겠다는 뜻이다.
여기서는
SELECT가 아니라 테이블 생성이 목적이므로
executeUpdate()를 사용한다.
또 코드 뒤쪽의
finally는
작업이 끝난 뒤Statement와Connection을 닫아서 자원을 정리하기 위한 부분이다.
헷갈리기 쉬운 부분테이블 생성도 조회가 아니므로
executeQuery()가 아니라executeUpdate()를 사용한다.
또 이미 같은 이름의
student테이블이 존재하면
다시 만들 때 오류가 날 수 있다.
즉, 이 예제는 처음 테이블을 준비하는 상황에서 실행된다고 이해해야 한다.
5-2. student 데이터 삽입
사용 예제: InsertData1
개념테이블만 만들어 두면 아직 안에 데이터가 없다.
그래서 조회 전에 먼저 샘플 데이터를 넣어야 한다.
즉,
CreateTable이 조회할 틀을 만드는 단계였다면,
InsertData1은 그 틀 안에 실제로 조회할 수 있는 행 데이터를 채워 넣는 단계다.
여기서는 사용자가 값을 입력하는 것이 아니라,
미리 정해 둔 학생 이름과 점수를 하나씩 넣어서
뒤의 조회 예제를 바로 실행할 수 있게 준비한다.
예제: InsertData1package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class InsertData1 { public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; conn = DriverManager.getConnection(url, user, passwd); // 1. DB 서버와 연결함 System.out.println("데이터베이스에 접속했습니다."); stmt = conn.createStatement(); // 2. SQL 실행 객체를 만듦 stmt.executeUpdate("insert into student values ('둘리', 100)"); // 3. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('또치', 90)"); // 4. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('도우너', 95)"); // 5. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('희동이', 80)"); // 6. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('마이콜', 85)"); // 7. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('고길동', 60)"); // 8. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('짱구', 90)"); // 9. 학생 1명 데이터 삽입 stmt.executeUpdate("insert into student values ('짱아', 75)"); // 10. 학생 1명 데이터 삽입 System.out.println("student 테이블에 데이터 삽입 완료"); } catch (SQLException se1) { // 11. 데이터 삽입 중 발생한 오류를 처리함 System.out.println(se1.getMessage()); } finally { try { stmt.close(); // 12. Statement 자원 반납 conn.close(); // 13. Connection 자원 반납 } catch (SQLException se2) { System.out.println(se2.getMessage()); } } } }
코드 해설이 예제는
student테이블에 조회용 샘플 데이터를 넣는 코드다.
역시 변경 작업이므로executeUpdate()를 사용한다.
여기서 중요한 점은
insert into student values (...)한 줄이
학생 한 명의 데이터를 한 행씩 추가하는 작업이라는 것이다.
즉, 이 코드는 여러 명의 학생 데이터를 한 번에 넣는 것이 아니라,
executeUpdate()를 여러 번 호출해서 여러 행을 차례대로 추가하고 있다.
또 이 예제는 사용자가 직접 입력하는 구조가 아니라,
조회 연습을 위해 미리 준비한 데이터를 넣는 예제라는 점도 중요하다.
그래서 뒤에서 나올 입력 기반 예제와는 성격이 조금 다르다.
여기까지 끝나야 비로소
SelectData1,SelectData2,SelectData3같은 조회 예제가 제대로 의미를 가진다.
즉, 조회문이 동작하더라도 읽어 올 데이터가 없으면 결과를 확인하기 어렵기 때문이다.
헷갈리기 쉬운 부분데이터 삽입도 조회가 아니므로
executeQuery()가 아니라executeUpdate()를 사용한다.
또
student테이블에서name은 기본키이기 때문에
이미 들어 있는 이름을 다시 삽입하면 오류가 날 수 있다.
즉, 같은 학생 이름을 중복해서 넣을 수는 없다고 이해하면 된다.
6. student 테이블로 보는 조회
개념이제
student테이블이 준비되었으니
그다음부터는 조회 예제를 볼 수 있다.
이 구간은
emp에서 봤던 조회 흐름이
사용자 입력과 함께 어떻게 바뀌는지 확인하는 단계라고 보면 된다.
특히 여기서는 단순히 전체 데이터를 읽는 것에서 끝나지 않고,
- 사용자가 값을 입력하고
- 그 입력값이 조회 조건이 되고
- 결과가 한 명일 수도, 여러 명일 수도, 없을 수도 있는
구조까지 함께 보게 된다.
즉,
6번은student테이블을 이용해
입력값이 들어가는 조회 구조와Statement/PreparedStatement차이를 본격적으로 확인하는 구간이라고 이해하면 된다.
6-1. 전체 조회
사용 예제: SelectData1
예제: SelectData1package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class SelectData1 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); Statement stmt = conn.createStatement()) { // 1. DB 연결과 SQL 실행 객체를 try-with-resources로 생성함 // 블록이 끝나면 자동으로 닫히는 구조임 ResultSet rs = stmt.executeQuery("select name, score from student"); // 2. student 테이블 전체 조회를 실행하고 결과를 받음 while (rs.next()) { // 3. 결과를 한 행씩 이동하면서 읽음 // 여기서는 컬럼명 대신 순서 번호로 값을 꺼냄 System.out.println(rs.getString(1) + "학생은 " + rs.getInt(2) + "점 입니다."); } rs.close(); // 4. ResultSet 자원 반납 } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설
SelectData1은 가장 기본적인 전체 조회다.
student테이블의 모든 학생을 한 줄씩 읽어 와서 출력한다.
이 예제도 흐름은 같다.
Statement를 만들고,executeQuery()로 조회를 실행한 뒤,
결과를ResultSet으로 받아while(rs.next())로 한 줄씩 읽는다.
여기서는
rs.getString(1),rs.getInt(2)처럼
컬럼 이름 대신 조회한 컬럼의 순서 번호로 값을 꺼내고 있다는 점도 볼 수 있다.
즉, 첫 번째 컬럼은name, 두 번째 컬럼은score다.
또
Connection과Statement를try (...)안에 선언했기 때문에
블록이 끝나면 자동으로 닫히는 구조라는 점도 중요하다.
6-2. 이름으로 한 명 조회
사용 예제: SelectData2
개념이번에는 전체 조회가 아니라,
사용자가 입력한 이름으로 한 학생만 찾는 구조다.
여기서 핵심 흐름은 이렇다.
- 사용자에게 이름을 입력받는다.
- 입력한 이름을 조회 조건으로 사용한다.
- 결과가 있으면 점수를 출력한다.
- 결과가 없으면 데이터가 없다고 안내한다.
즉, 이 예제는 입력 → 조건 조회 → 결과 유무 판단 흐름을 보여 준다.
예제: SelectData2package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class SelectData2 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); Statement stmt = conn.createStatement(); Scanner scan = new Scanner(System.in);) { // 1. DB 연결, SQL 실행 객체, 입력 객체를 준비함 System.out.print("학생 이름을 입력하세요 : "); String name = scan.nextLine(); // 2. 조회 조건으로 사용할 학생 이름을 입력받음 ResultSet rs = stmt.executeQuery("select score from student where name = '" + name + "'"); // 3. 입력한 이름을 SQL 문자열에 직접 이어 붙여 조건 조회를 실행함 if (rs.next()) // 4. 결과가 있으면 첫 번째 행으로 이동한 상태가 됨 System.out.println(name + "학생의 점수 : " + rs.getInt("score")); else // 5. 결과가 없으면 안내 문구 출력 System.out.println(name + "학생에 대한 데이터가 없습니다."); System.out.println("수행 종료..."); rs.close(); // 6. ResultSet 자원 반납 } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설
SelectData2는 입력한 이름을 조건으로 해서 점수를 조회한다.
결과가 있을 수도 있고 없을 수도 있으므로if(rs.next())구조가 들어간다.
여기서 중요한 점은
입력한 이름을where name = '..."'형태로 SQL 문자열에 직접 이어 붙여서 조건을 만들고 있다는 것이다.
즉, 이 예제는 사용자 입력을 받아 조건 조회를 하는 가장 기본 형태이고,
바로 다음SelectData3에서 이 방식을PreparedStatement로 다시 바꿔 보게 된다.
또 이름으로 찾는 조회는 보통 결과가 한 행이거나 없을 가능성이 크기 때문에,
먼저rs.next()로 결과가 존재하는지 확인한 뒤 점수를 출력하는 구조를 사용한다.
6-3. PreparedStatement로 같은 조회 다시 보기
사용 예제: SelectData3
개념
SelectData2와 같은 기능을
이번에는PreparedStatement방식으로 다시 본다.
여기서 봐야 할 핵심은 기능 차이가 아니라
입력값을 SQL에 넣는 방식의 차이다.
즉,
SelectData2는 SQL 문자열을 직접 이어 붙였고SelectData3는?가 들어 있는 SQL 틀을 먼저 만든 뒤setString()으로 값을 나중에 넣는다.
그래서 이 예제는 단순 조회 예제가 아니라
Statement방식과PreparedStatement방식의 차이를 비교해서 보는 예제다.
예제: SelectData3package jdbcexam; 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 SelectData3 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("select score from student where name = ?"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. ?가 들어 있는 SQL 틀을 먼저 준비함 // 3. 입력 객체 준비 System.out.print("학생 이름을 입력하세요 : "); String name = scan.nextLine(); // 4. 조회 조건으로 사용할 이름 입력 pstmt.setString(1, name); // 5. 첫 번째 ? 자리에 실제 이름 값을 넣음 ResultSet rs = pstmt.executeQuery(); // 6. 값을 채운 SQL로 조회를 실행하고 결과를 받음 if (rs.next()) // 7. 결과가 있으면 점수 출력 System.out.println(name + "학생의 점수 : " + rs.getInt("score")); else // 8. 결과가 없으면 안내 문구 출력 System.out.println(name + "학생에 대한 데이터가 없습니다."); System.out.println("수행 종료..."); rs.close(); // 9. ResultSet 자원 반납 } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설이 예제는 출력 결과만 보면
SelectData2와 같다.
하지만 입력값을 SQL에 넣는 방식이 다르다는 점이 핵심이다.
SelectData2는 문자열 결합으로 조건을 만들었다면,
SelectData3는 먼저select score from student where name = ?라는
SQL 틀을 준비해 둔다.
그리고
pstmt.setString(1, name)으로
첫 번째?자리에 실제 값을 넣은 뒤executeQuery()를 실행한다.
즉, 이 예제의 핵심 흐름은 이렇다.
SQL 틀 준비 → 입력값 받기 →
setString()으로 값 넣기 → 조회 실행 → 결과 유무 확인
그래서 이 예제는 같은 기능이어도
문자열 결합 대신?와setString()으로 값을 넣는 방식으로 바뀌었다는 점이 중요하다.
6-4. 점수 조건으로 여러 명 조회
사용 예제: SelectData4, SelectData5
개념이번에는 이름 한 명이 아니라,
특정 점수 이상인 학생들을 조회하는 구조다.
즉, 앞의 한 명 조회와 달리
이번에는 결과가 여러 행 나올 수 있다는 점이 중요하다.
그래서 여기서는 단순히
if(rs.next())로 끝나는 것이 아니라,
- 먼저 결과가 하나 이상 있는지 확인하고
- 첫 번째 행은 이미 잡은 상태에서
do-while로 첫 행부터 출력하는 구조가 자연스럽게 사용된다.
예제: SelectData4package jdbcexam; 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 SelectData4 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("select name from student where score >= ?"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 점수 조건 조회용 SQL 틀 준비 // 3. 입력 객체 준비 System.out.print("점수를 입력하세요 : "); int score = Integer.parseInt(scan.nextLine()); // 4. 기준 점수를 입력받음 pstmt.setInt(1, score); // 5. 첫 번째 ? 자리에 기준 점수 입력 ResultSet rs = pstmt.executeQuery(); // 6. 조건에 맞는 학생들 조회 if (rs.next()) { // 7. 결과가 하나 이상 있으면 첫 번째 행으로 이미 이동한 상태 do { // 8. 첫 번째 행부터 이름 출력 System.out.println(rs.getString("name")); } while (rs.next()); // 9. 다음 행이 있으면 이동해서 계속 반복 } else { // 10. 결과가 없으면 안내 문구 출력 System.out.println("해당 학생이 없습니다."); } rs.close(); // 11. ResultSet 자원 반납 } catch (SQLException se) { System.out.println(se.getMessage()); } } }
예제: SelectData5package jdbcexam; 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 SelectData5 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("select name, score from student where score>= ? order by score desc"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 점수 조건 + 정렬이 들어 있는 SQL 틀 준비 // 3. 입력 객체 준비 System.out.print("점수를 입력하세요 : "); int score = Integer.parseInt(scan.nextLine()); // 4. 기준 점수 입력 pstmt.setInt(1, score); // 5. 첫 번째 ? 자리에 기준 점수 입력 ResultSet rs = pstmt.executeQuery(); // 6. 조건에 맞는 학생들을 점수 높은 순으로 조회 if (rs.next()) { // 7. 결과가 있으면 첫 번째 행으로 이동한 상태 System.out.println("[ 점수가 " + score + " 이상인 학생 이름(점수가 높은 순) ]"); do { // 8. 이름과 점수를 함께 출력 System.out.println(rs.getString("name") + " : " + rs.getInt("score")); } while (rs.next()); // 9. 다음 행이 있으면 계속 반복 } else // 10. 결과가 없으면 안내 문구 출력 System.out.println("점수가 " + score + " 이상인 학생은 존재하지 않습니다."); System.out.println("수행 종료..."); rs.close(); // 11. ResultSet 자원 반납 } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설
SelectData4는 조건에 맞는 학생 이름들을 모아서 보여준다.
이때 결과가 여러 행일 수 있기 때문에if(rs.next())로 먼저 결과 존재 여부를 확인하고,
첫 번째 행은 이미 잡은 상태에서do-while로 첫 행부터 출력한다.
이 구조는 앞에서 본
ResultSet.next()처리 방식과 연결된다.
즉, 첫 번째next()가 이미 첫 행으로 이동했기 때문에
단순while(rs.next())만 쓰면 첫 행을 놓칠 수 있어do-while이 자연스럽게 사용된다.
SelectData5는SelectData4와 같은 여러 명 조회 구조에
order by score desc를 추가해서 점수가 높은 순으로 정렬해 보여 준다.
또
SelectData4가 이름만 출력했다면,
SelectData5는 이름과 점수를 함께 출력한다는 차이도 있다.
여기서 중요한 공통 흐름은 이렇다.
사용자 입력 받기 → 조건에 값 넣기 → 조회 실행 → 결과 유무 확인 → 있으면 반복 출력 → 없으면 안내 메시지 출력
7. student 테이블로 보는 입력, 수정, 삭제, 테이블 삭제
개념
student파트는 조회만 보는 것이 아니라
입력, 수정, 삭제까지 이어서 보면 훨씬 이해가 잘 된다.
조회가 데이터를 읽는 작업이었다면,
여기서는student테이블 안의 데이터를 직접 바꾸는 작업을 보게 된다.
즉, 여기서는
student테이블을 기준으로
생성 → 삽입 → 조회 → 수정 → 삭제 → 테이블 삭제 흐름을 하나로 묶어서 보는 것이 중요하다.
특히 이 장은 단순히 SQL 문장을 하나씩 보는 것이 아니라,
- 새 데이터를 넣을 수도 있고
- 기존 데이터를 바꿀 수도 있고
- 특정 데이터만 지울 수도 있고
- 마지막에는 테이블 자체를 없앨 수도 있다는 점
을 확인하는 구간이라고 이해하면 된다.
7-1. 반복 입력
사용 예제: InsertData2
개념
InsertData1이 미리 준비된 데이터를 한 번에 넣는 예제였다면,
InsertData2는 사용자가 직접 값을 입력해서 데이터를 계속 추가하는 예제다.
즉, 여기서부터는 정적인 샘플 데이터 삽입이 아니라
입력 기반 삽입을 보게 된다.
이 예제에서 핵심 흐름은 이렇다.
- 학생 이름과 점수를 입력받는다.
PreparedStatement의?자리에 값을 넣는다.executeUpdate()로 한 행을 삽입한다.- 계속 입력할지 물어보고 반복하거나 종료한다.
예제: InsertData2package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Scanner; public class InsertData2 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("insert into student values (?, ?)"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 학생 이름, 점수를 넣을 SQL 틀 준비 // 3. 입력 객체 준비 while (true) { // 4. 계속 입력할 수 있도록 무한 반복 시작 System.out.print("학생 이름을 입력하세요 : "); String name = scan.nextLine(); // 5. 첫 번째 ? 자리에 들어갈 학생 이름 입력 System.out.print("학생 점수를 입력하세요 : "); int score = Integer.parseInt(scan.nextLine()); // 6. 두 번째 ? 자리에 들어갈 학생 점수 입력 pstmt.setString(1, name); // 7. 첫 번째 ? 자리에 이름 설정 pstmt.setInt(2, score); // 8. 두 번째 ? 자리에 점수 설정 pstmt.executeUpdate(); // 9. 학생 한 명의 데이터를 student 테이블에 삽입 System.out.println("student 테이블에 데이터 삽입 완료"); System.out.print("계속 입력하겠습니까?(y/n)"); String aws = scan.nextLine(); // 10. 계속 입력할지 여부를 다시 입력받음 if (aws.equalsIgnoreCase("y")) { // 11. y 또는 Y면 다음 학생 입력으로 다시 반복 continue; } System.out.println("student 테이블 데이터 삽입 작업 종료"); break; // 12. y가 아니면 반복 종료 } } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설
InsertData2는 데이터를 한 번만 넣는 것이 아니라,
계속 입력할지 물어보면서 반복 삽입을 처리하는 예제다.
여기서
insert into student values (?, ?)의
첫 번째?는 학생 이름, 두 번째?는 학생 점수를 의미한다.
그래서
setString(1, name)으로 이름을 넣고,
setInt(2, score)로 점수를 넣은 뒤executeUpdate()를 실행하면
학생 한 명의 데이터가 한 행 추가된다.
즉, 이 예제의 핵심 흐름은 이렇다.
입력값 받기 →
?자리에 값 넣기 → 삽입 실행 → 계속 입력 여부 확인 → 반복 또는 종료
헷갈리기 쉬운 부분
executeUpdate()는 한 번 실행할 때 한 행을 추가하는 구조다.
여러 명을 넣는 것은while(true)반복문으로 여러 번 실행하기 때문이다.
또
student테이블에서name은 기본키이므로
이미 존재하는 이름을 다시 입력하면 오류가 날 수 있다.
7-2. 수정
사용 예제: UpdateData
개념수정은 새 데이터를 추가하는 것이 아니라,
이미 들어 있는 데이터를 찾아서 값을 바꾸는 작업이다.
이 예제에서는 학생 이름을 기준으로 대상을 찾고,
그 학생의 점수를 새 값으로 변경한다.
즉, 여기서 핵심은
- 누구를 바꿀지 찾는 조건
- 무엇으로 바꿀지 정하는 새 값
이 두 가지가 함께 들어간다는 점이다.
예제: UpdateDatapackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Scanner; public class UpdateData { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("update student set score = ? where name = ?"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 점수를 수정할 SQL 틀 준비 // 첫 번째 ?는 새 점수 // 두 번째 ?는 수정할 학생 이름 // 3. 입력 객체 준비 System.out.print("학생 이름을 입력하세요 : "); String name = scan.nextLine(); // 4. 수정할 대상 학생 이름 입력 System.out.print("학생 점수를 입력하세요 : "); int score = Integer.parseInt(scan.nextLine()); // 5. 새로 바꿀 점수 입력 pstmt.setInt(1, score); // 6. 첫 번째 ? 자리에 새 점수 설정 pstmt.setString(2, name); // 7. 두 번째 ? 자리에 수정 대상 이름 설정 int updateNum = pstmt.executeUpdate(); // 8. 수정 실행 후 실제로 바뀐 행 수를 받음 if (updateNum > 0) // 9. 조건에 맞는 학생이 있으면 수정 성공 System.out.println("student 테이블에서 " + updateNum + "행 변경 완료"); else // 10. 조건에 맞는 학생이 없으면 수정할 대상이 없다는 뜻 System.out.println("변경 실패!!"); } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설
UpdateData는 학생 이름을 기준으로 점수를 수정하는 예제다.
여기서
update student set score = ? where name = ?는
name에 해당하는 학생을 찾아score값을 새 값으로 바꾸겠다는 뜻이다.
즉,
score = ?는 바꿀 새 점수where name = ?는 수정할 대상 학생의 역할을 한다.
또
executeUpdate()가 돌려주는 값은 실제로 수정된 행 수다.
그래서updateNum > 0이면 수정 성공이고,
0이면 조건에 맞는 학생이 없어서 바뀐 행이 없다고 이해하면 된다.
헷갈리기 쉬운 부분
변경 실패!!는 꼭 SQL 문법 오류라는 뜻이 아니다.
입력한 이름에 해당하는 학생이 없어서 수정할 대상이 없을 수도 있다.
7-3. 한 명 삭제
사용 예제: DeleteData1
개념이번에는 학생 한 명을 이름으로 찾아서 삭제한다.
수정과 마찬가지로 이것도 조회가 아니라 변경 작업이다.
여기서는 이름이 조건이 되므로
삭제 결과는 보통 0행 또는 1행으로 이해하면 된다.
예제: DeleteData1package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class DeleteData1 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); Statement stmt = conn.createStatement(); Scanner scan = new Scanner(System.in);) { // 1. DB 연결, SQL 실행 객체, 입력 객체 준비 System.out.print("삭제할 학생 이름을 입력하세요 : "); String name = scan.nextLine(); // 2. 삭제할 대상 학생 이름 입력 int deleteNum = stmt.executeUpdate("delete from student where name = '" + name + "'"); // 3. 입력한 이름을 SQL 문자열에 직접 이어 붙여 삭제 실행 // 삭제된 행 수를 반환받음 if (deleteNum > 0) // 4. 조건에 맞는 학생이 있으면 삭제 성공 System.out.println("student 테이블에서 " + deleteNum + "행 삭제 완료"); else // 5. 조건에 맞는 학생이 없으면 삭제할 행이 없음 System.out.println("삭제 실패!!"); } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설이 예제는
Statement방식으로 삭제를 처리한다.
이름을 직접 이어 붙여서 삭제문을 만든 뒤executeUpdate()로 실행한다.
즉, 흐름은 이렇다.
삭제할 이름 입력 → SQL 문자열 완성 → 삭제 실행 → 삭제된 행 수로 성공 여부 확인
여기서 이름은 기본키이므로
삭제 결과는 보통 1행 또는 0행으로 생각하면 된다.
이 예제는
6-2에서 문자열 결합으로 조회문을 만들었던 방식과 비슷하게,
삭제문도 문자열을 직접 이어 붙여 만들 수 있다는 점을 보여 준다.
7-4. 조건에 맞는 여러 행 삭제
사용 예제: DeleteData2
개념이번에는 한 명 삭제가 아니라,
입력한 점수 범위에 해당하는 여러 행을 한 번에 삭제하는 구조다.
즉,
DeleteData1이 이름으로 한 명을 지우는 예제였다면,
DeleteData2는 조건 범위를 이용해 여러 명이 한 번에 지워질 수 있는 예제다.
그래서 이 예제에서는
executeUpdate()결과가
1이 아니라 2, 3처럼 여러 행일 수도 있다.
예제: DeleteData2package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Scanner; public class DeleteData2 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("delete from student where score between ? and ?"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 점수 범위 삭제용 SQL 틀 준비 // 첫 번째 ?는 최소 점수 // 두 번째 ?는 최대 점수 // 3. 입력 객체 준비 System.out.print("최소 점수를 입력하세요 : "); int min = Integer.parseInt(scan.nextLine()); // 4. 최소 점수 입력 System.out.print("최대 점수를 입력하세요 : "); int max = Integer.parseInt(scan.nextLine()); // 5. 최대 점수 입력 pstmt.setInt(1, min); // 6. 첫 번째 ? 자리에 최소 점수 설정 pstmt.setInt(2, max); // 7. 두 번째 ? 자리에 최대 점수 설정 int deleteNum = pstmt.executeUpdate(); // 8. 범위 조건에 맞는 학생들을 한 번에 삭제 // 실제 삭제된 행 수를 반환받음 if (deleteNum > 0) // 9. 조건에 맞는 학생이 하나 이상 있으면 삭제 성공 System.out.println("student 테이블에서 " + deleteNum + "행 삭제 완료"); else // 10. 조건에 맞는 학생이 없으면 삭제할 행이 없음 System.out.println("삭제 실패!!"); } catch (SQLException se) { System.out.println(se.getMessage()); } } }
코드 해설이 예제는 삭제도
PreparedStatement로 더 깔끔하게 처리할 수 있다는 점을 보여준다.
delete from student where score between ? and ?에서
- 첫 번째
?는 최소 점수- 두 번째
?는 최대 점수를 의미한다.
그래서 최소 점수와 최대 점수를 입력받아 그 범위에 들어가는 학생들을
한 번에 삭제하게 된다.
이 예제의 핵심은
한 명 삭제가 아니라 조건에 맞는 여러 행 삭제라는 점이다.
따라서deleteNum도 여러 행 수가 될 수 있다.
헷갈리기 쉬운 부분최소 점수와 최대 점수를 거꾸로 입력하면
의도한 범위 삭제가 되지 않을 수 있다.
즉, 범위 조건은 입력 순서도 함께 신경 써야 한다.
7-5. 마지막에 테이블 자체 삭제
사용 예제: DropTable
개념이번 코드 흐름에서는 마지막에
student테이블 자체를 제거하는 예제도 제공되어 있다.
즉, 한 행 삭제와는 다른 테이블 제거 단계다.
여기서 중요한 점은
delete와drop table이 완전히 다르다는 것이다.
delete는 테이블은 남겨 두고 행만 지운다.drop table은 테이블 구조 자체를 없앤다.
그래서 이 단계는 단순 데이터 정리가 아니라,
student테이블 자체를 더 이상 존재하지 않게 만드는 마무리 작업이라고 보면 된다.
예제: DropTablepackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class DropTable { public static void main(String[] args) { Connection conn = null; Statement stmt = null; try { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; conn = DriverManager.getConnection(url, user, passwd); // 1. DB 서버와 연결함 System.out.println("데이터베이스에 접속했습니다."); stmt = conn.createStatement(); // 2. SQL 실행 객체 생성 stmt.executeUpdate("drop table student"); // 3. student 테이블 자체를 제거함 System.out.println("student 테이블 제거"); } catch (SQLException se1) { // 4. 테이블 제거 중 발생한 오류 처리 System.out.println(se1); } finally { try { stmt.close(); // 5. Statement 자원 반납 conn.close(); // 6. Connection 자원 반납 } catch (SQLException se2) { System.out.println(se2.getMessage()); } } } }
코드 해설이 예제는
student테이블 작업을 전부 마친 뒤
테이블 자체를 제거하는 마무리 코드다.
역시 조회가 아니므로executeUpdate()를 사용한다.
여기서
drop table student는
데이터 몇 행만 없애는 것이 아니라,
student테이블의 구조 자체를 없애는 명령이다.
따라서 이 예제를 실행한 뒤에는
SelectData1,UpdateData,DeleteData1같은 예제를 바로 다시 실행할 수 없다.
다시 사용하려면CreateTable과InsertData1또는InsertData2부터 다시 해야 한다.
헷갈리기 쉬운 부분
delete는 테이블은 남고 데이터만 지워지지만,
drop table은 테이블 자체가 사라진다.
그래서 둘은 비슷해 보여도 전혀 다른 작업이다.
8. Statement와 PreparedStatement 비교 정리
개념여기서는
student예제에서 이미 본 내용을 기준으로
Statement와PreparedStatement를 비교해서 정리한다.
즉, 이 장의 핵심은
입력값을 SQL에 넣는 방식이 어떻게 달라지는지 비교하는 것이다.
앞에서 각각의 역할을 이미 보았다면,
여기서는 그 개념을 다시 길게 설명하기보다
같은 기능을 만들 때 코드가 어떻게 달라지는지를 비교해서 보는 것이 중요하다.
핵심 특징
Statement는 실행할SQL문장을 직접 완성해서 넘기는 방식이다.PreparedStatement는?가 들어 있는SQL틀을 먼저 만들고 값을 나중에 넣는 방식이다.- 두 방식 모두 조회면
executeQuery(), 변경이면executeUpdate()를 사용한다.- 사용자 입력이 자주 바뀌는 프로그램은
PreparedStatement가 더 자연스럽다.
사용 예제
SelectData2:StatementDeleteData1:StatementSelectData3:PreparedStatementInsertData2,UpdateData,DeleteData2,SelectData4,SelectData5:PreparedStatement확장 사용
정리
Statement는 실행할SQL문장을 먼저 완성해서 넘긴다.
그래서 사용자 입력이 들어가는 경우에는 값과SQL문장이 한 줄 안에 같이 섞여 보인다.
예를 들면
SelectData2,DeleteData1같은 예제에서는
입력한 이름을 문자열에 직접 이어 붙여서
where name = '입력값'형태의SQL문장을 만든 뒤 실행했다.
반면
PreparedStatement는
?를 포함한SQL틀을 먼저 준비해 둔다.
그리고
setString(),setInt()같은 메서드로
각?자리에 실제 값을 넣은 뒤 실행한다.
즉, 흐름이
SQL 틀 준비 → 값 넣기 → 실행
순서로 나뉘어 있다.
예를 들면
SelectData3에서는
select score from student where name = ?라는 틀을 먼저 만들고,
setString(1, name)으로 실제 이름 값을 넣은 뒤 조회를 실행했다.
그래서 두 방식을 비교하면 이렇게 정리할 수 있다.
Statement:SQL문장을 직접 완성해서 실행PreparedStatement:SQL틀과 입력값을 나눠서 처리
결국
PreparedStatement는
SQL구조와 입력값이 분리되어 보여서 코드 흐름을 따라가기가 더 쉽다.
그래서 입력값이 자주 바뀌는 조회, 입력, 수정, 삭제 예제들에서는 이 방식이 더 자연스럽게 사용된다.
9. visitor 예제로 보는 자원 반납과 예외 처리
개념
JDBC프로그램은 메모리 안의 일반 변수만 쓰는 것이 아니라
DB 연결 같은 외부 자원을 사용한다.
그래서 사용이 끝난 뒤 정리하지 않으면 연결이 계속 남아 있게 된다.
쉽게 말하면 문을 열고 들어갔으면,
일을 마친 뒤 문도 닫고 나와야 한다는 뜻이다.
이 때문에
JDBC에서는
“어떻게 조회하느냐” 못지않게
“어떻게 안전하게 닫느냐”도 중요하다.
특히 이 장은 같은
visitor조회 예제를 기준으로
자원 반납 방식이 어떻게 달라지는지 비교해서 보는 장이라고 이해하면 된다.
9-1. 가장 기본적인 수동 반납
사용 예제: VisitorList
개념가장 먼저 볼 구조는
필요한 객체를 만들고, 작업이 끝난 뒤 마지막에 직접close()를 호출하는 방식이다.
즉,
Connection생성Statement생성ResultSet조회- 마지막에 하나씩 직접 닫기
구조라고 보면 된다.
이 방식은 가장 기본형이라 흐름을 이해하기는 쉽지만,
예외가 발생하면 뒤쪽close()까지 도달하지 못할 수 있다는 한계도 있다.
예제: VisitorListpackage jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class VisitorList { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; Connection conn = DriverManager.getConnection(url, user, passwd); // 1. DB 서버와 연결함 Statement stmt = conn.createStatement(); // 2. SQL 실행 객체를 만듦 String sql = "SELECT id, name, writedate, memo FROM visitor"; // 3. visitor 테이블 조회 SQL 준비 ResultSet rs = stmt.executeQuery(sql); // 4. 조회를 실행하고 결과를 받음 while (rs.next()) { // 5. 결과를 한 행씩 이동하면서 출력 System.out.print(rs.getString("id") + "\t"); System.out.print(rs.getString("name") + "\t"); System.out.print(rs.getString("writedate") + "\t"); System.out.println(rs.getString("memo")); } rs.close(); // 6. ResultSet 자원 반납 stmt.close(); // 7. Statement 자원 반납 conn.close(); // 8. Connection 자원 반납 } }
코드 해설
VisitorList는 가장 단순한 조회 구조를 보여준다.
필요한 객체를 만들고, 마지막에 직접 하나씩 닫는 방식이다.
흐름 자체는 단순하다.
연결을 만들고,Statement로 조회를 실행하고,
ResultSet을 읽은 뒤 마지막에close()를 차례대로 호출한다.
다만 이 구조는 정상 흐름 끝까지 가야만 자원을 닫을 수 있다는 점이 중요하다.
즉, 중간에 예외가 발생하면 뒤쪽rs.close(),stmt.close(),conn.close()까지 도달하지 못할 수 있다.
또 자원은 보통 만든 순서의 반대로
ResultSet → Statement → Connection순서로 닫는다고 이해하면 된다.
9-2. try-catch-finally로 닫기
사용 예제: VisitorList2
개념수동 반납 방식의 가장 큰 약점은
예외가 발생하면 자원 닫는 코드까지 도달하지 못할 수 있다는 점이다.
이를 보완하기 위해
try-catch-finally구조를 사용하면,
중간에 문제가 생겨도finally에서 자원을 정리할 수 있다.
즉, 이 방식은
조회 실행과 자원 반납을 분리해서 더 안전하게 처리하는 구조라고 보면 된다.
예제: VisitorList2package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class VisitorList2 { public static void main(String[] args) { Connection conn = null; Statement stmt = null; ResultSet rs = null; // 1. finally에서도 사용해야 하므로 try 밖에서 미리 선언함 try { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; conn = DriverManager.getConnection(url,user,passwd); // 2. DB 연결 생성 stmt = conn.createStatement(); // 3. SQL 실행 객체 생성 String sql = "SELECT id, name, writedate, memo FROM visitor"; rs = stmt.executeQuery(sql); // 4. 조회 실행 후 결과 받기 while (rs.next()) { // 5. 결과를 한 행씩 출력 System.out.print(rs.getString("id") + "\t"); System.out.print(rs.getString("name") + "\t"); System.out.print(rs.getString("writedate") + "\t"); System.out.println(rs.getString("memo")); } } catch (Exception e) { // 6. 조회 중 발생한 예외 처리 System.err.println("오류 발생 : " + e); } finally { try { if (rs != null) rs.close(); // 7. ResultSet이 실제로 생성된 경우에만 닫음 if (stmt != null) stmt.close(); // 8. Statement가 생성된 경우에만 닫음 if (conn != null) conn.close(); // 9. Connection이 생성된 경우에만 닫음 } catch (Exception e) { e.printStackTrace(); } } } }
코드 해설
VisitorList2는 예외가 발생하더라도finally에서 자원을 닫는 구조를 보여준다.
그래서 단순 수동 반납보다 조금 더 안전하다.
VisitorList와 비교하면 차이는 분명하다.
앞의 예제는 정상 흐름 끝까지 가야 닫을 수 있었지만,
여기서는 중간에 예외가 나더라도finally가 실행되기 때문에 자원 정리가 가능하다.
또
if (rs != null),if (stmt != null),if (conn != null)같은 검사는
예외가 중간에 나면 어떤 객체는 아직 생성되지 않았을 수도 있기 때문에 필요하다.
즉, 아직 만들어지지 않은 객체에close()를 호출하지 않기 위해null여부를 먼저 확인하는 것이다.
9-3. try-with-resources로 더 간단하게 닫기
사용 예제: VisitorList3
개념
try-catch-finally는 안전하지만,
finally에서 자원을 하나씩 닫는 코드가 길어질 수 있다.
이를 더 간단하게 만든 구조가
try-with-resources다.
이 방식에서는
try (...)안에
Connection,Statement,ResultSet같은 자원을 함께 선언해 두면,
블록이 끝날 때 자동으로 닫힌다.
즉, 이 예제는
finally버전보다 자원 반납 코드를 더 짧고 깔끔하게 줄인 구조를 보여준다.
예제: VisitorList3package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class VisitorList3 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; String sql = "SELECT id, name, writedate, memo FROM visitor"; try (Connection conn = DriverManager.getConnection(url, user, passwd); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);) { // 1. DB 연결, SQL 실행 객체, 조회 결과 객체를 try(...) 안에서 함께 생성함 // 블록이 끝나면 자동으로 닫히는 구조임 while (rs.next()) { // 2. 결과를 한 행씩 출력 System.out.print(rs.getString("id") + "\t"); System.out.print(rs.getString("name") + "\t"); System.out.print(rs.getString("writedate") + "\t"); System.out.println(rs.getString("memo")); } } catch (Exception e) { // 3. 조회 중 발생한 오류 처리 System.err.println("오류 발생 : " + e); } } }
코드 해설
try (...)안에Connection,Statement,ResultSet을 함께 선언해 두었기 때문에
블록이 끝나면 자동으로 닫힌다.
그래서
VisitorList2처럼finally에서
null검사와close()를 하나씩 적지 않아도 된다.
즉, 이 예제는 자원 관리 방식은 더 간단해졌지만
조회 흐름 자체는 그대로 유지된다는 점이 중요하다.
결국 같은 작업을 하더라도 자원 반납 코드를 더 짧고 안전하게 쓸 수 있다는 뜻이다.
또 여기서는
Connection만 자동 반납되는 것이 아니라
Statement,ResultSet까지 함께 관리된다는 점도 함께 볼 수 있다.
9-4. 결과가 없을 때도 고려한 조회
사용 예제: VisitorList4
개념앞 예제들에서는 자원 반납 방식 차이에 집중했다면,
이번에는 여기에 더해 조회 결과가 없을 수도 있다는 상황까지 함께 본다.
즉, 이 예제는
- 자원 반납은
try-with-resources로 처리하고- 결과가 있으면 출력하고
- 결과가 없으면 안내 문구를 보여주는
구조를 함께 담고 있다.
예제: VisitorList4package jdbcexam; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class VisitorList4 { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; String sql = "SELECT id, name, DATE_FORMAT(writedate, '%Y년 %m월 %d일') writedate, memo FROM visitor"; // 1. 날짜를 보기 좋게 가공한 조회 SQL 준비 try (Connection conn = DriverManager.getConnection(url, user, passwd); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql);) { // 2. try-with-resources로 자원 자동 관리 if (rs.next()) { // 3. 결과가 하나 이상 있으면 true // 이 순간 첫 번째 행으로 이미 이동한 상태임 do { // 4. 첫 번째 행부터 결과 출력 System.out.print(rs.getString("id") + "\t"); System.out.print(rs.getString("name") + "\t"); System.out.print(rs.getString("writedate") + "\t"); System.out.println(rs.getString("memo")); } while (rs.next()); // 5. 다음 행이 있으면 계속 반복 } else { // 6. 결과가 전혀 없으면 안내 문구 출력 System.out.println("추출된 행이 없숑!!"); } } catch (Exception e) { // 7. 조회 중 발생한 오류 처리 System.err.println("오류 발생 : " + e); } } }
코드 해설
VisitorList4는 자원 반납 구조뿐 아니라
조회 결과가 없을 수도 있다는 상황까지 함께 처리한 예제다.
자원 관리는
try-with-resources로 처리하고,
결과 처리에서는if(rs.next()) + do-while구조를 사용한다.
이 구조는 앞에서 본
ResultSet.next()흐름과 연결된다.
if(rs.next())가 참이면 첫 번째 행으로 이미 이동한 상태이므로,
첫 행부터 출력하려면do-while이 자연스럽게 사용된다.
반대로 결과가 하나도 없으면
else로 들어가서
안내 문구를 출력하게 된다.
또
DATE_FORMAT(writedate, '%Y년 %m월 %d일')처럼
날짜를 보기 좋게 가공하는 작업을 SQL에서 함께 처리하고 있다는 점도 볼 수 있다.
즉, 이 예제는
자원 관리 + 결과 유무 처리 + 출력용 가공을 함께 보여 주는 마무리 단계라고 이해하면 된다.
10. 이미지 데이터 처리
개념여기까지가 기본 조회와 변경 흐름이었다면,
이 장은 그 흐름을 파일 데이터까지 확장해서 보는 예제다.
데이터베이스에는 문자와 숫자만 저장하는 것이 아니다.
이미지처럼 큰 파일 데이터도 저장할 수 있다.
이런 데이터는 값을 한 번에 다루기보다
파일 내용을 읽어 와서 흘려 보내듯 처리해야 한다.
그래서 자바에서는InputStream,OutputStream같은 스트림을 이용한다.
즉, 이 장의 핵심은
파일 내용을 DB에 넣는 흐름과
DB에 들어 있는 파일 내용을 다시 꺼내는 흐름을 함께 보는 것이다.
10-1. 이미지를 DB에 저장하기
사용 예제: InsertImage
개념이미지를 DB에 저장할 때는
보통 파일 이름과 파일 내용을 함께 다룬다.
이 예제에서는
imgtab테이블에
- 이미지 이름
- 이미지 파일 내용
을 함께 넣는다고 이해하면 된다.
즉,
- 이미지 이름은 문자열 데이터
- 이미지 내용은 바이너리 파일 데이터
라는 점이 중요하다.
예제: InsertImagepackage jdbcexam; import java.io.File; import java.io.FileInputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.util.Scanner; public class InsertImage { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("insert into imgtab values (?, ?)"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 이미지 이름, 이미지 내용을 넣을 SQL 틀 준비 // 3. 입력 객체 준비 System.out.print("삽입할 이미지 파일명을 입력하세요 : "); String name = scan.nextLine(); // 4. 사용자가 삽입할 이미지 파일 경로 또는 이름을 입력함 File imgFile = new File(name); // 5. 입력한 경로를 기준으로 File 객체 생성 if (imgFile.exists()) { // 6. 실제 파일이 존재할 때만 삽입 진행 FileInputStream fin = new FileInputStream(imgFile); // 7. 이미지 파일 내용을 읽어 올 입력 스트림 생성 pstmt.setString(1, imgFile.getName()); // 8. 첫 번째 ? 자리에 이미지 파일명 저장 pstmt.setBinaryStream(2, fin, (int) imgFile.length()); // 9. 두 번째 ? 자리에 이미지 파일 내용을 바이너리 스트림으로 저장 // 세 번째 인자는 파일 길이임 pstmt.executeUpdate(); // 10. imgtab 테이블에 이미지 정보 삽입 System.out.println("이미지 삽입 성공"); fin.close(); // 11. 파일 입력 스트림 닫기 } else { // 12. 파일이 없으면 삽입하지 않음 System.out.println("[오류] 이미지가 존재하지 않음"); } } catch (Exception e) { System.out.println(e.getMessage()); } } }
코드 해설
InsertImage는 파일 시스템에 있는 이미지를 DB 안으로 넣는 예제다.
여기서 중요한 흐름은 이렇다.
파일명 입력 → 파일 존재 여부 확인 → 파일 입력 스트림 생성 →
파일명은 문자열로 저장 → 파일 내용은 바이너리 스트림으로 저장 → 삽입 실행
pstmt.setString(1, imgFile.getName())은
이미지 이름을 문자열 값으로 넣는 부분이다.
반면
pstmt.setBinaryStream(2, fin, (int) imgFile.length())는
파일 내용을 그대로 DB에 넣는 핵심 부분이다.
즉, 이미지처럼 큰 파일 데이터는 문자열처럼 넣는 것이 아니라
입력 스트림을 통해 파일 내용을 흘려 보내는 방식으로 저장한다.
여기서 세 번째 인자인
(int) imgFile.length()는
저장할 파일 내용의 크기를 함께 알려 주는 값이라고 이해하면 된다.
헷갈리기 쉬운 부분파일이 존재하지 않으면 DB 삽입 자체를 진행할 수 없다.
그래서 먼저imgFile.exists()로 실제 파일이 있는지 확인한다.
또
Connection,PreparedStatement만 자원이 아니라
FileInputStream도 자원이기 때문에 사용이 끝나면 닫아야 한다.
10-2. DB에서 이미지를 다시 꺼내기
사용 예제: SelectImage
개념이번에는 반대로 DB에 저장된 이미지 내용을 다시 꺼내서
파일로 복원하는 흐름을 본다.
저장할 때 파일 이름과 파일 내용을 함께 넣어 두었기 때문에,
읽을 때는 이미지 이름으로 내용을 찾아서 다시 파일로 저장하게 된다.
즉, 이 예제는
- DB에서 이미지 내용을 읽고
- 파일 출력 스트림으로 다시 써서
- 실제 파일로 복원하는 구조
라고 보면 된다.
예제: SelectImagepackage jdbcexam; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.Scanner; public class SelectImage { public static void main(String[] args) { String url = "jdbc:mysql://localhost:3306/edudb?characterEncoding=UTF-8&serverTimezone=UTC"; String user = "jdbctest"; String passwd = "jdbctest"; try (Connection conn = DriverManager.getConnection(url, user, passwd); PreparedStatement pstmt = conn.prepareStatement("select imgcontent from imgtab where imgname = ?"); Scanner scan = new Scanner(System.in);) { // 1. DB 연결 생성 // 2. 이미지 이름으로 내용을 찾는 SQL 틀 준비 // 3. 입력 객체 준비 System.out.print("추출할 이미지명을 입력하세요 : "); String name = scan.nextLine(); // 4. DB에서 찾을 이미지 이름 입력 pstmt.setString(1, name); // 5. 첫 번째 ? 자리에 이미지 이름 설정 ResultSet rs = pstmt.executeQuery(); // 6. 조건에 맞는 이미지 내용 조회 if (rs.next()) { // 7. 이미지가 있으면 첫 번째 행으로 이동 InputStream is = rs.getBinaryStream("imgcontent"); // 8. DB에 저장된 바이너리 내용을 입력 스트림으로 받음 File imgFile = new File("copy_" + name); // 9. 복원한 파일을 저장할 새 파일 객체 생성 FileOutputStream fos = new FileOutputStream(imgFile); // 10. 실제 파일로 쓸 출력 스트림 생성 byte[] b = new byte[2048]; // 11. 파일 내용을 조금씩 옮기기 위한 버퍼 준비 int n; while ((n = is.read(b)) > 0) { // 12. DB에서 읽어 온 내용을 버퍼에 담고 fos.write(b, 0, n); // 13. 그만큼 다시 파일로 기록함 } fos.close(); // 14. 파일 출력 스트림 닫기 is.close(); // 15. 바이너리 입력 스트림 닫기 System.out.println("이미지 추출 성공"); } else { // 16. 해당 이름의 이미지가 없으면 안내 문구 출력 System.out.println("이미지가 존재하지 않습니다."); } rs.close(); // 17. ResultSet 자원 반납 } catch (Exception e) { System.out.println(e.getMessage()); } } }
코드 해설
SelectImage는 DB에 저장된 이미지 내용을 다시 파일로 복원하는 예제다.
흐름을 보면 먼저 이미지 이름을 입력받고,
그 이름으로imgtab에서 이미지 내용을 찾는다.
결과가 있으면
rs.getBinaryStream("imgcontent")로
DB 안의 바이너리 내용을 입력 스트림 형태로 읽어 온다.
그다음
FileOutputStream을 이용해
새 파일copy_이름에 내용을 다시 써 넣는다.
여기서copy_를 붙인 것은 원본과 구분되는 복사본 파일을 만들기 위한 것이라고 보면 된다.
byte[] b = new byte[2048]는
파일 내용을 한 번에 전부 처리하지 않고,
버퍼를 이용해 조금씩 읽고 조금씩 쓰기 위한 준비다.
그래서
while ((n = is.read(b)) > 0)반복문은
DB에서 내용을 조금씩 읽고, 읽은 만큼 다시 파일로 써 주는 구조다.
즉, 이 예제의 핵심은 DB 안의 바이너리 데이터를 다시 파일 형태로 복원하는 과정이다.
헷갈리기 쉬운 부분이 예제는 단순히 조회 결과를 화면에 출력하는 것이 아니라,
조회한 바이너리 내용을 다시 파일로 저장하는 작업까지 포함한다.
또
InputStream,FileOutputStream도 자원이므로
작업이 끝난 뒤 닫아야 한다.
정리이 두 예제는 서로 반대 방향의 흐름을 보여준다.
- 저장 : 파일 → DB
- 읽기 : DB → 파일
즉,
InsertImage는 파일 내용을 DB 안으로 넣는 예제이고,
SelectImage는 DB 안의 파일 내용을 다시 실제 파일로 복원하는 예제다.