본문 바로가기

Sever

3. JDBC의 흐름 및 JDBC Tamplate

JDBC (Java Database Connectivity)

 Java 프로그래밍 언어에서 데이터베이스에 접속하여 데이터를 조작할 수 있도록 하는 API

 

✓ 흐름

① 드라이버 로드: 데이터베이스 연결을 위해 해당 데이터베이스 JDBC 드라이버 로드

연결 생성: DriverManager 클래스를 이용하여 데이터베이스 연결 객체(Connection)생성(이때, 연결 정보를 지정)

Statement 생성: Connection 객체를 이용해, SQL문 실행을 위한 Statement 객체 생성

SQL문 실행: Statement 객체를 이용해 SQL문을 실행, 결과를 ResultSet 객체로 받아옴

결과 처리: ResultSet 객체를 이용하여 SQL 실행 결과를 처리

자원 반환: 모든 작업이 완료되면 사용한 자원(Connection, Statement, ResultSet 등)을 반드시 반환

                    (메모리 누수 방지, 다른 프로그램에서 해당 자원 접근 가능)

 

① 드라이버 로드

 - JDBC는 다양한 데이터베이스관리시스템(DBMS)과 호환될 수 있도록, DBMS 별로 JDBC 드라이버를 제공한다. 따라서, 프로그램에 JDBC를 사용하여 특정 DBMS에 접속하려면, 해당 DBMS에 맞는 JDBC 드라이버를 로드하여야 한다.

 - 드라이버를 로드하는 작업은 JDBC를 사용하는 모든 프로그램에서 동일하게 이루어지기 때문에, 일종의 Tamplate이라 볼 수 있다. , 프로그램에서 데이터베이스에 접속하기 위해서는 반드시 드라이버 로드 작업이 필요하며, 이 작업은 모든 JDBC 프로그램에서 공통적으로 수행된다.

 - 따라서, JDBC 프로그램에서는 드라이버 로드 작업을 하나의 Tamplate으로 묶어, 코드의 재사용성과 유지보수성을 높일 수 있다. 예를 들어, 드라이버 로드 작업을 수행하는 클래스를 만들어 이를 다른 JDBC 프로그램에서 재사용할 수 있다.

 

※ Singleton Tampalte

- 클래스의 인스턴스를 단 하나만 생성하고 이를 전역적으로 제공하는 패턴

 - JDBC에서도 Singleton Tamplate를 이용하여 드라이버 로드와 연결 생성을 담당하는 클래스를 만들어서 모든 JDBC 프로그램에서 이를 공유하여 사용할 수 있다. 이렇게 하면 드라이버 로드와 연결 생성 작업을 반복적으로 수행하지 않아도 되므로, 코드의 중복을 줄이고 성능을 개선할 수있다.

 - Singleton Tamplate을 구현할 때, 클래스 내부에 private static으로 해당 클래스의 인스턴스를 하나만 생성하고, 이를 가져다 쓸 수 있는 public static 메소드를 구현한다. 이때, 생성자는 private로 선언하여 외부에서 인스턴스를 생성할 수 없도록 한다.

- DB 연결(Connection 생성), 자동 comit off, 트랜잭션제어, JDBC 객체 자원반환(close)등, JDBC에서 반복 사용되는 코드를 모아둔 클래스로, 모든 필드와 메서드가 static 으로 어디서든 클래스명.필드명  or 클래스명.메서드명으로 별도 객체 생성 없이 호출 가능하다.

 

<Singleton Tamplate 예시1: DBCP(DataBase Connection Pool) 사용 >

package edu.kh.todo.common;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;

//-----------------------------

public class JDBCTemplate {

	private static Connection conn = null;
	// -> static 메서드에서 필드를 사용하기 위해서는 필드도 static 필드여야한다.
	// JDBC 제공 인터페이스 Connection을 통하여 JDBCTampalate 클래스의 static 필드로 선언한 것.
	// Connection 인스턴스 변수 : conn 
	// conn은 JDBCTamplate 클래스의 모든 static 메서드에서 사용할 수 있다. 
	// 이렇게 클래스 내부에 선언된 변수를 '필드'라고 부르며 인스턴스 변수는 객체마다 각각의 값을 
	// 가지기 때문에 전역변수와도 유사하다. 


	public static Connection getConnection() {
	// DB 연결 정보를 담고있는 Connection 객체 생성 및 반환 메서드
	// (conn을 사용하기 위한 메서드)이 메서드를 사용하여 데이터베이스와 연결 
		
		try {
			
			Context initContext = new InitialContext();
			// JNDI(Java Naming and Directory Interface API)
			// - Java Application에서 네트워크 서비스, 파일 서비스 및 디렉토리 서비스 등
			//   자원에 액세서 하기 위한 일반적인 인터페이스를 제공한다. 
			// - 즉,JNDI를 이용하여 InitialContext 객체를 생성. 
			//   InitialContext는 JNDI에 등록된 Namimg Context에 접근하여 객체를 검색할 수 있음
			// - 따라서, InitContext 변수는 InitialContext 객체를 생성하고, 이를 사용하여 
			//   Naming Context에 등록된 다른 객체를 검색할 수 있는 기능 제공. 
			//   .. 이를 통해 필요한 객체를 검색하고 이용할 수 있다. 
			
			
			Context envContext = (Context)initContext.lookup("java:/comp/env");
			//servers -> context.xml 파일 찾기
			// "java:/comp/env"라는 Context 객체를 검색하고, 검색한 객체를 반환함. 
			// 그러나, 이 반환된 객체의 타입은 Context인터페이스의 서브타입 중 하나일 수 있다. 
			// 따라서 이를 사용하기 위해 원래 타입에서 Context인터페이스로 형변환을 해주어야 한다. 
            
			DataSource ds = (DataSource)envContext.lookup("jdbc/oracle");
			// DBCP(DataBase Connection Pool)세팅의 (DBCP: DB연결 풀링 구현 라이브러리 ex:톰캣)
			// <Resource>태그를 찾아서 DataSource 형식의 객체로 얻어오기
			// DataSource : Connection Pool을 구현하는 객체(만들어둔 Connection 얻어오기 가능)
			// 즉, "jdbc/oracle" 이름의 DataSource객체 검색 위해 envContext.lookup()메서드 호출
            
			conn = ds.getConnection();
			// DBCP에서 DataSource를 이용하여 Connection 객체를 생성하고, 생성된 Connection 
			// 객체를 변수 conn에 대입. DB와의 작업을 위해 사용 
			
			conn.setAutoCommit(false);
			// Connection 객체에서 수행하는 작업을 트랜잭션 단위로 처리하기 위한 메서드 
			// false로 설정하였으니, DB작업 수행 후 commit() 매서드를 호출하기 전까지 반영하지 않음. 
			// 데이터의 일관성을 보장하기 위한 것. 
			
		} catch (Exception e) {
			System.out.println("[Connection 생성 중 예외 발생]");
			e.printStackTrace();
		}

		return conn;
	}
	
	
	
	/** Connection 객체 자원 반환 메서드
	 * @param conn
	 */
	public static void close(Connection conn) {
		try {
			if(conn != null && !conn.isClosed()) conn.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/** Statement(부모), PreparedStatement(자식) 객체 자원 반환 메서드
	 * 다형성 / 동적바인딩
	 * @param stmt
	 */
	public static void close(Statement stmt) {
		try {
			if(stmt != null && !stmt.isClosed()) stmt.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}


	/** ResultSet 객체 자원 반환 메서드
	 * @param conn
	 */
	public static void close(ResultSet rs) {
		try {
			if(rs != null && !rs.isClosed()) rs.close();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/** 트랜잭션 Commit 메서드
	 * @param conn
	 */
	public static void commit(Connection conn) {
		try {
			if(conn != null && !conn.isClosed()) conn.commit();
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/** 트랜잭션 Rollback 메서드
	 * @param conn
	 */
	public static void rollback(Connection conn) {
		try {
			if(conn != null && !conn.isClosed()) conn.rollback();
			
		}catch(Exception e) {
			e.printStackTrace();
		}
	}	
}

 

<Singleton Tamplate 예시2: DriverManager 사용 >

package edu.kh.jdbc.common;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

// --------------------------------

public class JDBCTemplate {
	
	private static Connection conn = null;
    
	
	/** DB 연결 정보를 담고있는 Connection 객체 생성 및 반환 메서드
	 * @return conn
	 */
	public static Connection getConnection() {
		
		try {
			
			if(conn == null || conn.isClosed()) {
			// 현재 커넥션 객체가 없을 경우 -> 새 커넥션 객체를 생성
			// conn.isClosed() : 커넥션이 close 상태이면 true 반환
				
				
				Properties prop = new Properties();
				// Map<String, String> 형태의 객체, XML 입출력 특화
				
                
				prop.loadFromXML( new FileInputStream("driver.xml") );
				// driver.xml 파일 읽어오기
				// -> XML 파일에 작성된 내용이 Properties 객체에 모두 저장됨.
				
				
				// XML에서 읽어온 값을 모두 String 변수에 저장
				String driver = prop.getProperty("driver");
				String url = prop.getProperty("url");
				String user = prop.getProperty("user");
				String password = prop.getProperty("password");
				
				
				// 커넥션 생성
				// Oracle JDBC Driver 객체 메모리 로드
				Class.forName(driver); 
				
                
				// DriverManager를 이용해 Connection 객체 생성
				conn = DriverManager.getConnection(url, user, password);
				
                
				// 자동 커밋 비활성화
				conn.setAutoCommit(false);
			}
            
			
		}catch(Exception e) {
			System.out.println("[Connection 생성 중 예외 발생]");
			e.printStackTrace();
		}
		
		return conn;
	}
	
	
	
		// 밑에 작성된 메서드는 위와 같음
	
	
}

 

 

* 메서드용 주석 ? 

 - Window: alt + shift + j   //   Mac : opt + comm + j 

 - 주석이 없어도 메서드의 동작에는 지장이 없으나, 작성한다면 코드의 가독성과 유지보수성을 높이기 때문에 쓰는 것이 좋다. 

 - 여기서, @param 태그는 메서드의 매개변수에 대한 설명을 작성할 때 사용된다. 여기서 @param conn은 conn 매개변수가 Connection의 객체임을 설명하는 것이다. 

 

* Statement와 PreparedStatement 
 - 모두 SQL 쿼리를 실행하는 데 사용하는 인터페이스 
 - Statement는 쿼리를 실행하기 전 SQL쿼리 문자열 생성, 이 문자열은 직접 SQL 쿼리로 파싱한다. 그러므로, 사용 시 매번 쿼리를 실행할 때마다 SQL파서가 쿼리를 분석하고 컴파일해야하기 때문에 반복적 쿼리 실행 시 효율적이지 않다. 
 - PreparedStatement는 쿼리를 실행하기 전 미리 컴파일 된 SQL쿼리를 가지고 있어, 이를 통해 매번 쿼리를 실행할 때마다 SQL파서를 호출할 필요가 없기 때문에 효율적이다. 또한, 입력 매개변수를 사용하여 쿼리를 만들어 더욱더 안전하고 쉽게 작성할 수 있다. 

 - Statement는 인터페이스 중 하나로, 이를 구현하는 Statement, PreparedStatement, CallableStatement 세가지 구현체가 있다. 이 구현체는 모두  Statement 인터페이스를 상속받는다. 

 

* 동적 바인딩과 정적 바인딩

 - 바인딩: 프로그램의 변수나 메서드 호출 등의 이름과 값을 연결하는 과정 

 - 동적 바인딩: 프로그램 실행 중 메서드를 호출할 때, 어떤 메서드를 호출할지 결정하는 과정으로 호출하는 객체의 실제 타입에 따라 동적으로 결정된다. 

 - 정적 바인딩: 컴파일 타임에 따라 결정되는 바인딩 방식으로 메서드 호출 시 해당 변수나 객체의 타입을 보고 호출할 메서드를 결정 

 

 

 

* 인스턴스 / 인터페이스 / static 필드 

더보기

인스턴스(Instance)

클래스를 기반으로 실체화된 객체. 클래스는 객체를 만들기 위한 설계도라면 이를 실제로 생성한 것을 인스턴스라 함. 객체지향프로그래밍에서는 클래스를 정의하고 이를 기반으로 여러 개의 인스턴스를 생성할 수 있다. 이때 각각의 인스턴스는 자신의 고유한 속성과 메소드를 가지며, 서로 독립적으로 동작한다.

예를 들어, Person이라는 클래스를 정의하였다면, 이 클래스를 기반으로 Joe Jane이라는 두 개의 인스턴스를 생성할 수 있다. Joe Jane은 모두 Person클래스의 속성과 메소드를 상속받지만, 각자 고유한 속성과 메소드를 가지며 서로 독립적으로 동작한다. 인스턴스를 생성하기 위해서는, 클래스를 이용하여 new 연산자를 사용해 객체를 생성하고, 생성된 객체를 해당 클래스의 인스턴스라 부른다. 이렇게 생성된 인스턴스는 메모리에 할당되며, 이를 이용하여 해당 클래스의 속성과 메소드에 접근할 수 있다.

 

인터페이스(Interface) 

객체지향프로그래밍에서 구현 객체의 교환성을 높여주기 위한 개념으로 일종의 추상 클래스이다. 인터페이스는 클래스가 구현해야 하는 케서드들의 명세(specification)을 정의하는데 사용한다. 즉, 인터페이스는 클래스가 가져야 할 기능을 정의하고 있으며, 이를 구현한 클래스는 인터페이스에서 정의한 메서드들을 반드시 구현해야 한다. 인터페이스는 다른 클래스에서 구현되어 사용되는데, 구현 클래스에서는 인터페이스가 정의한 메서드를 반드시 오버라이드(재정의) 해야한다. 이를 통해 다형성을 구현할 수 있으며, 인터페이스를 구현한 여러 클래스들은 같은 매서드 이름을 가지므로, 이를 호출하는 코드는 어떤 클래스의 인스턴스를 사용하든지 상관 없이 같은 이름의 메서드를 호출할 수 있다. 

 

Static field

클래스 변수라고도 불리며, 해당 클래스의 모든 인스턴스가 공유하는 필드. static 키워드를 사용하여 선언하며, 객체 생성과 상관 없이 클래스 로딩 시점에 메모리에 할당이 된다. 즉, 필드는 인스턴스 생성시마다 메모리에 할당이 되어, 모든 인스턴스가 공유할 수 있다. 

* field: 클래스나 객체에 속한 변수, 클래스 내부에서 선언된 변수 중 메서드 밖에서 사용할 수 있는 변수(클래스 내에서 전역적으로 사용되는 변수)