본 시리즈에서는 데이터베이스의 개념을 정리하고 필요시 실습을 진행합니다.
Ubuntu-24.04
)10.11.8-MariaDB
)2024.2.2
)correto-1.8
)Ultimate Edition
)JDBC (Java Database Connectivity)는 Java 애플리케이션
이 데이터베이스
와 상호작용할 수 있도록 지원하는 표준 API입니다.
데이터베이스
에 SQL 문장
을 전달하고, 그 결과를 Java 애플리케이션
에서 처리할 수 있도록 설계되었습니다.JDBC
를 통해 Java 프로그램
은 다양한 관계형 데이터베이스
와 연결할 수 있습니다.SQL 문장
을 데이터베이스
에 전달하고, 결과를 받아 처리합니다.데이터베이스
로부터 데이터를 조회하거나, 수정/삭제와 같은 작업을 수행할 수 있습니다.데이터베이스 벤더
에 상관없이 동일한 API를 사용하여 데이터베이스와 상호작용할 수 있습니다.DriverManager
DriverManager.getConnection(url, user, password)
.connector
와 유사합니다.Connection
SQL 쿼리
를 실행하기 위해 필요한 객체를 생성.connection.createStatement()
Statement
Statement
: 정적인 SQL 실행 (해킹 위험으로 거의 쓰이지 않음)PreparedStatement
: 파라미터화된 SQL 실행 (주로 쓰이는 형태)CallableStatement
: DB에 저장된 Procedure를 호출cursor
와 유사합니다.ResultSet
JDBC 드라이버 로드
JDBC 드라이버
를 로드하여 DriverManager
에 등록합니다.데이터베이스 연결
DriverManager.getConnection()
메서드를 사용해 데이터베이스와 연결합니다.SQL 실행
Statement
또는 PreparedStatement
객체를 통해 SQL 문장을 실행합니다.결과 처리
SELECT 쿼리
의 경우 ResultSet
객체를 사용하여 결과를 처리합니다.INSERT, UPDATE, DELETE
의 경우 실행 결과로 영향을 받은 행 수를 반환합니다.연결 종료
Connection, Statement, ResultSet
)를 명시적으로 종료합니다.try-with-resources
로 자동으로 닫히도록 할 수도 있습니다.DB 벤더
별로 다른 API를 사용할 필요 없이 JDBC
를 통해 통합된 방법으로 접근 가능.MySQL, MariaDB, PostgreSQL, Oracle
등 다양한 관계형 데이터베이스와 호환.JDBC를 통해 Java 애플리케이션
과 MariaDB
를 연결하기 위해 필요한 설정을 단계별로 다뤄보겠습니다.
MariaDB Connector는 MariaDB
와 Java 애플리케이션
간의 통신을 가능하게 하는 JDBC 드라이버입니다.
MariaDB Connector/J 다운로드 페이지로 이동합니다.
.jar
파일을 다운로드합니다.편의상 진행하는 Project의 디렉토리에 위치하도록 파일을 옮깁니다.
lib
이라는 디렉토리를 만들어서 넣어주었습니다.IntelliJ에서 프로젝트를 열고 File > Project Structure
로 이동합니다.
Modules > Dependencies
탭을 선택한 뒤, + > JARs or Directories
를 클릭합니다.
다운로드한 MariaDB Connector .jar
파일을 추가합니다.
프로젝트가 새로 빌드되며, MariaDB 드라이버가 프로젝트에 포함됩니다.
Maven을 사용하는 경우, 의존성을 추가하면 다운로드 및 설정이 자동으로 완료됩니다.
pom.xml
파일 수정
프로젝트의 pom.xml 파일에 아래 의존성을 추가합니다:
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.2</version>
</dependency>
Maven 프로젝트 동기화
IntelliJ IDEA
상단에서 "Reload Maven Project" 아이콘을 클릭하여 의존성을 다운로드합니다.
Gradle을 사용하는 경우, MariaDB Connector 의존성을 build.gradle
파일에 추가합니다.
build.gradle
파일 수정
dependencies {
implementation 'org.mariadb.jdbc:mariadb-java-client:3.1.2'
}
프로젝트 동기화
IntelliJ IDEA
상단의 "Sync Now"를 클릭하여 Gradle 의존성을 다운로드합니다.
참고 (Maria DB 데이터베이스 생성)
- 서버 컴퓨터에서 MySQL/MariaDB의
root
유저로 접속 (이 내용까진 자세히 다루진 않습니다)- DB & User 생성 및 권한 부여
-- 새 데이터베이스 생성 CREATE DATABASE blog_jdbc; -- 새 사용자 생성 및 권한 부여 CREATE USER 'jdbc_user'@'%' IDENTIFIED BY 'your_password'; GRANT ALL PRIVILEGES ON blog_jdbc.* TO 'jdbc_user'@'%'; FLUSH PRIVILEGES;
MariaDB
와 Java 애플리케이션
을 연결했다고 가정하고, SQL
을 실행하기 위해 사용되는 주요 JDBC 객체와 그 동작 방식을 다뤄보겠습니다.
JDBC를 통해 MariaDB
에 연결하기 위해서는 데이터베이스 URL(String)이 필요합니다.
URL
은 연결 정보를 포함하며 아래와 같은 형식을 따릅니다:
jdbc:mariadb://<host>:<port>/<database>?<options>
jdbc:mariadb://
: MariaDB JDBC 드라이버를 사용한다는 의미.<host>
: 데이터베이스 서버의 IP 주소 또는 호스트 이름. (예: localhost
, 127.0.0.1
)<port>
: MariaDB가 사용하는 포트 번호(기본값: 3306
).<database>
: 연결하려는 데이터베이스 이름.<options>
: 추가적인 옵션(예: 인증
이나 SSL
설정).String url = "jdbc:mariadb://localhost:3306/blog_jdbc?useSSL=false";
String user = "jdbc_user";
String password = "your_password";
JDBC 드라이버를 로드하여 DriverManager
가 MariaDB
와 연결할 수 있도록 준비합니다.
Class.forName()
메서드를 사용하여 MariaDB 드라이버 클래스를 로드합니다.Class.forName("org.mariadb.jdbc.Driver");
META-INF/services/java.sql.Driver
에 정의된 정보를 통해 자동으로 로드됩니다. try {
Class.forName("org.mariadb.jdbc.Driver");
System.out.println("MariaDB JDBC Driver 로드 성공!");
} catch (ClassNotFoundException e) {
System.out.println("JDBC Driver 로드 실패: " + e.getMessage());
}
Connection
객체는 데이터베이스와의 연결을 나타냅니다.
DriverManager.getConnection()
메서드를 호출하여 Connection
객체를 생성합니다.
URL, 사용자명, 비밀번호
를 인자로 전달합니다.String url = "jdbc:mariadb://localhost:3306/blog_jdbc?useSSL=false";
String user = "jdbc_user";
String password = "your_password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("데이터베이스 연결 성공!");
} catch (SQLException e) {
e.printStackTrace();
}
createStatement()
Statement
객체를 생성.Statement stmt = conn.createStatement();
prepareStatement()
PreparedStatement
객체를 생성. PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users VALUES (?, ?)");
prepareCall()
CallableStatement
객체를 생성. CallableStatement cstmt = conn.prepareCall("{CALL procedure_name(?)}");
close()
try-with-resources
를 사용하여 자동으로 닫음.JDBC에서는 SQL 문장을 실행하기 위해 Statement
, PreparedStatement
, CallableStatement
세 가지 형태의 객체를 제공합니다.
특징
사용법
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement()) {
// SELECT 문 실행
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
}
// INSERT 문 실행
int rowsInserted = stmt.executeUpdate("INSERT INTO users (name, email) VALUES ('John', 'john@example.com')");
System.out.println(rowsInserted + " rows inserted.");
} catch (SQLException e) {
e.printStackTrace();
}
Statement
가 잘 쓰이지 않는 이유
- SQL Injection 취약
- 동적 쿼리에서 사용자 입력값을 직접 포함하면
SQL Injection 공격
에 노출됩니다.String userInput = "john@example.com'; DROP TABLE users; --"; String query = "SELECT * FROM users WHERE email = '" + userInput + "'"; // 의도치 않은 SQL 실행 가능
- 성능 저하
- 매번 쿼리를 새로 컴파일하기 때문에 동일한 SQL 문장을 반복 실행할 경우 성능이 떨어집니다.
특징
SQL Injection
방지.사용법
String query = "INSERT INTO users (name, email) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(query)) {
// 파라미터 설정
pstmt.setString(1, "Jane");
pstmt.setString(2, "jane@example.com");
// SQL 실행
int rowsInserted = pstmt.executeUpdate();
System.out.println(rowsInserted + " rows inserted.");
} catch (SQLException e) {
e.printStackTrace();
}
특징
사용법
try (Connection conn = DriverManager.getConnection(url, user, password);
CallableStatement cstmt = conn.prepareCall("{CALL insert_user(?, ?)}")) {
// 입력 매개변수 설정
cstmt.setString(1, "Alice");
cstmt.setString(2, "alice@example.com");
// 저장 프로시저 실행
cstmt.execute();
System.out.println("Stored procedure executed successfully.");
} catch (SQLException e) {
e.printStackTrace();
}
CallableStatement 주요 메서드
setXxx(int parameterIndex, Xxx value)
: 입력 매개변수 설정.registerOutParameter(int parameterIndex, int sqlType)
: 출력 매개변수 등록.객체 유형 | 주요 용도 | 특징 | 장점 및 단점 |
---|---|---|---|
Statement | 정적 SQL | 매번 SQL을 문자열로 전달 | SQL Injection 취약, 성능 저하 |
PreparedStatement | 파라미터화된 SQL | 파라미터 지원, 컴파일된 쿼리 구조 재사용 | 안전하고 성능 우수 |
CallableStatement | 저장 프로시저 호출 | IN/OUT/INOUT 매개변수 지원 | 복잡한 로직을 DB 내에서 처리 가능 |
메서드 | 용도 | 반환값 | 설명 |
---|---|---|---|
executeQuery() | SELECT 문 실행 | ResultSet | 조회 결과를 행(Row) 단위로 처리 |
executeUpdate() | INSERT, UPDATE, DELETE 실행 | 영향을 받은 행(row) 개수 반환 | DML(데이터 수정) 작업에서 사용 |
execute() | 모든 SQL 문 실행 가능 | true 또는 false | SELECT일 경우 true, 아니면 false |
SELECT
쿼리 실행 시 결과를 ResultSet
객체로 반환합니다.
ResultSet rs = stmt.executeQuery("SELECT * FROM users");
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name"));
}
INSERT, UPDATE, DELETE
쿼리 실행 시 영향을 받은 행(row) 개수
를 반환합니다.
int rowsAffected = stmt.executeUpdate("DELETE FROM users WHERE id = 1");
System.out.println(rowsAffected + " rows deleted.");
SELECT
쿼리인지 여부
에 따라 반환값이 달라집니다.
boolean hasResultSet = stmt.execute("DROP TABLE test_table");
if (!hasResultSet) {
System.out.println("테이블 삭제 성공.");
}
이제 JDBC를 사용하여 실제로 CREATE, READ, UPDATE, DELETE
(CRUD) 작업을 구현해 보겠습니다.
velog_jdbc_kj
이며, IP 주소
는 보안을 위해 예제 코드에서 가리고 설명하겠습니다.jdbc:mariadb://<DB_IP>:3306/velog_jdbc_kj
<DB_IP>
는 보안상 표시하지 않았습니다. "jdbc:mariadb://127.0.0.1:3306/velog_jdbc_kj"
(localhost)MariaDB
테이블 생성: users
실습을 위한 테이블
을 준비합니다.
USE velog_jdbc_kj; # 필요시 사용
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE
);
Java
에서 데이터를 효율적으로 다루기 위해 User 클래스
를 정의합니다.
public class User {
private Integer id; // id는 null 가능 (삽입 시 DB에서 자동 생성)
private String userName;
private String email;
public User(Integer id, String userName, String email) {
this.id = id;
this.userName = userName;
this.email = email;
}
public User(String userName, String email) {
this.userName = userName;
this.email = email;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{id=" + id +
", name='" + userName + '\'' +
", email='" + email + '\'' + '}';
}
}
INSERT
문을 사용해 데이터베이스에 새로운 사용자 정보를 삽입합니다.
id
는 AUTO_INCREMENT
로 자동 생성됩니다.User
객체를 생성하여 데이터를 전달합니다.INSERT INTO users (user_name, email) VALUES (?, ?)
?
는 SQL 파라미터
로, PreparedStatement
를 사용해 동적으로 값을 설정합니다.import java.sql.*;
public class CreateUser {
public static void main(String[] args) {
String url = "jdbc:mariadb://<DB_IP>:3306/velog_jdbc_kj";
String user = "jdbc_user";
String password = "your_password";
String query = "INSERT INTO users (user_name, email) VALUES (?, ?)";
User newUser = new User("John Doe", "john.doe@example.com");
// 연결 (try-with-resource)
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement cursor = conn.prepareStatement(query)) {
// SQL 파라미터 설정
cursor.setString(1, newUser.getUserName());
cursor.setString(2, newUser.getEmail());
// SQL 실행
int rowsInserted = cursor.executeUpdate();
System.out.println(rowsInserted + " row(s) inserted.");
} catch (SQLException e) {
e.printStackTrace(System.err);
}
}
}
- 데이터베이스 연결
DriverManager.getConnection()
메서드를 사용해 데이터베이스와 연결합니다.- 연결 정보는
url, user, password
로 지정합니다.- PreparedStatement 생성
PreparedStatement
는 SQL 문을 사전에 컴파일하고, 동적으로 값을 설정할 수 있는 객체입니다.- 이 예제에서는
query
문자열을 기반으로PreparedStatement
객체를 생성합니다.- SQL 파라미터 설정
cursor.setString(1, newUser.getUserName())
: SQL의 첫 번째?
에 사용자 이름 설정.cursor.setString(2, newUser.getEmail())
: SQL의 두 번째?
에 이메일 주소 설정.- 참고로, SQL에선
0
부터 숫자를 세지 않고1
부터 셉니다.- SQL 실행
executeUpdate()
: INSERT, UPDATE, DELETE 문을 실행하고 영향을 받은 행의 개수를 반환합니다.- 반환된
rowsInserted
는 삽입된 행의 개수입니다.- 예외 처리
SQLException
발생 시 에러 메시지를 출력합니다.try-with-resource
를 사용하여Connection
및PreparedStatement
객체를 자동으로 닫습니다.
1 row(s) inserted.
SELECT * FROM users;
SELECT
문을 사용해 데이터베이스에서 사용자 정보를 조회합니다.
SELECT * FROM users
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class ReadUsers {
public static void main(String[] args) {
String url = "jdbc:mariadb://<DB_IP>:3306/velog_jdbc_kj";
String user = "jdbc_user";
String password = "your_password";
String query = "SELECT * FROM users";
List<User> users = new ArrayList<>();
// 연결 (try-with-resource)
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
// ResultSet을 User 객체로 변환하여 리스트에 추가
while (rs.next()) {
User userObj = new User(
rs.getInt("id"),
rs.getString("user_name"),
rs.getString("email")
);
users.add(userObj);
}
// 조회된 데이터 출력
users.forEach(System.out::println);
} catch (SQLException e) {
e.printStackTrace(System.err);
}
}
}
- 데이터베이스 연결
DriverManager.getConnection()
메서드를 사용하여 데이터베이스에 연결합니다.- Statement 생성
Statement
는 정적 SQL 쿼리를 실행하는 객체로, 이 예제에서는SELECT
문에 사용됩니다.- SQL 실행 및 ResultSet 처리
stmt.executeQuery(query)
는SELECT
문을 실행하고 결과 집합(ResultSet)을 반환합니다.ResultSet
의 각 행(row)을 읽어User
객체로 변환하고, 이를 리스트(List<User>
)에 추가합니다.- 데이터 출력
- 리스트의 모든
User
객체를 출력합니다.
User{id=1, name='John Doe', email='john.doe@example.com'}
User{id=2, name='Cheong Kyung Jae', email='dankool@naver.com'}
UPDATE
문을 사용해 데이터베이스에서 특정 사용자의 정보를 수정합니다.
id
값을 조건으로 사용해 특정 사용자를 선택하여 수정합니다.PRIMARY KEY
를 이용하지만 필요에 따라 조건을 추가할 수 있습니다.UPDATE users SET email = ? WHERE id = ?
import java.sql.*;
public class UpdateUser {
public static void main(String[] args) {
String url = "jdbc:mariadb://<DB_IP>:3306/velog_jdbc_kj";
String user = "jdbc_user";
String password = "your_password";
String query = "UPDATE users SET email = ? WHERE id = ?";
User updatedUser = new User(1, "John Doe", "john.new@example.com");
// 연결 (try-with-resource)
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement cursor = conn.prepareStatement(query)) {
// SQL 파라미터 설정
cursor.setString(1, updatedUser.getEmail());
cursor.setInt(2, updatedUser.getId());
// SQL 실행
int rowsUpdated = cursor.executeUpdate();
System.out.println(rowsUpdated + " row(s) updated.");
} catch (SQLException e) {
e.printStackTrace(System.err);
}
}
}
- 데이터베이스 연결
DriverManager.getConnection()
메서드로 데이터베이스와 연결합니다.- PreparedStatement 생성
PreparedStatement
를 사용하여 SQL 파라미터를 동적으로 설정합니다.- SQL 파라미터 설정
cursor.setString(1, updatedUser.getEmail())
: SQL의 첫 번째?
에 새로운 이메일 설정.cursor.setInt(2, updatedUser.getId())
: SQL의 두 번째?
에 수정할 대상의id
설정.- SQL 실행
executeUpdate()
는 수정된 행의 개수를 반환합니다.
1 row(s) updated.
SELECT * FROM users;
DELETE
문을 사용해 데이터베이스에서 특정 사용자의 정보를 삭제합니다.
id
값을 조건으로 사용해 특정 사용자를 삭제합니다.DELETE FROM users WHERE id = ?
import java.sql.*;
public class DeleteUser {
public static void main(String[] args) {
String url = "jdbc:mariadb://<DB_IP>:3306/velog_jdbc_kj";
String user = "jdbc_user";
String password = "your_password";
String query = "DELETE FROM users WHERE id = ?";
User userToDelete = new User(1, null, null);
// 연결 (try-with-resource)
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement cursor = conn.prepareStatement(query)) {
// SQL 파라미터 설정
cursor.setInt(1, userToDelete.getId());
// SQL 실행
int rowsDeleted = cursor.executeUpdate();
System.out.println(rowsDeleted + " row(s) deleted.");
} catch (SQLException e) {
e.printStackTrace(System.err);
}
}
}
- 데이터베이스 연결
DriverManager.getConnection()
으로 데이터베이스에 연결합니다.- PreparedStatement 생성
PreparedStatement
를 사용하여 SQL 파라미터를 설정합니다.- SQL 파라미터 설정
cursor.setInt(1, userToDelete.getId())
: 삭제할 대상의id
를 SQL의 첫 번째?
에 설정합니다.- SQL 실행
executeUpdate()
는 삭제된 행의 개수를 반환합니다.
1 row(s) updated.
SELECT * FROM users;
이번 포스팅에서는 JDBC(Java Database Connectivity)를 사용하여 Java 애플리케이션
과 MariaDB 데이터베이스
를 연결하고, 이를 통해 CRUD 작업을 수행하는 방법을 다뤘습니다.
Java 애플리케이션
에서 데이터베이스
와 상호작용하기 위한 표준 API입니다.DriverManager, Connection, Statement, PreparedStatement, CallableStatement, ResultSet
등 JDBC의 주요 객체를 이해하고 활용하는 것이 중요합니다.PreparedStatement
는 Statement
에서 일어날 수 있는 SQL Injection
방지와 성능 최적화
를 가능하게 하기 때문에 일반적으로 가장 많이 활용됩니다.이번 포스팅에서는 기본 JDBC 활용법에 초점을 맞췄습니다.
Java 개발자
에게 가장 기본적이면서도 중요한 기술 중 하나이며, SQL을 직접 제어할 수 있다는 점에서 강력합니다. 이후의 포스팅은 데이터베이스 및 JDBC의 내용 중 필요에 따라서 추가적으로 정리가 필요한 부분들이 생기면 작성해볼 예정입니다. (Spring
이나 JPA
는 추후에 따로 시리즈를 만들게 될 것 같습니다..)