72일차 JDBC

쿠우·2022년 7월 7일

JDBC는 java Database Connectivity
자바 언어로 관계형 데이터베이스에 접속해 SQL문 수행가능

Jdbc를 사용하려면 Driver와 API가 필요하다.

JDBC Driver는 프린트 드라이버와 거의 동일하다
JDBC API를 사용하기위해 JDBC Driver가 필수적이다.

- 연동과정
JDBC프로그램 - JDBC 인터페이스 - JDBC드라이버 - DB

JDBC API = java.sql 패키지
JDBC Driver = java.sql 패키지에 인터페이스를 구현한 몸통부분

▲메이븐에서 pom.xml 부분 JDBC드라이버를 알아서 관리해줌 굿!
->원래는 따로 다운받고 관리해줘야함

▼ JDBC API 사용순서

(0) java.sql.DriverManager (Class) -- 1번의 선행조건
(1) java.sql.Connection (interface) -- 2,3번 생성의 선행조건 / 네트워크상의 연결 그자체를 의미한다. / 자바와 DB의 사이에 연결된 길이라 생각하라
(2) java.sql.Statement (interface) ---> Dynamic SQL (잘 사용하면 안됨!!!--> 성능을 떨어뜨림) / DB쪽에서 처리된 결과를 프로그램쪽에 전달(데이터 전달)
(3) java.sql.PreparedStatement (interface) --> Prepared SQL // Statement와 같은 기능을 수행하지만 ? 기호를 쓸 수 있다는 장점 가독성과 유지보수성이 좋음
(4) java.sql.ResultSet (interace) -->2,3번이 있어야 사용가능
(5) java.sql.SQLException (class) - Checked Exception (쳌드오류를 확인)

  • 위에서, (1) ~ (4) 까지는 자원객체(Resources) 임 =>
    때문에, 다 사용하고 나면, 반드시 자원해제(close) 해줘야됨!
    이때 , 자원객체를 닫는 순서가 정해져 있고, 이를 반드시 지켜야 됨! :
    (4) > (2) 또는 (3) > (1)

메소드와 객체에 대해서 사용 설명은 아래의 코드들에 주석으로 적혀있다.
또한 사용에 있어서 간략화하는 과정을 5단계로 나누고 6번 예제는 응용

▼최대한 모든 객체와 메서드 다 적용해서 API설명해놓음


@Log4j2
public class JDBCExample1 {
    								// 오라클 프로토콜 :@ 서버주소 /서버포트 / DB이름 -> 아래는 지갑과 별칭과 URL로 표시
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
	private static String driverClass = "oracle.jdbc.OracleDriver";
	private static String user = "HR";
	private static String pass = "Oracle12345678";
	
	
	
	
//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	log.trace("main({}) invoked.", Arrays.toString(args) );
    	
    	// Step.2 : JDBC를 위한 지역변수 선언
    	Connection conn =null; 
    	Statement stmt = null;  
    	ResultSet rs = null;
    	
    	
    	try {
    	
	    	// Step.3 : JDBC Driver Class를 등록 (registration)= 드라이버 로딩이라고함
	    	Class.forName(driverClass); // 정적 메소드여서 정적 멤버여야함
	    	
	    	// Step.4 : 연결하기
	    	conn = DriverManager.getConnection(jdbcUrl, user, pass);
	    	
	    	log.info("\t  + conn : " ,  conn);
	    	
	    	// Step 5: Connection 객체로부터, Statement 객체를 생성할 수 있다! 
	    	stmt = conn.createStatement();
	    	
	    	// Step6: 실제 수행시킬 , SQL 문장 생성
	    	String sql = "SELECT current_date FROM dual";
	    	
	    	
	    	
	    	//Step 7: Statement 실행 (SQL문장을 전송)
	    	rs = stmt.executeQuery(sql); //실행 메소드를 통해 실행 if DQL(SELECT문장만 해당)
	    	
	    	
	    	//executeQuery()  -> sql ReusltSet 결과값을 가진 객체인 ReusltSet를 반환
	    	
	    	//-ReusltSet객체의 메소드 
	    	//afterLast() -> 맨 뒤 빈행으로 
	    	//beforeFirst() -> 맨 앞 빈행으로
	    	//next() -> 다음 행
	    	//getString().... 등등  - 값 가져오기
	    	
	    	//executeUpdate() -> 반환하는 int값을 표시 즉, 변경된 레코드 개수
	    	
	    	
	    	
	    	//Step 8: ResultSet을 이용해서, 각 레코드별로, 각 컬럼의 값 추출
	    	if(rs.next()) {
	    		//현재 레코드의 구성 칼럼의 값을 추출
	    		//rs.get  < 추출컬럼타입에 대응되는 자바타입> ("컬럼명");
	    		Timestamp now = rs.getTimestamp("current_date");
	        	log.info("\t  + now : " ,  now);
	    	} // while
    	
	    	
    	} catch(SQLException | ClassNotFoundException e ) {
    		e.printStackTrace();
    	
    	
    	
    	} finally {
    		
	    	//Step 9 : 자원객체를 모두 해제(순서가 중요)
	    	// 순서: ResultSet > Statement > Connection
	    	try { if(rs != null && !rs.isClosed())rs.close(); 
	    	// 2번 호출하면 안되니까 닫혔는지도 확인한다.
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
    	
	    	try { if(stmt != null && !stmt.isClosed() )stmt.close();
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
	    	
	    	try { if(conn != null && !conn.isClosed())conn.close();
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
	    	
    	}// try- catch- finally
		
    	
    	
    }//main
}// end class

▼ Class.forName() 제거

Class.forName 메서드는 런타임 시점에 해당 경로의 클래스를 동적으로 로드한다. JDK 6 이후엔 Class.forName() 구문을 적지 않아도 된다. java.sql.Driver 인터페이스의 벤더별 구현 클래스를 모두 로드한다. 그래서 Class.forName으로 로드할 필요가 없다.



@Log4j2
public class JDBCExample2 {
    	//driverClass /ForName 을 사용하지 않는다 
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
//	private static String driverClass = "oracle.jdbc.OracleDriver";
	private static String user = "HR";
	private static String pass = "Oracle12345678";

//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	log.trace("main({}) invoked.", Arrays.toString(args) );
    	
    	// Step.2 : JDBC를 위한 지역변수 선언
    	Connection conn =null;
    	Statement stmt = null;
    	ResultSet rs = null;
    	
    	
    	try {
    	
	    	// Step.3 : JDBC Driver Class를 등록 (registration)
//	    	Class.forName(driverClass); // 정적 메소드여서 정적 멤버여야함
	    	
	    	// Step.4 : 연결하기
	    	conn = DriverManager.getConnection(jdbcUrl, user, pass);
	    	
	    	log.info("\t  + conn : {} " ,  conn);
	    	
	    	// Step 5: Connection 객체로부터, Statement 객체를 생성할 수 있다!
	    	stmt = conn.createStatement();
	    	
	    	// Step6: 실제 수행시킬 , SQL 문장 생성
	    	String sql = "SELECT current_date \r\n" +  // 여러줄을 넣게되면 개행 문자 넣어주기
	    				 "FROM dual";
	    	
	    	//Step 7: Statement 실행 (SQL문장을 전송)
	    	rs = stmt.executeQuery(sql); //실행 메소드를 통해 실행 if DQL(SELECT문장만 해당)
	    	
	    	//Step 8: ResultSet을 이용해서, 각 레코드별로, 각 컬럼의 값 추출
	    	if(rs.next()) {
	    		//현재 레코드의 구성 칼럼의 값을 추출
	    		//rs.get  < 추출컬럼타입에 대응되는 자바타입> ("컬럼명");
	    		Timestamp now = rs.getTimestamp("current_date");
	        	log.info("\t  + now : {} " ,  now);
	    	} // while
    	
    	} catch(SQLException e ) {
    		e.printStackTrace();
    	} finally {
    		
	    	//Step 9 : 자원객체를 모두 해제(순서가 중요)
	    	// 순서: ResultSet > Statement > Connection
	    	try { 
	    		//if(rs != null && !rs.isClosed())
	    		//assert rs != null && !rs.isClosed(); // 언어상의 문법 참이면 그냥 지나간다 아니면 에러발생 
	    		Objects.requireNonNull(rs); // 인자값이 없으면 null포인트 에러를 보낸다
	    		rs.close(); 
	    	// 2번 호출하면 안되니까 닫혔는지도 확인한다.
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
    	
	    	try { if(stmt != null && !stmt.isClosed() )stmt.close();
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
	    	
	    	try { if(conn != null && !conn.isClosed())conn.close();
	    	} catch(SQLException e) {
	    		;;
	    	}// try-catch
	    	
    	}// try- catch- finally
		
    	
    	
    }//main
}// end class


▼ try with resources 블럭으로 자원체계 단순화



@Log4j2
public class JDBCExample3 {
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
	private static String user = "HR";
	private static String pass = "Oracle12345678";

//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	
    	try {
    	
    		Connection conn = DriverManager.getConnection(jdbcUrl, user, pass);
	    	Statement stmt = conn.createStatement();
	    	
	    	String sql = "SELECT current_date \r\n" + "FROM dual";
	    	ResultSet rs = stmt.executeQuery(sql); 
	    	
	    	//자동으로 닫힐 자원 객체를 나열  (생성의 반대의 순서로 닫아야함)
	    	// 닫는순서 ( <- )
	    	try(conn; stmt; rs){
	    		
	    		if(rs.next()) {
	    			Timestamp now = rs.getTimestamp("current_date");
	    			log.info("\t  + now : {} " ,  now);
	    		} // if

	    	} // try with resources
    	} catch(SQLException e ) {
    		e.printStackTrace();
    	} // try- catch
		
    	
    	
    }//main
}// end class



▼lombok의 Cleanup 어노테이션 사용으로 더 단순화


@Log4j2
public class JDBCExample4 {
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
	private static String user = "HR";
	private static String pass = "Oracle12345678";
	
//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	
    	try {
//    		@Cleanup("close") <-("자원객체를 닫는 메소드의 이름지정 - 기본은 close")
    		// 순서는 생성 반대로 닫힘
    		@Cleanup Connection conn = DriverManager.getConnection(jdbcUrl, user, pass);
    		@Cleanup Statement stmt = conn.createStatement();
	    	
	    	String sql = "SELECT current_date \r\n" + "FROM dual";
	    	@Cleanup ResultSet rs = stmt.executeQuery(sql); 
	    		
	    		if(rs.next()) {
	    			Timestamp now = rs.getTimestamp("current_date");
	    			log.info("\t  + now : {} " ,  now);
	    		} // if

    	} catch(SQLException e ) {
    		e.printStackTrace();
    	} // try- catch
		
    }//main
}// end class

▼ PreparedStatement의 ? 이용해 가독성 유지보수성 향상



@Log4j2
public class JDBCExample5 {
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
	private static String user = "HR";
	private static String pass = "Oracle12345678";
	
//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	
    	try {
    		@Cleanup Connection conn = DriverManager.getConnection(jdbcUrl, user, pass);
    		// 1. Dynamic SQL(동적인 SQL) => Database에 상당한 부담을 줌 어쩔 수 없는 경우에만 씀
//    		@Cleanup Statement stmt = conn.createStatement(); // Dynamic SQL에 대한 문장생성
	    	
    		 
    		// 2. prepared SQL(정적인 SQL) => DB에 최적화 된 처리가능하게 함// 물음표의 값에 따라 삭제되는 부분이 변함 
	    	String sql = "DELETE FROM tbl_emp WHERE employee_id = ? ";   // ?: Bind Variable 이라고 부름
	    	PreparedStatement pstmt = conn.prepareStatement(sql);
	    	
	    	
	    	// 3. Bind Variable에 값 설정 (Binding)
	    	pstmt.setInt(1,167); 
	    	
	    	
	    	int affectedLines = pstmt.executeUpdate();
	    	
	    	
	    	log.info("\t 삭제된 라인수를 반환: {}" , affectedLines);
	    	
    	} catch(SQLException e ) {
    		e.printStackTrace();
    	} // try- catch
		
    }//main
}// end class



▼ rs.next() ,rs.get~~() 사용을 통한 데이터뽑기 응용


@Log4j2
public class JDBCExample6 {
	private static String jdbcUrl = "jdbc:oracle:thin:@db20220510181319_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP";
	private static String user = "HR";
	private static String pass = "Oracle12345678";
	
//	public static void main( String[] args ) throws ClassNotFoundException, SQLException{
	public static void main( String[] args ) {
    	
    	try {
    		@Cleanup Connection conn = DriverManager.getConnection(jdbcUrl, user, pass);
	    	
    		 
    		// 1. 문제에 맞는 쿼리 설정
	    	String sql = "SELECT * FROM employees WHERE salary >= ? AND department_id >= ? ";   // ?: Bind Variable 이라고 부름
	    	PreparedStatement pstmt = conn.prepareStatement(sql);
	    	
	    	// 2. Bind Variable에 값 설정 (Binding)
	    	pstmt.setDouble(1,7000); // 첫번째 물음표에 7000대입 
	    	pstmt.setInt(2,50); // 두번째 물음표에 50 대입
	    	
	    	
	    	// DQL 조건에서 얻어낸 SELECT 결과값 저장
	    	@Cleanup ResultSet rs =pstmt.executeQuery();
	    	
            //결과에서 행을 다음으로 넘기며 해당 속성값을 출력한다.
	    	while(rs.next()) {
	    		Integer EMPLOYEE_ID 	= rs.getInt("EMPLOYEE_ID");
	    		String FIRST_NAME   	= rs.getString("FIRST_NAME");
	    		String LAST_NAME 		= rs.getString("LAST_NAME");
	    		String EMAIL 			= rs.getString("EMAIL");
	    		String JOB_ID 			= rs.getString("JOB_ID");
	    		String PHONE_NUMBER 	= rs.getString("PHONE_NUMBER");
	    		Double SALARY 			= rs.getDouble("SALARY");
	    		Date HIRE_DATE 			= rs.getDate("HIRE_DATE");
	    		Double COMMISSION_PCT 	= rs.getDouble("COMMISSION_PCT");
	    		Integer MANAGER_ID 		= rs.getInt("MANAGER_ID");
	    		Integer DEPARTMENT_ID 	= rs.getInt("DEPARTMENT_ID");
	    	
	    		String employee = 
	    			String.format("%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",EMPLOYEE_ID,FIRST_NAME,LAST_NAME, EMAIL, 
	    					JOB_ID, PHONE_NUMBER, SALARY ,HIRE_DATE ,COMMISSION_PCT, MANAGER_ID, DEPARTMENT_ID );
	    		
	    		
	    	log.info("Employee: {}" , employee);
    		}// while
    	} catch(SQLException e ) {
    		e.printStackTrace();
    	} // try- catch
		
    }//main
}// end class

끝.

profile
일단 흐자

0개의 댓글