본문 바로가기
백엔드/자바

자바 - 24

by study_yeon 2023. 6. 2.

2023.05.31

* crud의 read를 배울 것

* 파일을 순서대로 읽는다 fileinputstream (=read)
- 자바에서 파일은 1바이트 단위로 읽음 

 

* 바이너리파일 : 바이트 단위를 읽기 위함
* 텍스트파일 : 글자를 읽기 위함

* src : 소스코드가 있는 메인 루트(경로)

* .toString(); 을 마지막에 붙이면 문자로 변경

* uri : 통합 자원 식별자

url(주소 고정)보다 큰 개념 - 포함관계
모든 서버를 기준으로 위치

 

* 정규화 될 수록 느려짐
중복을 최소화함 => 관련된 테이블 수가 늘어남


이론

1. 데이터 수정
- String sql에 매개변수화된 UPDATE 문을 저장

String sql = new StringBuilder()
 .append("UPDATE boards SET ")
 .append("btitle= ?, ")
 .append("bcontent= ?, ")
 .append("bfilename= ?, ")
 .append("bfiledata= ? ")
 .append("WHERE bno= ?")
 .toString();

- 매개변수화된 UPDATE 문을 실행하기 위해 prepareStatement() 메소드로부터 PreparedStatement를 얻고, ?에 해당하는 값을 지정

PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "눈사람");
pstmt.setString(2, "눈으로 만든 사람");
pstmt.setString(3, "snowman.jpg");
pstmt.setBlob(4, new FileInputStream("src/ch20/mysql/sec07/snowman.jpg"));
pstmt.setInt(5, 3); // 3번째 게시물 변경


- 값을 모두 지정하였다면 UPDATE 문을 실행하기 위해 executeUpdate() 메소드를 호출

int rows = pstmt.executeUpdate();

 

2. 데이터 삭제

- 매개변수화된 DELETE 문을 String 타입 변수 sql에 대입

// boards 테이블에서 bwriter가 ?인 모든 게시물을 삭제
String sql = "DELETE FROM boards WHERE bwriter= ?";


- 매개변수화된 DELETE 문을 실행하기 위해 prepareStatement ( ) 메소드로부터 PreparedStatement를 얻고 ?에 값을 지정한 후, executeUpdate로 SQL 문을 실행

String sql = "DELETE FROM boards WHERE bwriter= ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "winter");  // 작성자가 winter인 모든 게시물 삭제
int rows = pstmt.executeUpdate();



3. 데이터 읽기
PreparedStatement를 생성할 때 SQL 문이 INSERT, UPDATE, DELETE일 경우에는 executeUpdate() 메소드를 호출
데이터를 가져오는 SELECT 문일 경우에는 executeQuery() 메소드를 호출
executeQuery() 메소드는 가져온 데이터를 ResultSet에 저장하고 리턴

* ResultSet의 특징

커서cursor(행을 가리키는 포인터)가 있는 행의 데이터만 읽을 수 있다. 
ResultSet은 실제 가져온 데이터 행의 앞과 뒤에 beforeFirst(최초 커서) 행과 afterLast 행이 붙어서 
첫 번째 데이터 행을 읽으려면 next()를 활용하여 커서를 이동시켜야 한다. 
next() : 커서를 다음 행으로 이동

 

3-1. 데이터 행 읽기
- 커서가 있는 데이터 행에서 각 컬럼의 값은 Getter 메소드로 읽을 수 있다.

- 컬럼의 데이터 타입에 따라서 getXxx() 메소드가 사용되며, 매개값으로 컬럼의 이름 또는 컬럼 순번을 줄 수 있다.

- ResultSet에서 컬럼 순번은 1부터 시작

// 컬럼 이름
String userId = rs.getString("userid");
String userName = rs.getString("username");
int userAge = rs.getInt("userage");

// 컬럼 순번
String userId = rs.getString(1);
String userName = rs.getString(2);
int userAge = rs.getInt(3);

@Data  어노테이션을 사용하기 위해 롬복 설치하기

2023.06.01 - [백엔드/자바] - 자바 - 24 (lombok설치하기) 

 

3-2. 사용자 정보 읽기
▷ users 테이블의 한 개의 행(사용자)을 저장할 User 클래스 작성

- @Data 어노테이션을 이용해서 Getter, Setter, toString() 메소드를 자동 생성

- 어노테이션 : 특수한 기능을 실행하는 자바의 실행주석

package mariadb;

import lombok.Data;

@Data // 롬복 라이브러리에의해 생성자 setter/getter메소드가 만들어짐
public class User {
	// 사실상 DTO
	private String userId;
	private String userName;
	private String userPassword;
	private int    userAge;
	private String userEmail;

}

※ 외부에서 접근할 수 있는 속성함수(getter/setter)작성
1. 직접 입력
2. 이클립스의 자동완성(sourse/generate...getter|setter) 메뉴 이용
3. 롬복(lombok)이라는 개발 지원 툴 라이브러리를 이용
- 프로젝트 개발 실행시 자동생성

- @Data : 롬복 라이브러리에의해 Constructor(기본생성자), Getter, Setter, hashCode(), equals(), toString() 자동 생성
- 소스코드에서는 안보이지만 실제로 컴파일된 결과물(.class)에는 코드가 생성되어 있음

 

 테이블에서 userid가 winter인 사용자의 정보를 가져와 출력하기

package mariadb;

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

public class UserSelectExampleApp {

	public static void main(String[] args) {
		Connection conn = null;
		
		String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
		String JDBC_URL = "jdbc:mariadb://localhost/thisisjava"; // 기본포트번호는 생략 가능
		String USER = "root";
		String PASSWORD = "mariadb";
		
		try {
			// 드라이버 찾기
			Class.forName(JDBC_DRIVER);
			
			// 마리아디비와 연결
			conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
			
			String sql = "" + 
				"SELECT userid, username, userpassword, " + 
				"		userage, useremail " + 
				"FROM users " +
				"WHERE userid = ?; ";
			
			// preparedStatement 얻기 및 값인수 지정
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			// SQL 실행 후 결과 얻어오기(ResultSet 반환)
			ResultSet rs = pstmt.executeQuery();
			
			// 한개의 레코드만 가져왔다고 가정
			if (rs.next()) { // 결과 데이터(사용자 아이디) 있음
				// DTO 클래스 사용
				User user = new User();
				user.setUserId( rs.getString("userid") ); // 컬럼 이름으로 읽기
				user.setUserName( rs.getString("username") );
				user.setUserPassword( rs.getString("userpassword") );
				user.setUserAge( rs.getInt(4) ); // 컬럼순번 이용
				user.setUserEmail( rs.getString(5) ); 
				
				System.out.println( user );
				
			} else { // 결과 데이터가 없음
				System.out.println("사용자 아이디가 존재하지 않습니다.");
			}
			
		} catch(ClassNotFoundException e) {
			e.printStackTrace();
			
		} catch(SQLException e) {
			e.printStackTrace();
			
		} catch(Exception e) {
			e.printStackTrace();
			
		} finally {
			try {
				if ( conn != null ) {
					// 연결 끊기	
					conn.close();
				}
			} catch(Exception e) {
	
			}
			
		}

	}
	
}

롬복 효과

- Date는 import util로 하기 

 

3-3 게시물정보읽기
▷ boards 테이블에 bwriter를 winter로 하는 게시물을 1개 저장할 Board 클래스(DTO)

- 컬럼 개수와 타입에 맞게 필드 선언

- Data 어노테이션 활용

package mariadb;

import java.sql.Blob;
import java.util.Date; // 유틸임

import lombok.Data;

// DTO / VO 클래스 
// Data 클래스
@Data
public class Board {
	private int bno;
	private String btitle;
	private String bcontent;
	private String bwriter;
	private Date bdate;
	private String bfilename; 
	private Blob bfiledata;

}

 boards 테이블에서 bwriter가 winter인 게시물 정보를 가져오는 전체 코드

package mariadb;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class BoardSelectExampleApp {

	public static void main(String[] args) {
		
		Connection conn = null;
		String JDBC_DRIVER = "org.mariadb.jdbc.Driver";
		String JDBC_URL = "jdbc:mariadb://localhost/thisisjava";
		String USER = "root";
		String PASSWORD = "mariadb";
		
		try {
			Class.forName(JDBC_DRIVER);
		
			conn = DriverManager.getConnection(JDBC_URL, USER, PASSWORD);
			
			// 마리아디비서버에 질의할 SELECT SQL 정의
			String sql = "" +
				"SELECT bno, btitle, bcontent, bwriter, bdate, bfilename, bfiledata " + 
				"FROM boards " + 
				"WHERE bwriter = ?; ";
			
			// 명령객체 생성 및 쿼리
			PreparedStatement pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, "winter");
			
			// 쿼리 실행 및 결과 얻기
			ResultSet rs = pstmt.executeQuery();
			
			while(rs.next()) {
				// 데이터행을 읽고 각 컬럼값을 대입하여 DTO board 객체 생성
				Board board = new Board();
				board.setBno(rs.getInt("bno")); // set : 데이터 넣기
				board.setBtitle(rs.getString("btitle")); 
				board.setBcontent(rs.getString("bcontent"));
				board.setBwriter(rs.getString("bwriter"));
				board.setBdate(rs.getDate("bdate"));
				board.setBfilename(rs.getString("bfilename"));
				board.setBfiledata(rs.getBlob("bfiledata")); // 파일은 2진데이터
				
				// 콘솔 출력
				System.out.println(board);
				
				// board 객체의 bfiledata를 파일로 저장
				// board DTO객체의 Blob타입 bfiledata의 참조주소를 blob변수에 저장
				// 참조라는 것은 원본데이터를 직접 접근할 수 있다
				// 새로운 변수를 만들면 접근경로가 짧아 접근이 쉬워짐(안하면 .appends와 같이 활용해야함)
				Blob blob = board.getBfiledata();
				
				if(blob != null) {
					// getBinaryStream()은 blob객체로부터 읽어온다(=read) = 입력한다(=input)
					InputStream is = blob.getBinaryStream(); // 운영체제(JVM)에 도움을 받아 파일로 저장됨
					OutputStream os = new FileOutputStream(  // 업캐스팅, 1바이트씩 넘겨주지만 buffer됨
							"c:/Temp/" + board.getBfilename()); // 저장될 경로
					is.transferTo(os); // 인풋에서 아웃풋으로 흘러나간다
					os.flush(); // 저장공간이 남아있어도 강제저장
					os.close();
					is.close();	
				}
				rs.close();
				
				pstmt.close();
			}
			
		} catch(Exception e) { // 일일히 다 적어주지 않고 Exception로 일괄해도 됨
			e.printStackTrace();
			
		} finally {
			if( conn != null ) {
				try {
					conn.close();
				} catch (Exception e) { 
				
				}
			} 
		}
	}

}

* Blob 바이너리 타입 
메모리에 2~4기가 정도 확보

Blob blob = board.getBfiledata();

-> board의 blob 객체를 다이렉트로 가르키기 위함
blob 힙(메모리)의 주소가 저장됨

* FileOutputStream : 이미지의 데이터사이즈만큼 읽어서 파일로 저장 
들어오는 입구가 메모리, 나가는 출력 흐름이 파일임

* FileInputStream 
들어오는 입구가 파일, 나가는 출력 흐름이 이미지임

 

* 메모리와 아웃풋 연결
is.transferTo(os); 


▷ boards 테이블에 bwriter를 winter로 하는 게시물을 2개 이상 저장

- Borad :  Data 클래스,  boards 테이블의 1개 행(게시물)을 저장할 클래스
- BoardSelectExampleApp : boards 테이블에서 bwriter가 winter인 게시물 정보를 가져오는 전체 코드

 

1. BoardInsertExampleApp 여러번 실행
- sql 편집기 열어서 확인

 

2. sql 편집기 열기 > 원하는 테이블 위에서 오른쪽 마우스 > SQL 생성 > INSERT문 복사 > 자바(sts)에서 sql명령문 작성하기


● Stream 
- 시작(입력방향)과 끝(출력방향)이 있는 순차적 흐름 
- 스트림은 다른스트림의 입력(출력)스트림이 될 수 있다

* InputStream 
바이트단위로 처리(느림)

나를 중심으로 내 쪽으로 화살표
Buffer에 담아 처리
Buffer : 일정공간을 채워 한번에 처리
파일로 데이터를 읽는 작업은 오래걸리므로 Buffer 활용

* OutputStream

바이트단위로 처리(느림)
나를 중심으로 내 바깥 쪽으로 화살표

 

인풋 : 읽어라
아웃풋 : 저장해라


* DB에 있는 메모리를 영구저장을 하기 위해서는 파일로 만들어야함

* Blob (Binary Large Object)
- 바이너리 형태로 큰 객체를 저장할 것이라는 것
- 큰 객체라는 것은 주로 이미지, 비디오, 사운드 등과 같은 멀티미디어 객체

- Blob 객체는 파일류의 불변하는 미가공 데이터. 
- 텍스트와 이진 데이터의 형태로 읽을 수 있으며, ReadableStream으로 변환한 후 스트림 메서드를 사용해 데이터를 처리할 수도 있다
- blob의 경우 4GB의 이진 데이터를 저장할 수 있다. 하지만 이건 DB에 직접 저장하는 것이 아니라 DB에는 Large Object의 위치 포인터만 저장


▶ 폴더 없을 시 폴더 생성

String folderPath = "c:/temp/";
String filepath = folderPath + board.getBfilename();
File folder = new File(folderPath);
					
if(!folder.exists()) {
	try {
		folder.mkdir(); // 폴더를 생성
		System.out.println("폴더를 생성했습니다.");
	} catch(Exception e) {
			e.printStackTrace();
	}
}





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

자바 - 26 (Stream)  (0) 2023.06.02
자바 - 25 (트랜잭션)  (0) 2023.06.02
자바 - 24 (lombok설치하기)  (0) 2023.06.01
자바 - 23  (0) 2023.06.01
자바 - 22 (List - 리스트)  (0) 2023.05.31