[아이티센 부트캠프] 15일차 JDBC

이언덕·2026년 4월 3일

아이티센 부트캠프

목록 보기
36/115
post-thumbnail

JDBC

1. 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()로 닫아야 한다.



예제별 역할

  • DBConnect
  • SelectEmp
  • SelectData1~5
  • VisitorList~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()로 닫아야 한다.



예제별 역할

  • SelectEmp
  • CreateTable
  • InsertData1
  • DeleteData1
  • DropTable



부분 예제

// 연결을 이용해서 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을 다시 읽어야 한다.



예제별 역할

  • SelectEmp
  • SelectWhereEmp
  • SelectPatternEmp
  • SelectPatternEmp2
  • SelectSalEmp
  • SelectGroupEmp
  • SelectSubqueryEmp
  • SelectJoinEmp
  • SelectData1~5
  • VisitorList~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에서 호출한다.
  • 데이터 변경에서는 보통 영향받은 행 수를 반환한다.
  • 성공 여부를 확인하는 기준으로 자주 사용한다.



예제별 역할

  • CreateTable
  • InsertData1
  • InsertData2
  • UpdateData
  • DeleteData1
  • DeleteData2
  • DropTable
  • InsertImage



부분 예제

// 테이블 생성, 데이터 삽입, 수정, 삭제 같은 변경 작업에 사용한다.
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()로 닫아야 한다.



예제별 역할

  • SelectEmp
  • SelectPatternEmp2
  • SelectData1~5
  • VisitorList~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()로 닫아야 한다.



예제별 역할

  • SelectData3
  • SelectData4
  • SelectData5
  • InsertData2
  • UpdateData
  • DeleteData2
  • InsertImage
  • SelectImage



부분 예제

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에 가지고 있는 것만으로는 부족하다는 점이다.
지금 실행할 자바 프로젝트가 그 드라이버를 실제로 사용할 수 있는 상태여야 한다.



설정 보는 순서

  1. 프로젝트를 우클릭하고 Properties로 들어간다.
  2. Java Build Path를 연다.
  3. 외부 JDBC 드라이버 jar가 아직 추가되지 않은 상태인지 확인한다.
  4. Add External JARs...를 눌러 mysql-connector-java-8.0.17.jar를 추가한다.
  5. 추가 후 Classpath 아래에 해당 jar가 보이는지 확인한다.
  6. 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 코드로 실제 연결 확인하기

코드 보는 순서

  1. URL, 사용자명, 비밀번호를 준비하는 부분을 본다.
  2. DriverManager.getConnection(...)으로 연결을 얻는 부분을 본다.
  3. DatabaseMetaData로 DB 서버 정보를 꺼내는 부분을 본다.
  4. SQLException 처리와 finally에서의 자원 정리를 함께 본다.



예제: DBConnect

package 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



예제: SelectEmp

package 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 쪽에서 미리 보기 좋게 가공할 수도 있다는 점도 함께 보여 준다.



코드 보는 순서

  1. DB 접속 정보를 준비한다.
  2. Connection을 만든다.
  3. Statement를 만든다.
  4. executeQuery()로 조회를 실행한다.
  5. ResultSet을 rs.next()로 한 줄씩 이동하면서 필요한 열 값을 읽는다.
  6. 출력 형식에 맞게 보여 준다.
  7. 사용이 끝난 자원을 닫는다.



4-2. 조건을 붙여서 조회하기

사용 예제: SelectWhereEmp, SelectPatternEmp



개념

조회는 모든 행을 다 가져오는 것만 있는 게 아니다.
보통은 필요한 데이터만 골라 가져온다.


예를 들어 이런 경우다.

  • 커미션이 있는 직원만 보고 싶다.
  • 이름에 특정 문자가 들어간 직원만 찾고 싶다.

이럴 때 WHERE, LIKE, IS NOT NULL 같은 조건이 사용된다.
즉, SELECT 문도 조건을 어떻게 주느냐에 따라 가져오는 범위가 달라진다는 뜻이다.



예제: SelectWhereEmp

package 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. 사용한 자원을 순서대로 닫음
	}
}



예제: SelectPatternEmp

package 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()를 어떻게 쓰느냐에 따라 첫 번째 행을 놓치는 실수가 생길 수 있다.



예제: SelectPatternEmp2

package 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



개념

여기까지가 조회의 기본 구조였다면,
이제부터는 그 구조 위에서 조회를 조금 더 확장해서 사용하는 방법을 보는 단계다.


조회가 조금만 복잡해지면
단순히 “가져온다”에서 끝나지 않고 어떻게 가져올지까지 중요해진다.


정렬은 보기 좋은 순서로 바꾸고 싶을 때 사용한다.
그룹 조회는 개별 행이 아니라 요약 결과가 필요할 때 사용한다.
서브쿼리는 조건을 먼저 계산해야 할 때 사용한다.
조인은 다른 테이블의 정보와 함께 봐야 할 때 사용한다.



예제: SelectSalEmp

package 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. 자원 반납
	}
}



예제: SelectGroupEmp

package 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. 자원 반납
	}
}



예제: SelectSubqueryEmp

package 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. 사용한 자원을 닫음
	}
}



예제: SelectJoinEmp

package 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을 기본키로 사용한다는 규칙

을 한 번에 정의한다.



예제: CreateTable

package 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은 그 틀 안에 실제로 조회할 수 있는 행 데이터를 채워 넣는 단계다.


여기서는 사용자가 값을 입력하는 것이 아니라,
미리 정해 둔 학생 이름과 점수를 하나씩 넣어서
뒤의 조회 예제를 바로 실행할 수 있게 준비한다.



예제: InsertData1

package 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



예제: SelectData1

package 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



개념

이번에는 전체 조회가 아니라,
사용자가 입력한 이름으로 한 학생만 찾는 구조다.


여기서 핵심 흐름은 이렇다.

  • 사용자에게 이름을 입력받는다.
  • 입력한 이름을 조회 조건으로 사용한다.
  • 결과가 있으면 점수를 출력한다.
  • 결과가 없으면 데이터가 없다고 안내한다.

즉, 이 예제는 입력 → 조건 조회 → 결과 유무 판단 흐름을 보여 준다.



예제: SelectData2

package 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 방식의 차이를 비교해서 보는 예제다.



예제: SelectData3

package 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로 첫 행부터 출력하는 구조

가 자연스럽게 사용된다.



예제: SelectData4

package 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());
		}
	}
}



예제: SelectData5

package 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()로 한 행을 삽입한다.
  • 계속 입력할지 물어보고 반복하거나 종료한다.



예제: InsertData2

package 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



개념

수정은 새 데이터를 추가하는 것이 아니라,
이미 들어 있는 데이터를 찾아서 값을 바꾸는 작업이다.


이 예제에서는 학생 이름을 기준으로 대상을 찾고,
그 학생의 점수를 새 값으로 변경한다.


즉, 여기서 핵심은

  • 누구를 바꿀지 찾는 조건
  • 무엇으로 바꿀지 정하는 새 값

이 두 가지가 함께 들어간다는 점이다.



예제: UpdateData

package 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행으로 이해하면 된다.



예제: DeleteData1

package 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처럼 여러 행일 수도 있다.



예제: DeleteData2

package 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 테이블 자체를 더 이상 존재하지 않게 만드는 마무리 작업이라고 보면 된다.



예제: DropTable

package 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 : Statement
  • DeleteData1 : Statement
  • SelectData3 : PreparedStatement
  • InsertData2, 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()까지 도달하지 못할 수 있다는 한계도 있다.



예제: VisitorList

package 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에서 자원을 정리할 수 있다.


즉, 이 방식은
조회 실행과 자원 반납을 분리해서 더 안전하게 처리하는 구조라고 보면 된다.



예제: VisitorList2

package 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 버전보다 자원 반납 코드를 더 짧고 깔끔하게 줄인 구조를 보여준다.



예제: VisitorList3

package 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로 처리하고
  • 결과가 있으면 출력하고
  • 결과가 없으면 안내 문구를 보여주는

구조를 함께 담고 있다.



예제: VisitorList4

package 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 테이블에

  • 이미지 이름
  • 이미지 파일 내용

을 함께 넣는다고 이해하면 된다.


즉,

  • 이미지 이름은 문자열 데이터
  • 이미지 내용은 바이너리 파일 데이터

라는 점이 중요하다.



예제: InsertImage

package 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에서 이미지 내용을 읽고
  • 파일 출력 스트림으로 다시 써서
  • 실제 파일로 복원하는 구조

라고 보면 된다.



예제: SelectImage

package 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 안의 파일 내용을 다시 실제 파일로 복원하는 예제다.

0개의 댓글