JDBC
2025. 3. 19. 18:42ㆍ기반기술/서버 개발
1. JDBC란?
- JDBC는 자바에서 데이터베이스와 연결하여 데이터를 주고받을 수 있도록 해주는 API이다.
- 자바 애플리케이션에서 SQL을 실행할 수 있도록 도와주는 역할
- java.sql package의 interface와 class를 활용한다.
- JDBC 주요 기능
- 데이터베이스 연결 : DriverManager, Connection
- SQL 실행 : Statement, PreparedStatement
- 결과 가져오기 : ResultSet
- 트랜잭션 처리 : commit, rollback
- 연결 종료 : close (Connection, Statement, ResultSet 모두 자원 반납 필수임)
2. 데이터베이스 연결
1) DriverManager Class
- 데이터 원본(= Database)에 JDBC driver를 통하여 Connection을 만드는 역할을 한다.
- 반드시 예외 처리를 해야 한다.
- 직접 instance 생성이 불가하고, DriverManager 클래스의 getConnection() 메소드를 사용하여 Connection instance를 생성할 수 있다.
2) Connection Class
- 특정 데이터 원본(= Database)과 연결된 Connection을 나타낸다.
- 쿼리문을 실행할 수 있는 Statement 혹은 PreparedStatement 객체를 생성할 수 있는 기능을 제공한다.
- Connection 객체 자원은 사용 후 반드시 반납해야 한다.
- Connection 객체를 생성하는 코드 또한 중복 작성하게 되므로, Template 클래스의 static 메소드로 Connection 객체를 생성하거나 반납하는 코드를 작성하여 공통으로 사용하도록 하는 것이 일반적이다.
3. SQL 실행
1) Statement
- SQL문을 저장하고 실행한 뒤 결과를 받아 반환해주는 메소드들이 묶여 있는 타입의 클래스이다.
- Statement 객체 생성 및 사용
- Connection class의 createStatement() 메소드를 호출하여 Statement instance를 생성한다.
- 생성한 instance의 executeQuery() 메소드를 호출하여 SQL문 수행한다. (SQL문을 String 형태로 인자로 전달한다.)
2) PreparedStatement
- PreparedStatement는 placeholder '?'를 활용한 하나의 문자열 형태로 쿼리를 작성한다.
- 완성된 쿼리문과 미완성된 쿼리문(= 위치홀더를 사용한 쿼리문)을 모두 사용할 수 있다.
- PreparedStatement는 인수가 많아 특정 값만 바꾸어 여러 번 실행하는 상황에 유용하다.
- 장점: 수행 속도가 빠르다. SQL injection 공격에 대하여 안전한다.
4. ResultSet
- SELECT문 수행 성공 시 반환한 결과값을 받아오는 객체이다.
- SQL문에 의해 생성된 결과 테이블을 담고 있다.
- Connection과 Statement도 close()로 자원 반납하듯, ResultSet도 close()를 통해 자원을 반납해야 한다.
- Method
- get자료형(”컬럼명”) : ResultSet의 현재 커서 위치에 존재하는 로우에서 인자로 전달한 컬럼의 결과 값을 가지고 온다.
- next() : ResultSet의 커서 위치를 하나 내리면서 다음 행이 존재하면 true 존재하지 않으면 false를 반환한다.
5. JDBC 활용 예시
//MySQL 설정
//build.gradle에 mysql dependencies 추가
dependencies {
// https://mvnrepository.com/artifact/com.mysql/mysql-connector-j
implementation 'com.mysql:mysql-connector-j:9.2.0'
...
}
//설정 정보를 저장하는 파일 따로 분리
//jdbc-config.properties
url=jdbc:mysql://localhost:3306/companydb
user=practice
password=practice
//JDBC Template 파일
public class JDBCTemplate {
public static Connection getConnection(){
Properties properties = new Properties();
Connection con = null;
try {
// url, user, password와 같은 설정 정보는
// 유지보수성을 위해 파일 내 리터럴 값으로 작성하지 않고
// 별도의 properties 파일로 분리하여 관리하는 것이 좋다.
properties.load(new FileReader("src/main/java/com/hnjee/config/jdbc-config.properties"));
String url = properties.getProperty("url");
con = DriverManager.getConnection(url, properties);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return con;
}
//Connection을 닫는 개념은 별도의 메소드로 분리하고 실제 닫는 시점은 Service 계층에서 진행
public static void close(Connection con){
try {
if(con != null && !con.isClosed()) con.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void close(ResultSet rset){
try {
if(rset != null && !rset.isClosed()) rset.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void close(Statement stmt){
try {
if(stmt != null && !stmt.isClosed()) stmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
//Statement 활용
import static com.hnjee.common.JDBCTemplate.close;
import static com.hnjee.common.JDBCTemplate.getConnection;
//사번을 Scanner로 입력 받아서 사원의 정보를 출력하는 프로그램
//사원의 정보: emp_id, emp_name, salary
//없는 사번이면 "해당 사원의 조회 결과가 없습니다" 출력
public class Application2 {
public static void main(String[] args) {
Connection con = getConnection();
Statement stmt = null;
ResultSet rset = null;
Scanner sc = new Scanner(System.in);
System.out.print("사번을 입력하세요:");
int empId = sc.nextInt();
try {
stmt = con.createStatement();
rset = stmt.executeQuery("SELECT * FROM employee WHERE emp_id ="+empId);
if(rset.next()){
int emp_id = rset.getInt("emp_id");
String emp_name = rset.getString("emp_name");
int salary = rset.getInt("salary");
System.out.println(emp_id+", "+emp_name+", "+salary);
} else{
System.out.println("해당 사원의 조회 결과가 없습니다");
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
close(con);
close(stmt);
close(rset);
}
}
}
//PreparedStatement 활용
import static com.hnjee.common.JDBCTemplate.close;
import static com.hnjee.common.JDBCTemplate.getConnection;
//사번을 Scanner로 입력 받아서 사원의 정보를 출력하는 프로그램
//사원의 정보: emp_id, emp_name, salary
//없는 사번이면 "해당 사원의 조회 결과가 없습니다" 출력
public class Application2 {
public static void main(String[] args) {
Connection con = getConnection();
PreparedStatement pstmt = null;
ResultSet rset = null;
Scanner sc = new Scanner(System.in);
System.out.print("사번을 입력하세요:");
int empId = sc.nextInt();
try {
//쿼리 세팅
//PreparedStatement는 placeholder '?'를 활용한 하나의 문자열 형태로 쿼리를 작성한다.
pstmt = con.prepareStatement(
"SELECT emp_id, emp_name, salary " +
"FROM employee " +
"WHERE emp_id = ? and ent_yn = ?"
);
//파라미터 세팅
//쿼리 실행 전 placeholder의 내용을 인덱스 번호를 통해 설정한다.
pstmt.setInt(1, empId);
pstmt.setString(2, "N");
//쿼리 실행
rset = pstmt.executeQuery();
if(rset.next()){
int emp_id = rset.getInt("emp_id");
String emp_name = rset.getString("emp_name");
int salary = rset.getInt("salary");
System.out.println(emp_id+", "+emp_name+", "+salary);
} else{
System.out.println("해당 사원의 조회 결과가 없습니다");
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
close(con);
close(pstmt);
close(rset);
}
}
}