백엔드/JAVA

데이터 입출력(1)

두개의 문 2023. 5. 30. 22:18
JDBC 개요

 

- JDBC ( Java Database Connectivity ) 라이브러리 ( java.sql.패키지 )를 제공 데이터베이스 ( DB )와 연결해서 데이터 입출력 작업

 · JDBC는 데이터베이스 관리시스템 ( DBMS )의 종류와 상관없이 동일하게 사용할 수 있는 클래스와 인터페이스로 구성

 

- JDBC에 포함되어 있는 클래스와 인터페이스들의 연관관계

  · JDBC Driver 

    : JDBC 인터페이스를 구현한 것으로, JDBC 인터페이스를 통해 실제로 DB와의 작업을 함

      DBMS마다 별도로 다운로드 받아 사용해야 함 (이전 게시물에 자세히 작성함)

   

  ① DriverManager

    : DriverManager 클래스는 JDBC Driver를 관리하며 DB와 연결해서 Connection 구현 객체 생성

 

  ② Connection

    : Connection 인터페이스는 Statement, PreparedStatement, CallableStatement 구현 객체를 생성

      트랜잭션( transation ) 처리 및 DB 연결을 끊을 때 사용

 

  ③ Statement 

    : Statement 인터페이스는 SQL의 DDL과 DML을 실행할 때 사용

      → 주로 변경되지 않는 정적 SQL문을 실행할 때 사용

 

  ④ PreparedStatement

    : PreparedStatement는 Statement와 동일하게 SQL의 DDL, DML문을 실행할 때 사용

      Statement과의 차이점 : 매개변수화된 SQL문을 사용할 수 있으므로 편리성과 보안성이 좋음

 

  ⑤ CallableStatement

    : CallableStatement는 DB에 저장되어 있는 프로시저와 함수를 호출할 때 사용

 

 

 


DB 구성 - 학습용 DB 및 Table 생성

 

▶ DB 생성

① Database - 오른쪽 클릭 - [ Create New Database ] - Name 입력란에 데이스베이스명 입력 후 OK

② 성공적으로 DB가 생성되면 해당 데이터베이스를 선택 후 오른쪽 클릭 - [ Set as default ] 클릭 → localhost 계정의 기본 DB로 설정

 

 

▶ Table 생성

 - 새 SQL 편집기를 열어 테이블 생성 코드를 작성

 

① 사용자 정보가 저장될 users 테이블 생성

 

② 게시물 정보가 저장될 boards 테이블 생성  - UI 방식 

  - 해당 데이터베이스에서 오른쪽 클릭 - [ Create ] - [ Table ] → 테이블 생성 창 뜸 ( 테이블명 작성 및 하단 컬럼모양 아이콘 클릭하여 컬럼명 및 속성 설정 ) 

  · Engine : InnoDB ( Transaction-safe 한 Storage Engine을 의미 )

  · Auto Increment : 0 ( 자동 숫자 증가 : 0부터 시작하라는 의미 )

 

 

- 아래 사진과 같이 컬럼을 다 추가시킨 후, 기본키 설정하기

  기본키로 정할 컬럼명에서 오른쪽 클릭 - [ New Constraint from Selection ] - [ Create constraint for table "boards" ] 창이 뜸 

  → 여기서 기본키로 설정할 컬럼명을 선택한 후 OK버튼 클릭

 

 

- 하단의 디스크 모양의 저장버튼 클릭 시, [ Persist Changes ] 창이 뜸 → UI로 작성한 테이블의 컬럼이 SQL문으로 작성된 것을 확인 가능 → 확인 후, 하단의 [ Persist ] 클릭  

 

 

③ 계좌 정보가 저장될 accounts 테이블 생성

- 테이블 생성 후, Insert문을 통해 데이터 추가

 

 

해당 데이터베이스에서 오른쪽 클릭 - [ Refresh ] → Database Navigator 에서 생성한 테이블 확인 가능  

 

 

 


DB 연결

 

- 클라이언트 프로그램에서 DB와 연결하려면 해당 DBMS의 JDBC Driver 필요 및 다음 4가지의 정보가 필요

 

① DBMS가 설치된 컴퓨터의 IP 주소 → 해당 컴퓨터를 찾아가기 위해

② DBMS가 허용하는 포트(Port) 번호 → DBMS로 연결하기 위해

③ 사용자(DB 계정) 및 비밀번호 → 어떤 사용자인지 인증받기 위한 계정 및 비밀번호 필요

④ 사용하고자 하는 DB 이름 → DBMS는 여러 개의 DB를 관리하므로 실제로 사용할 DB 이름 필요 

 

 

▶ JDBC Driver 설치 

(이전 게시물에 자세히 작성)

 

▶ DB 연결

① 클라이언트 프로그램을 DB와 연결하기 위해 가장 먼저 해야할 작업 : JDBC Driver를 메모리에 로딩하기

     Class.forName( ) 메서드 : 문자열로 주어진 JDBC Driver 클래스를 Build Path에서 찾은 후, 메모리에 로딩함

 String JDBC_DRIVER = "org.mariadb.jdbc.Driver";     
Class.forName(JDBC_DRIVER);

    → JDBC Driver 클래스의 static 블록이 실행되면서 DriverManager에 JDBC Driver 객체를 등록하게 됨

         만약 Build Path에서 JDBC Driver 클래스를 찾지 못한다면, ClassNotFoundException이 발생하므로 예외처리 필수!

 

② DriverManager에 JDBC Driver가 등록되면 getConnection( ) 메서드로 DB와 연결 가능

String JDBC_URL = "jdbc:mariadb://localhost:3306/(사용할 DB명)";
                                                                                      IP주소     포트(기본 포트일 경우, 생략 가능)
                                                                                    // localhost는 로컬에 설치된 MariaDB에 연결하겠다는 의미 

                                                                                       원격 MariaDB에 연결하려면 IP주소로 기술해야 함
String USER = "root";
String PASSWORD = "mariadb";

Connection conn = DriverManager.getConnection("연결 문자열", "사용자", "비밀번호");

    → 연결에 성공하면 getConnection( ) 메서드는 Connection 객체를 리턴함

        만약 연결이 실패하면 SQLException이 발생하므로 예외처리 필수!

 

 

▶ ConnectionExample 클래스

package mariadb;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class ConnectionExampleApp2 {

	public static void main(String[] args) {
			// 멤버변수
			Connection conn = null;		// 로컬변수는 반드시 초기화해주어야 함
			
            		// 멤버상수
			String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
			String JDBC_URL = "jdbc:mariadb://localhost:3306/thisisjava";
			String USER = "root";
			String PASSWORD = "mariadb";
			
			// JDBC_DRIVER 가져오기
			try {
            			// JDBC_DRIVER 등록
				Class.forName(JDBC_DRIVER);
				System.out.println("JDBC_DRIVER 가져오기 성공!");	// 로그
				// 연결
			    conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
				System.out.println("MariaDB에 연결 성공!");
			} catch(ClassNotFoundException e) {
				e.printStackTrace();
			} catch(SQLException e) {
				e.printStackTrace();
			} finally {
				// 연결 끊기 -> Connection 객체 닫기
				try {
					if( conn != null)
					conn.close();
				} catch (SQLException e) { }
			}	
	}
}

멤버 변수 ( 클래스 변수와 인스턴스 변수 )와 배열의 초기화는 선택적이지만, 지역변수의 초기화는 필수적!

 

→ 메서드 선언부에 키워드 throws를 통해 예외를 선언함으로써 메서드를 사용하려는 사람이 메서드의 선언부를 보았을 때, 이 메서드를 사용하기 위해서는 어떠한 예외들이 처리되어져야 하는지 쉽게 알 수 있음 

    ∴ 이 메서드 사용 시, 해당 예외에 대한 처리를 해주어야 함 → try - catch 구문 이용

 

 

 


데이터 저장

 

데이터를 저장하기 전에, Statement와 PreparedStatement 객체의 차이점에 대해 알아보자.

 ① Statement 

    : Statement 인터페이스는 SQL의 DDL과 DML을 실행할 때 사용

      → 주로 변경되지 않는 정적 SQL문을 실행할 때 사용

INSERT INTO users
               ( userid, username, userpassword, userage, useremail )
               VALUES
               ( 'winter', '한겨울', '12345', 25, 'winter@company.com' );

 

  ② PreparedStatement

    : PreparedStatement는 Statement와 동일하게 SQL의 DDL, DML문을 실행할 때 사용

       Statement과의 차이점 : 매개변수화된 SQL문을 사용할 수 있으므로 편리성과 보안성이 좋음

INSERT INTO users
               ( userid, username, userpassword, userage, useremail )
               VALUES
               ( ?, ?, ?, ?, ? );
// 값을  ?( 물음표 )로 대체한 매개변수화된 INSERT문으로 변경하면 됨

 

users 테이블 : JDBC를 이용해서 INSERT문을 통해 새로운 사용자 정보를 저장해보자.

package mariadb;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class UserInsertExampleApp {

	public static void main(String[] args) {
		// 멤버변수
        	Connection conn = null;
		
        	// 멤버상수
		String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
		String JDBC_URL ="jdbc:mariadb://localhost:3306/thisisjava";
		String USER = "root";
	    	String PASSWORD = "mariadb";

		try {
        		// JDBC_DRIVER 등록
			Class.forName(JDBC_DRIVER);
			System.out.println("DRIVER 로딩 성공!");
			// 연결
			conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
			System.out.println("MariaDB 연결 성공!");
			
			// 실제 JDBC를 사용하여 쿼리를 처리할 때는 PreparedStatement를 사용
			String sql = """
					INSERT INTO users 
						( userid, username, userpassword, userage, useremail )
						VALUES
						( ?, ?, ?, ?, ? ); """;
                        
			// PreparedStatement 명령 객체 생성 및 값 지정
				// ① sql문을 미리 준비해서 객체 생성 시, sql문을 매개변수로 지정 
			PreparedStatement pstmt = conn.prepareStatement(sql);
			
				// ② 실행할 쿼리에 필요한 값 지정
			pstmt.setString(1, "winter");
			pstmt.setString(2, "한겨울");
			pstmt.setString(3, "12345");
			pstmt.setInt(4, 25);
			pstmt.setString(5, "winter@mycompany.com");
			
			// SQL문 실행 (명령객체 생성 시 sql를 매개변수로 지정했으므로 executeUpdate()에는 지정 안함)
			int insertCount = pstmt.executeUpdate();
			System.out.println("저장된 행 수 : " + insertCount);
			
			// 반드시 명령객체(실행객체)를 닫아야 함 
				// -> pstmt가 try블럭에서 선언되었으므로 finally 블럭에서 닫을 수 없음
			pstmt.close();
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch(SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				// 연결끊기
				if(conn != null) 
				conn.close();
			} catch (SQLException e) { }
		}
	}
}

PreparedStatement의 경우, Statement와 달리 Connection의 prepareStatement( ) 메서드에 매개변수로 미리 준비한 sql문이 들어감 → SQL문 실행 즉, executeUpdate( ) 메서드에는 매개변수로 sql를 지정하지 않음

매개변수화된 sql문의 ?에 들어갈 값을 지정함

    즉, setString( ) 메서드의 선언부를 찾아보면 다음과 같음

    첫번째 매개변수가 parameterIndex값이므로 ?는 순서에 따라 1번부터 번호 부여

    ∴ 값의 타입에 따라 Setter메서드를 선택한 후, 매개변수 첫번째에는 ?의 순번을, 두번째에는 값을 지정

executeUpdate( ) 메서드의 경우, 리턴하는 값이 저장된 행 수이므로 정상적으로 실행될 경우 1을 리턴함

 

→ 총 3개의 정보를 추가해보았고,

    그 결과는 Database Navigator 창 - 테이블명(users)에서 오른쪽 클릭 - [ 데이터 수정 ] - 상단 [ Data ]  클릭해서 확인 

 

 

boards 테이블 : 게시물 정보를 저장해보자.

 

① 매개변수화된 INSERT문을 사용하여 PreparedStatement 객체 이용

String sql = "" +
        "INSERT INTO boards ( " +
                     " btitle, bcontent, bwriter, bdate, bfilename, bfiledata )" +
        "VALUES ( ?, ?, ?, now(), ?, ? ) ; " ;

   ❶ bno의 경우, 'auto_increment'로 자동 증가 컬럼이므로 생략됨

   ❷ bdate의 경우, default 값이므로 ?를 지정할 필요 없음 → now( ) 메서드로 현재 시간을 나타냄

 

② 매개변수화된 INSERT문을 실행하기 위해 PreparedStatement 객체를 얻는데, 이전과는 다르게 두 번째 매개값이 존재함

PreparedStatement pstmt = conn.prepareStatement( sql, Statement.RETURN_GENERATED_KEYS);

 - 두번째 매개변수인 Statement.RETURN_GENERATED_KEYS

    ·  INSERT문이 실행된 후 가져올 키 값으로, 자동 증가된 bno값을 가져옴 

    ·  SQL문이 실행되기 전까지는 bno 값을 모르기 때문에 SQL문이 실행된 후에 bno 컬럼에 실제로 저장된 값을 얻어보는 것

 

③ 매개변수화된 sql문의 ?에 들어갈 값을 지정함

pstmt.setString(1, "눈 오는 날");
pstmt.setString(2, "함박눈이 내려요.");
pstmt.setString(3, "winter");
pstmt.setString(4,"snow.jpg");
pstmt.setBlob( 5, new FileInputStream("src/mariadb/images/snow.jpg"));

 - bfilename 컬럼은 바이너리 타입( blob )

   → ?에 값을 지정하기 위해서는 setBinaryStream( ), setBlob( ), setByte( ) 메서드 중 하나를 이용해야 함

       setBlob( ) 메서드를 이용해서 바이트 입력 스트림을 제공함 → setBlob( ) 메서드의 B는 'Binary'를 의미함

· 파일의 종류
 ❶ 바이너리 파일 : .jpg, .png와 같은 그림 파일 / .mp3와 같은 음악 파일 / .exe와 같은 실행 파일 등이 해당함
     - 사용자 또는 프로그램이 사용하던 정보나 숫자 값을 특별한 가공 없이 그대로 파일에 저장 
     - 바이너리 파일은 데이터를 읽거나 쓸 때 파일 구성 형식에 특별한 조건이 없기 때문에 대부분 데이터의 크기로 판단
        → '20바이트를 읽어라'와 같은 명령 사용 
        ∴ 이런 특징으로 인해 파일의 내용을 확인하기 위해서는 해당 파일을 볼 수 있는 프로그램이 별도로 존재해야 함
 ❷ 텍스트 파일 : 문자를 기반으로 하는 코드 값이 저장된 파일
     - 프로그램이 이 파일의 데이터를 읽거나 쓸 때는 포맷 형식에 따라 데이터의 변환이 일어남

· FileInputStream(매개변수로 파일의 위치가 들어감)
   ※ 파일의 위치는 항상 현재 사용하고 있는 프로젝트를 기준으로! 
   - 파일을 열었을 때, 데이터를 순차적으로 읽어온다는 의미 ( Input를 read로 해석 )
   - 자바는 모든 데이터를 Stream으로 취급 → 모든 데이터가 한 줄로 늘어서있음 
  ( InputStream에 대해서는 다음시간에 더 자세히 다룰 예정 )

 - 파일을 해당 경로에 저장한 후, Package Explorer 창에서 [ Refresh ] 클릭 시, 아래와 같이 images 폴더에 이미지가 업로드됨

 

④ INSERT문 실행 후, 저장된 bno 값 얻기

// SQL문 실행
int rows = pstmt.executeUpdate();
System.out.println("현재 DB에 저장된 행 수 : " + rows);

// SQL문 실행 후, 저장된 bno 값 얻기
if ( rows == 1 ) { 
      ResultSet rs = pstmt.getGeneratedKeys();  

// 만약 값이 있다면
if(rs.next()) {
      int bno = rs.getInt(1); 
      System.out.println("저장된 bno : " + bno);
      rs.close();
}

 

  ❶ 게시물의 정보가 저장되었을 경우 ( = executeUpdate( ) 메서드에 의해 저장된 행 수가 1 ),

     getGeneratedKeys( ) 메서드에 의해 반환타입으로 ResultSet를 얻음

      즉, 마지막에 추가된 primary key인 bno의 컬럼값을 가져옴 

 

❷ 만약 값이 있다면, if문의 블럭 실행

   - boards 테이블에서 첫번째 컬럼인 bno의 컬럼 인덱스 번호를 변수 bno에 저장

   - 저장된 bno 값을 출력하면 현재 boards 테이블에 저장된 bno 값을 알 수 있음

 

package mariadb;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class BoardInsertExampleApp {

	public static void main(String[] args) {
		Connection conn = null;
		
		String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
		String JDBC_URL = "jdbc:mariadb://localhost:3306/thisisjava";
		String USER = "root";
		String PASSWORD = "mariadb";
		
		try {
			Class.forName(JDBC_DRIVER);
			System.out.println("DRIVER 불러오기 성공!");
			conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
			System.out.println("MariaDB 연결 성공!");
			
			// 여기서부터 추가 질의 
			String sql = "" +
					"INSERT INTO boards ( " +
					"       btitle, bcontent, bwriter, bdate, bfilename, bfiledata )" +
					"VALUES ( ?, ?, ?, now(), ?, ? );";
					  	
			// preparedStatement 얻기 및 값 지정
			PreparedStatement pstmt = conn.prepareStatement(
					sql, Statement.RETURN_GENERATED_KEYS);
					
			pstmt.setString(1, "눈 오는 날");
			pstmt.setString(2, "함박눈이 내려요.");
			pstmt.setString(3, "winter");
			pstmt.setString(4,"snow.jpg");
			pstmt.setBlob(
					5, new FileInputStream("src/mariadb/images/snow.jpg"));
			
			// SQL문 실행
			int rows = pstmt.executeUpdate();
			System.out.println("현재 DB에 저장된 행 수 : " + rows);
			
			// 마지막 추가된 레코드의 마지막 primary key 일련번호 ( = bno )
			if ( rows == 1 ) {
				// 현재 DB에 저장된 행 수가 1이므로, 1로 설정함
				ResultSet rs = pstmt.getGeneratedKeys();
				
				// 가장 최근에 추가된 id값을 리턴해줌
				if(rs.next()) {
					int bno = rs.getInt(1);
					System.out.println("저장된 bno : " + bno);
					rs.close();
				}
			}
			
			// PreparedStatement 닫기
			pstmt.close();
			
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
            			// 연결 끊기
				if ( conn != null ) {
					conn.close();
				} 
			} catch(SQLException e) {}	
		}
	}
}

 

출력 결과

+ 추가로 데이터를 저장한 후, 출력 결과

 

'백엔드 > JAVA' 카테고리의 다른 글

게시판 구현 (1)  (0) 2023.06.02
데이터 입출력 (2)  (0) 2023.05.31
JDBC 드라이버 다운로드 및 등록  (0) 2023.05.23
H2 데이터베이스 연결 (2)  (0) 2023.05.22
H2 데이터베이스 연결 (1)  (0) 2023.05.22