1. ResultSetMetaData 

- data dictionary를 사용하지 않고, 실행되는 select 쿼리에 해당하는 컬럼정보를 얻을 때 사용하는 ineterface

- 조회된 inline view에 한하여 컬럼정보 조회 가능

- DBMS가 달라도 정보를 얻을 수 있음

   > Oracle의 user_cons_columns ,,, 등과 같은 다양한 data dictionary는 oracle DB에서만 존재/사용 가능

- ResultSet을 기반으로 생성되며, ResultSet은 연결 끊어야 하나 ResultSetMetadata는 연결 안 끊어도 됨

- 사용법) 

> ResultSet에서 ResultSetMetaData를 얻기 : ResultSetMetaData rsmd = getMetaData();

> 정보 얻기

  • 컬럼의 개수 : rsmd.getColumnCount()
  • 컬럼명 : rsmd.getColumnName(컬럼 인덱스) or getColumnLabel (oracle의 컬럼 index는 1부터 시작)
  • 컬럼 데이터형명 : rsmd.getColumnTypeName(컬럼의 인덱스)
  • 컬럼 데이터형크기 : rsmd.getPrecision(컬럼의 인덱스)
  • null 허용 : rsmd.isnullable(컬럼의 인덱스) // not null이면 0 null 허용이면 1

 

 

 2. Transaction 처리 

- 데이터베이스의 작업 단위 (insert, update, delete)

- 작업이 완료될 때 commit, 작업을 취소할 때 rollback 사용 

- java에서는 Connection Interface가 Transaction을 처리

    > Transaction 대상쿼리문 하나 당 작업완료 (auto commit)

- auto commit 문제 : 

    > Transaction 대상 쿼리문 하나로 작업이 완료되면 auto commit은 문제를 발생시키지 않지만,

       여러 개의 쿼리문이 하나의 Transaction을 구성하면 문제를 발생시킬 수 있음

    > 여러 개의 작업이 모두 성공했을 때에만 DB작업이 완료되어야 하고,

       여러 개의 작업 중 하나라도 실패하면 모든 DB작업이 취소되어야 함 

    > 여러 쿼리가 있을 때 Transaction으로 관리하자

- 작업방법)

1) Connection의 auto commit을 해제

     con.setAutoCommit(false) ;  // 개발자가 transaction 완료 후 commit 또는 rollback 해야 함

    *주의 : method 안에서 Connection을 종료하면 commit이 된 후 Connection이 종료되므로,

                 연결 끊기는 DB작업 method 밖에서 끊음 

     (Connection을 DB작업 method 외부에서 선언 <- method가 2개로 분리됨)

2) 쿼리문 작업 외부에 Connection 선언 

3) DB작업 method가 쿼리 수행 행수를 반환

4) DB작업 method가 개발자가 목표로 한 레코드 수를 반환했을 때에만 commit 또는 rollback을 수행하고 연결을 끊음

 

- rollback 처리시 차이점:

1) update/delete의 경우 : 쿼리문 수행 실패 시 0건 수행이므로 else 부분에서 rollback 처리

2) insert의 경우 : 쿼리문 수행 실패 시 SQLException 발생하므로 catch 부분에서 rollback 처리

 

 


# 오늘의 코딩 #

- Transaction을 처리해보자

- 여러 쿼리문을 실행하고 모두 수행되었을 경우에만 commit!

 

# DB 구성은 아래와 같다!

- TRANSACTION2 테이블의 주소컬럼의 크기를 작게 설정하여 INSERT 되지 않을 경우/될 경우를 TEST 예정

TRANSACTION1
TRANSACTION2

 

# Transaction Test할 클래스

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

import test.dao.DbConnection;

/**
 * 쿼리문 여러개로 하나의 Transaction이 구성되는 경우의 처리
 * @author user
 */
 
public class TestTransaction {

	/**
	 * transaction을 처리할 쿼리문을 실행하고, 결과를 받아와서 transaction을 완료하는 method
	 */
	@SuppressWarnings("resource")
	public void transaction() {
		Connection con = null;
		try {
			// 2. 커넥션 얻기
			con = DbConnection.getInstance().getConn();
			// autocommit 해제
			con.setAutoCommit(false);
			// 3. 쿼리문 수행 후 결과 얻기
			int cnt = sqlJob(con); // 여러개의 쿼리문이 하나의 transaction을 구성
			// 쿼리문이 실행된 후 목표로 한 행수가 나오면 commit 아니면 rollback
			if (cnt == 2) {
				con.commit(); }// 트랜잭션 완료
		} catch (SQLException e) {
			try {
				 // insert로 트랜잭션이 구성되는 경우 catch 에서 rollback을 처리함
				con.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}//end catch
			e.printStackTrace();
		} finally {
			try {
				if (con != null) {
					con.close();
				} // end if
			} catch (SQLException e) {
				e.printStackTrace();
			} // end catch
		} // end finally
	}// transaction

	/**
	 * Connection을 받아, DB작업을 수행하는 method<br>
	 * transaction1 테이블과 transaction2 테이블에 이름과 주소를 입력하여 추가된 행수를 반환하는 일
	 * 
	 * @param con
	 * @return 쿼리문을 수행한 결과 횟수 (총합)
	 * @throws SQLException
	 */
	public int sqlJob(Connection con) throws SQLException {
		int allCnt = 0;  // 쿼리문 수행 성공 횟수를 담을 변수

		String name = "김동동";
		String addr = "서울시 동작구";

		// 쿼리문 수행객체 얻기
		// transaction1 테이블에 작업
		String insert1 = "insert into transaction1(name,addr) values (?,?)";
		PreparedStatement pstmt = con.prepareStatement(insert1);
		pstmt.setString(1, name);
		pstmt.setString(2, addr);

		int cnt1 = pstmt.executeUpdate();

		// transaction2 테이블에 작업
		String insert2 = "insert into transaction2(name,addr) values (?,?)";
		PreparedStatement pstmt2 = con.prepareStatement(insert2);
		pstmt2.setString(1, name);
		pstmt2.setString(2, addr);

		int cnt2 = pstmt2.executeUpdate();

		allCnt = cnt1 + cnt2;

		return allCnt;
	}// sqlJob

	public static void main(String[] args) {
		TestTransaction tt = new TestTransaction();
		tt.transaction();
	}// main

}// class

 

# 출력 결과 #

1) INSERT 정상 처리 (이름 : 김동동 / 주소 : 서울)

- 두 테이블 모두 정상 적으로 INSERT 처리되어 커밋 됨!

2) 오류 발생 시 에러 메시지(이름 : 김동동 / 주소 : 서울시 동작구)

- 모든 트랜잭션이 처리되지 않으면 커밋되지 않고 에러 발생!

 

 

 

 DAO 

- 실질적으로 DB에 접근하는 객체

- DB를 사용해 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 오브젝트 (보통 singleton 패턴으로 만듦)

 

 


# 오늘의 코딩 #

- 이번엔 SQL Injection이 발생하지 않는 PreparedStatement 활용해보자

- Singleton으로 만들어서 하나의 객체만 생성되고 그 객체가 DB와 연동, Login을 수행해보자

 

# Login VO 클래스

- Login 사용 클래스에서 설정한 ID와 PW를 저장하고 반환해줄 클래스

public class LoginVO {

	private String id, pass;

	public LoginVO() {
		super();
	}

	public LoginVO(String id, String pass) {
		this.id = id;
		this.pass = pass;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getPass() {
		return pass;
	}

	public void setPass(String pass) {
		this.pass = pass;
	}

	@Override
	public String toString() {
		return "LoginVO [id=" + id + ", pass=" + pass + "]";
	}
	
}

 

# Login DAO 클래스

- DB와 연결되어 일을 할 클래스

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

import test.dao.DbConnection; 

public class LoginDAO {
	
	public String usePreparedStatement(LoginVO loginVO) throws SQLException {
		String name =""; 
		
		Connection con = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		DbConnection db = DbConnection.getInstance();
		try {
		//1.드라이버로딩 & 2.Connection얻기 (중복된 일이라 따로 method 만들어놨음)
			con = db.getConn();
		//3.쿼리문 생성 객체 얻기
			StringBuilder selectName = new StringBuilder();
			selectName
			.append("	select	name ")
			.append("	from 	test_login	")
			.append("	where id = ? and pass = ? ");  // ? : 바인드 변수!!
			
			pstmt = con.prepareStatement(selectName.toString());
		//4.바인드 변수에 값 설정 (VO Class에서 가져오기)
			pstmt.setString(1, loginVO.getId());
			pstmt.setString(2, loginVO.getPass());
		//5.쿼리문 수행 후 결과 얻기
			rs = pstmt.executeQuery();
			
			if(rs.next()) {//입력된 id와 p/w에 일치하는 레코드가 존재
				name = rs.getString("name"); // 조회된 이름 가져와서 변수에 할당
			}//end if
			
		}finally{
		//6.연결 끊기 (얘도 method 만들어놨음)
			db.closeDB(rs, pstmt, con);
		}//end finally
		
		return name;
	}//usePreparedStatement
	
}//class

 

# Login을 수행할 클래스

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.sql.SQLException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;

@SuppressWarnings("serial")
public class TestLogin extends JFrame implements ActionListener {

	private JTextField jtfId;
	private JPasswordField jpfPass;
	private JLabel jlblOutput;
	
	public TestLogin() { 
		super("로그인");
		jtfId=new JTextField();
		jpfPass=new JPasswordField();
		jlblOutput=new JLabel("출력");
		
		jtfId.setBorder(new TitledBorder("아이디"));
		jpfPass.setBorder(new TitledBorder("비밀번호"));
		jlblOutput.setBorder(new TitledBorder("결과"));
		
		jpfPass.addActionListener(this);
		
		setLayout(new GridLayout(3,1));
		
		add(jtfId);
		add(jpfPass);
		add(jlblOutput);
	
		// 종료
		addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				dispose();
			}//windowClosing

			@Override
			public void windowClosed(WindowEvent e) {
				System.exit(JFrame.ABORT);
			}//windowClosed
		});
		setBounds(100, 100, 250, 300);
		setVisible(true);
	}//TestLogin
	
	public void usePreparedStatement() {
		LoginDAO ld = new LoginDAO();
		
		//아이디와 비밀번호를 받자
		LoginVO lv = new LoginVO(jtfId.getText(), new String(jpfPass.getPassword()));
		
		try {
			String name = ld.usePreparedStatement(lv);
			
			if("".equals(name)) {//이름이 조회되지 않음
				JOptionPane.showMessageDialog(this, "아이디나 비밀번호를 확인해주세요.");
				return;
			}//end if
			
			jlblOutput.setText(name + "님 반갑습니다.");
			
		} catch (SQLException e) {
			JOptionPane.showMessageDialog(this, "죄송합니다.");
			e.printStackTrace();
		}//end catch
		
	}//usePreparedStatement
	
	@Override
	public void actionPerformed(ActionEvent e) {
		usePreparedStatement(); // SQLInjection 공격불가
	}//actionPerformed

	public static void main(String[] args) {
		new TestLogin();
	}//main

}//class

 

# 출력 결과 #

(기존 DB 데이터의 ID와 비밀번호는 각각 WOO/4321)

 

> 로그인 성공시 화면

 

> 로그인 실패시 화면

 

 Singleton Pattern 

- JVM에서 하나의 인스턴스를 생성하고 사용하는 개발 패턴 (Design pattern)

- 메모리를 절감할 수 있음

- 참조하는 속도가 빠름

- Framework에서 주로 객체를 생성하고 사용하는 방식

- 아래 작성법이 class안에 들어가있으면 singleton 패턴인 클래스 라고 함

- Ex) Calendar Class의 getInstance()

- 작성법)

작성 코드
1. 객체화를 클래스 안에서만 할 수 있도록 작성함
 => 생성자의 접근지정자를 private로 지정

2.  객체를 생성하여 반환하는 일을 하는 method 작성
 => public static 클래스명 getInstance() {

3. 객체를 하나로 유지하고 반환하는 일.
      if ( 객체 == null ) { 객체 = new 생성자();  }
                return 객체;
    }
class Test{
        private static Test t;
        private Test(){
         }
       
        public static Test getInstance(){
                 if (t == null) {
                     t = new Test();  }
                 return t;       }    
        }

 


 

# 예시 #

- 싱글톤 패턴의 클래스

/**
 * 싱글톤 패턴으로 만드는 클래스 : 객체가 하나로만 사용되는 클래스
 * @author user
 */
public class Singleton {

	private static Singleton single;
	private Singleton() { // 접근지정자 private으로 클래스 외부에서 객체화 불가
		
	}// Singleton

	/**
	 * 생성된 객체를 얻기 위한 method
	 * @return
	 */
	public static Singleton getInstance() {
		// 객체를 하나로 생성 관리할 수 있는 코드
		if (single == null) {
			single = new Singleton();
		}//end if
		
		return single;
	}// getInstance

}// class

 

- 싱글톤 클래스 사용

public class UseSingleton {

	public static void main(String[] args) {
		//생성자가 private이기 때문에 클래스 외부에서 객체화 될 수 없음
//		Singleton single = new Singleton(); // 생성자가 보이지 않아서 생성 불가! getInstance 사용해야함
		
		//getInstance method를 통해서만 객체를얻음
		Singleton single = Singleton.getInstance();
		Singleton single2 = Singleton.getInstance();
		Singleton single3 = Singleton.getInstance();
		
		//몇개를 생성하든 객체는 하나이므로 주소는 똑같당
		System.out.println(single);
		System.out.println(single2);
		System.out.println(single3);
		
		//일반 class는 객체 생성 횟수만큼 heap 주소가 할당됨!
		UseStatement us = new UseStatement();
		UseStatement us2 = new UseStatement();
		UseStatement us3 = new UseStatement();
		
		System.out.println(us);
		System.out.println(us2);
		System.out.println(us3);
		
	}//main

}//class

 

# 출력 결과 #

 

> 객체를 하나만 생성해서 사용할 때 !! 

DB 연동 작업 시 한 객체만 DB에 접근/조작할 수 있도록 할 수 있음

 람다식(Lambda) 

- JDK 1.8에서 추가된 문법

- FunctionallInterface를 편하게 사용할 수 있는 문법

   > FunctionallInterface : abstract method를 하나만 가진 인터페이스

- interface를 구현한 클래스를 작성하거나, anonymous inner class를 만들지 않고 사용할 수 있는 방법

- 문법)   인터페이스명 객체명 = (매개변수,,,) -> { 코드 } ;

 

* interface위에 @FunctionallInterface라는 annotaion을 설정하면

  해당 인터페이스는 추상 method를 반드시 하나만 가져야 함!

 


# 오늘의 코딩 #

- 람다식을 알아보쟈!

 

1) 매개변수가 없는 인터페이스

@FunctionalInterface  // 추상method를 반드시 하나만 가지는 interface가 작성되어야 함
public interface TestLambda {

	public void method();
//	public void method1(); //error!@FunctionalInterface 가 들어가면 추상메소드가 하나여야함
	
}

2) 매개변수가 있는 인터페이스

@FunctionalInterface  // 추상method를 반드시 하나만 가지는 interface가 작성되어야 함
public interface TestLambda2 {
	//매개변수가 있는 abstract method
	public void method(int i, String str);
	
}

3) 매개변수와 반환형이 있는 인터페이스

@FunctionalInterface  // 추상method를 반드시 하나만 가지는 interface가 작성되어야 함
public interface TestLambda3 {
	//매개변수와 반환형이 있는 abstract method
	public String luckNum(String name);
	
}

4) 1,2,3 인터페이스를 사용할 class!

import java.util.Random;

/**
 * 람다식의 사용
 */
public class UseLambda {

	public static void main(String[] args) {
		/////////// 매개변수가 없는 abstarct method////////////////
		// 1. 매개변수가 없는 abstract method
		TestLambda tl = () -> {
			System.out.println("추상 method를 구현");
		};
		// 2. 호출
		tl.method();

		/////////// 매개변수가 있는 abstarct method////////////////
		// 1. 매개변수가 있는 abstract method
		TestLambda2 tl2 = (int i, String msg) -> {
			System.out.println("매개변수있는 추상 method를 구현");
			System.out.println("i=" + i + ",msg=" + msg);
		};
		// 2.호출
		tl2.method(2021, "오늘은 날이 좋네요!");

		/////////// 반환형이 있는 abstarct method////////////////
		TestLambda3 tl3 = (String name) -> {
			Random random = new Random();
			int num = random.nextInt(101);
			return name + "님의 행운의 번호 : " + num;
		};

		//호출
		String msg = tl3.luckNum("동동"); 
		System.out.println(msg);
	}// main

}// class

 

# 출력 결과 #

 

-- functionallInterface의 경우에는 lambda식이 정말 편한 방법인 것 같다 

 1. charset 

- 컴퓨터에서 문자를 사용하기 위해 미리 정의해둔 문자 테이블

- 종류 : 완성형 charset, 조합형 charset  // 조합형 charset은 지금 없어짐

- 조합형 : 자음과 모음을 조합하여 글자를 생성, 모든 한글을 표현할 수 있음 (예전 아래아한글이 제공)

- 완성형 : 모든 글자를 만들어 두고 사용하는 charset, charset에 존재하지 않는 문자는 표기할 수 없음

   

*  1byte charset, 2byte charset, 3byte charset이 있음

  • 1byte charset : 8859_1 => 영어(대,소), 숫자, 특문
  • 2byte charset : EUC-KR(국제표준), KSC5601(국내표준-X), MS949(마이크로소프트사-기업) => 영어(대,소), 숫자, 특문, 한글
  • 3byte charset : UTF-8(국제표준) => 영어(대,소), 숫자, 특문, 한글 => 국제 표준인 UTF-8을 많이 씀

 

 2. early return 

- method의 반환형이 void인 경우 값을 가지지 않은 return을 사용할 수 있음

- 괄호 갯수가 줄어서 가독성이 향상됨

- Ex. if~else문의 경우, if가 엄청나게 길때 사용하면 좋음!!

  기존 구문의 경우)

            return 값;

           -> 반환형이 존재할 때 사용하는 return, 값을 내보내는 일

  early return의 경우)

            return ;

           ->  반환형이 존재하지 않을 때 사용하는 return - 아랫줄의 코드를 실행하지 않고 호출한 곳으로 돌아가는 일

~전편에 이어서~

ObjectStream을 활용해보자!

 


# 오늘의 코딩 #

[순서]

1. 먼저 내보낼 객체의 기반이 되는 'MyData' Class를 생성 (Serializable interface를 구현한 클래스) 

2. ObjectStream활용을 위한 클래스를 생성하여 write와 read method를 작성/실행해보자

 

#1. MyData Class

- 사용자 정보를 가지고 있으며 직렬화가 막혀있는 변수가 존재함

package test;

import java.io.Serializable;

/**
 * 사용자의 정보를 가진 클래스
 * @author user
 */
public class MyData implements Serializable{
		
	/**
	 * serialVersionUID : 이 클래스 파일이 JVM 외부로 빠져나갔을 때 파일을 검증하기 위한 용도의 Constant
	 * 객체가 JVM 외부로 나갔을 때 검증하기 위해서 ID를 부여함
	 * 1. 현재 JVM에서 빠져나간 객체임을 검증 가능
	 * 2. 시간에 대한 체크 가능
	 */
	private static final long serialVersionUID = -6444648902030740467L;
	private double height;
	private transient double weight; // 직렬화를 막기 위해서는 transient를 걸자 (직렬화 방지)
	private int age;
	private transient String name; // 직렬화 방지
	
	public MyData() {
	}

	public MyData(double height, double weight, int age, String name) {
		this.height = height;
		this.weight = weight;
		this.age = age;
		this.name = name;
	}

	public double getHeight() {
		return height;
	}

	public void setHeight(double height) {
		this.height = height;
	}

	public double getWeight() {
		return weight;
	}

	public void setWeight(double weight) {
		this.weight = weight;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "MyData [height=" + height + ", weight=" + weight + ", age=" + age + ", name=" + name + "]";
	}
	
}//class

 

#2. UseObjectStream Class

1) MyData Class를 활용하여 객체 내보내기/읽기 Stream을 실행하는 class

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 객체를 직렬화하여 JVM외부로 내보내거나 읽어들이는 클래스
 */
public class UseObjectStream {

	public void writeObj(MyData md) throws IOException, NotSerializableException {
		// 1. 스트림 생성
		ObjectOutputStream oos = null;
		try {
			oos = new ObjectOutputStream(new FileOutputStream(new File("C:/Users/user/Desktop/obj.txt")));
			// 2. 객체 스트림에 기록
			oos.writeObject(md);
			// 3. 스트림의 내용 분출
			oos.flush();
		} finally {
			// 4. 연결 끊기
			if (oos != null) {
				oos.close();
			}//end if
		}//end finally
	}// writeObj

	public MyData readObj() throws IOException, ClassNotFoundException {
		MyData md = null;

		// 1. 스트림 생성
		ObjectInputStream ois = null;
		try {
			ois = new ObjectInputStream(new FileInputStream(new File("C:/Users/user/Desktop/obj.txt")));
			// 2. 스트림에서 객체 읽기
			md = (MyData) ois.readObject(); // method 반환형이 object이므로 클래스로 casting 해줘야 값을 받아올 수 있음
		} finally {
			// 3. 연결 끊기
			if (ois != null) {
				ois.close();
			} // end if
		} // end fianlly
		return md;
	}// readObj

 

2) main maethod에서 실행해보자!! writeObj를 먼저 실행하면!

	public static void main(String[] args) {

		MyData md = new MyData(180.3, 73.2, 26, "김동동");
		UseObjectStream uos = new UseObjectStream();

		try {
			uos.writeObj(md);
			System.out.println("직렬화된 객체:" + md);
		} catch (NotSerializableException nse) {
			System.out.println("객체가 직렬화되지 않습니다.");
			nse.printStackTrace();
		} catch (IOException ie) {
			System.out.println("객체를 쓰는 동안 문제 발생");
			ie.printStackTrace();
		} // end catch

	}// main

}// class

# 출력 결과 #

- JVM외부로 나간 객체의 내용 & 설정한 경로에 작성된 파일

 

 

3) readObj method도 실행해보자!

		try {
			MyData md1 = uos.readObj();
			System.out.println("역직렬화된 객체:" + md1);
		} catch (ClassNotFoundException cnfe) {
			cnfe.printStackTrace();
		} catch (IOException ie) {
			ie.printStackTrace();
		} // end catch
		
	}// main

}// class

 

# 출력 결과 #

- MyData Class에서 직렬화 방지를 위해 transient로 막아둔 변수는 null로 읽힘 

  >> 해당 변수는 JVM외부로 나가지 않았다라는 것 확인!

 

 1. 직렬화 (Serializable) 

객체를 일정 크기로 쪼개는 것.

- 단, 모든 객체는 직렬화되지 않음 (직렬화 되어져야 하는 객체는 java.io.Serivalizable 인터페이스를 구현한 클래스만 가능)

- Serializable 인터페이스를 구현한 클래스로부터 생성된 instance는 일정 크기로 쪼개질 수 있음

 

* Serializable 인터페이스는 Constant와 abstract method가 없음

   - 목적 : 데이터형에 체크 (is a 관계에 대한 확인용)

   - interface에 상수/추상메소드가 없고 -able로 명명되어있으면 특정한 용도를 확인하기 위한 interface인 경우가 많다!

 

 2. transient 

- 직렬화를 막는 키워드로 변수의 접근지정자 종류

  > 객체가 가진 중요한 정보를 JVM안에서만 사용하도록 만들 수 있음 (JVM밖으로 못 나감)

- 문법)

public class Test implements Serializable{

           int i; // 직렬화 가능

           String str; // 직렬화 가능 (String이 serializable을 implements함)

           transient String str1; // serializable보다 transient가 우선되어 직렬화 막기 가능

           private transient int j; // 직렬화 막기 }

 

 3. ObectStream 

- JVM에 생성된 instance(객체)를 JVM 외부로 내보내거나,

  JVM외부에 존재하는 객체를 JVM내부로 읽어 들일 때 사용하는 Stream

- 기본형 데이터형은 JVM외부로 나갈 수 있으나 instance는 JVM외부로 나갈 수 없음

  > 기본형 데이터형은 크기가 정해져 있어서 쪼갤 수 있음

  > 인스턴스는 클래스가 Serializable 인터페이스를 구현해야 직렬화 가능

- ObjectInputStream, ObjectOutputStream 사용 (8bit Stream)

 

 4. serialVersionUID 

- 이 클래스 파일이 JVM 외부로 빠져나갔을 때 파일을 검증하기 위한 용도의 Constant

- 용도 1) 현재 JVM에서 빠져나간 객체임을 검증 가능

  용도 2) 시간에 대한 체크 가능 (만약 내가 1년뒤에 ID 번호를 바꾸면 읽어들일 때 오류 발생!!)

- 클래스는 직렬화되어서 JVM밖으로 나갔다가 조작될 수 있는 위험(보안상의 위험)이 있기 때문에

  JVM이 serial warinng을 띄움

  > 이를 검증하기 위해 serialVersionUID를 거는 것이다!

- @SuppressWarnings(“serial”) 어노테이션 처리를 해도 되지만 ID부여가 안전함

 

- EX)

private static final long serialVersionUID = -8401830166169521484L; // 내보냈을 때 기존 ID

private static final long serialVersionUID = -8401830166169521485L; // 바뀐 ID

 

이후 .readObject();를 시행하면 아래와 같은 [InvalidClassException]이 발생함!

 

// 콘솔창 오류 내용 :
java.io.InvalidClassException: day0818.MyData; local class incompatible: stream classdesc serialVersionUID = -8401830166169521484, local class serialVersionUID = -8401830166169521485

(ID가 바뀌어서 유효한 클래스가 아니라는 의미! -> 밖에 있는 직렬화된 클래스가 내부로 들어오지 못함)

 

 

 

코딩은 다음편에!

~전편에 이어서 2~

오늘은 Stream으로 파일 쓰기!

 

 1. 8bit Stream 활용 

- 전 편에서 사용했던 java_test.txt 파일을 읽어서 새로운 파일을 써보자

- test라는 글자가 적혀있음

- FileInputStream과 FileOutputStream을 사용하면 됨

- 파일 복사도 1byte씩 읽어들인 것을 다른 파일에 flush하면서 진행됨 

  >> byte의 array를 만들어서 1byte가 아니라 더 많은 byte를 읽어들이면 파일 복사 효율 증대

 

# 코딩 #

- 이전에 만들었던 클래스에서 이어서 method 작성!

	/**
	 * 8bit Stream 사용하여 text 파일 쓰기
	 * 
	 * @throws IOException
	 */
	public void usestream3() throws IOException {
		File inFile = new File("C:/Users/user/Desktop/java_test.txt"); // 읽어올 파일
		File outFile = new File("C:/Users/user/Desktop/java_output_test.txt"); // 작성할 파일

		FileInputStream fis = new FileInputStream(inFile); // 파일에 스트림 연결
		FileOutputStream fos = new FileOutputStream(outFile); // 파일에 스트림 연결

		int val = 0;
		while ((val = fis.read()) != -1) { // 읽어들일 데이터가 없을 때까지 while 실행
			fos.write(val); 읽어들인 데이터를 val에 넣고 새 파일에 write
		}
		fis.close(); // 스트림 닫자!
		fos.close();

	}// usestream3

 

# 출력 결과 #

새로운 파일이 복사되었고 test 문장이 잘 나왔다!

복사된 파일
출력 결과창

 


 

2. 16bit Stream 활용

- 이번에는 한글을 작성해보자

- 한글은 16bit Stream 이용해야함!

- Java에서 기본형 데이터형의 경우에는 flush()를 하지 않더라도 Stream에 있는 데이터가 HDD로 나가지만, 

  다른 데이터의 경우에는 꼭 flush()를 해주어야 함!!

 

# 코딩 #

	/**
	 * 16bit Stream 사용하여 text 파일 쓰기
	 * 
	 * @throws IOException
	 */
	public void usestream4() throws IOException {
		File file = new File("C:/Users/user/Desktop/java_output_test2.txt"); // 생성할 파일

		BufferedWriter bw = new BufferedWriter(new FileWriter(file));

		bw.write("안녕하세요");
		bw.flush(); // 데이터 내보내기
		bw.close(); // 스트림 닫자

	}// usestream4

# 출력 결과 #

한글도 데이터 내보내기 완료!

새로 생긴 파일
출력 결과창

 

+ Recent posts