DAO

haribo·2021년 2월 27일
1

Java

목록 보기
15/17

들어가기에 앞서

  • 동기 방식 : 연극처럼 무대가 실시간으로 준비되어 바뀌는 방식. 동시에 상호작용을 하여 빠르고 효과적이지만 효율적이지 않다. (ex 라이브 코딩, 대면 수업) 페이지리다이렉트.
  • 비동기 방식 : 영화처럼 미리 세트장을 준비해두고 녹화를 해두어 시공간의 제약이 없는 방식이다. 구축에 오래걸리고 효과적이진 않지만, 효율적이며 시간과 돈을 많이 절약할 수 있다. (고급기술.) 에이작스. 스마트폰은 유선 하드웨어보다 떨어지지만 효율적이게 하기 위해 비동기방식을 채택하고 있다.

Why?

'서류'라는 개념이 없는 세상에서 회사에 입사했다고 가정해보자. 실시간으로 전화를 하고, 업무 브리핑을 하고, 그 공간에 없는 사람들은 내용을 모를테니 또 설명하고 또 설명하고..... 상상만해도 지옥이다. (동기방식의 지옥) 인간이 글을 만들어 낸 이유는 기록하고, 그 기록을 다른 사람에게도 쉽게 전달하기 위함이다. 정보의 공유가 쉬워지는 것이다.

DAO는 일종의 서류와도 같다. DB에 접근하여 각 계층마다 정보를 전달해주는 서류다. 정보를 전달해주려면 데이터를 읽을 줄 알아야하고, 그걸 또 읽어와 전달해 줄 수 있어야한다.

또 한가지 예를 들어보자. 요리를 하려고 하는데 파가 똑 떨어졌다. 원시적으로 생각했을때 우린 농장에 가서 파를 사와야할 것이다. 하지만 현대인인 우리는 직접 농장에 방문하지 않고 파를 구할 수 있다. 바로 '마트'라는 시스템을 이용하는 것이다.

마트에 진열된 파는 다 원산지(농원)가 다르지만, 일관된 패키지에 일관된 가격으로 판매되고 있다. 소비자인 우리는 마트를 신뢰하므로 유통경로고 뭐고 아무것도 신경쓰지않고 파를 구매할 수 있다.

DAO는 마트시스템과 비슷하다. 데이터의 저장형태(파의 원산지)는 다 다르지만, 내용의 형식은 동일(다 똑같은 파)하다. 그런 다양한 형태의 데이터(파)들을 일관된 방법(마트 시스템)으로 프로그램에서 읽어 들일 수 있는 기능(소비자에게 판매)을 구현한 것이다.

DAO 정의

DAO(Data Access Object) 패턴은 표준 J2EE 디자인 패턴들 중 하나로서 다음과 같은 요소들이 있다.

  • DAO 인터페이스 - 표준화를 위해 사용한다.
  • DAO 인터페이스를 구현한 클래스 - 데이터 유형에 따라 접근할 방법들을 상세히 설계한다.
  • 데이터 전송 객체(Beans에 대한 객체) - 읽어온 데이터의 정형화를 위한 것이다. DTO라고도 불린다.

이 패턴을 사용하여 저수준의 데이터 엑세스(기계에 가까운것을 저수준이라 한다. 순수 데이터 접근처리라 볼 수 있겠다.)와 고급 비즈니스 로직(프론트, UI쪽을 의미한다. 실습파일 Main.) 을 분리할 수 있다.

  • 데이터 활용 로직 = 비즈니스 로직 : 데이터를 활용하기 위한 프로그램이다. 사람이 사용하고, Main이고, UI 영역이다. 요리로 치자면 플레이팅에 가깝다. DAO를 이용하면 Main이 많이 안바뀌는게 강점인데 자세한건 아래 코드에서 확인하자.
  • 데이터 엑세스 : 데이터에 접근하는 처리방식을 뜻한다. 요리로 치자면 채썰고 손질하는 영역이다.

(분리하면 뭐가 좋나요? 서버 터졌다. 할떄 백엔드파트는 야근하지만 프론트엔드는 퇴근한다. 디자인 전면개편합시다. 프론트파트는 야근하지만 백엔드파트는 집으로 간다)

2020-12-18 // 관심사의 분리. 스프링을 배우고 나니 이제야 조금씩 더 보인다.

DAO 기능 설계 과정 ( 파와 DAO )

<파>

  1. 파 손질을 어떻게 할지 정한다.
  2. 파 묶은걸 톤 단위로 받아온다.
  3. 농원별로 관리할 직원을 배정한다. (새 농장 관리시 직원 추가채용) - 익명클래스를 사용하여 일회용으로 쓰고 말수도 있다.
  4. 직원들에게 메뉴얼을 배포한다. (농원별로 이렇게 해)

  1. 데이터의 형식을 표현하는 JavaBeans를 정의해 객체 형태로 데이터를 관리한다.
  2. 여러 개의 데이터를 일괄처리해야할때 컬렉션 프레임워크를 사용한다. (선택사항이다.)
  3. 저장 형태에 따라서 데이터를 읽어들이는 클래스를 분리하여 각각 구현한다. (추후 데이터 저장형태가 늘어나면 클래스를 추가하여 확장한다.)
  4. 데이터를 읽어들이는 클래스가 모두 동일한 인터페이스를 상속받아 같은 메서드 안에서 서로 읽어들이는 방법을 다르게 하도록 한다. 이렇게 하면 이 클래스를 활용하는 측에서는 인터페이스에 대한 객체를 통하여 일관된 호출방법을 유지할 수 있다. (누가 쓰든 비슷하게 쓰겠지. 매뉴얼 효과가 있구나.)

<실습>

package study.java.model;

/** 성적표의 데이터 구조를 표현하는 JavaBeans */
public class Grade {
	// 한 건에 대한 데이터를 구조화한다. 
	// 이 객체가 컬렉션에 저장되면 성적 리스트가 된다.
	private String name;
	private int kor;
	private int eng;
	private int math;
	
	public Grade(String name, int kor, int eng, int math) {
		super();
		this.name = name;
		this.kor = kor;
		this.eng = eng;
		this.math = math;
	}

	public String getName() {
		return name;
	}

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

	public int getKor() {
		return kor;
	}

	public void setKor(int kor) {
		this.kor = kor;
	}

	public int getEng() {
		return eng;
	}

	public void setEng(int eng) {
		this.eng = eng;
	}

	public int getMath() {
		return math;
	}

	public void setMath(int math) {
		this.math = math;
	}

	@Override
	public String toString() {
		return "Grade [name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + "]";
	}

	

}

<인터페이스>

package study.java.dao;

import java.util.List;
import study.java.model.Grade;

/** 학생별 성적 리스트 구현에 대한 설계 */
public interface GradeDao {
	/**
	 * 데이터파일을 읽어들인 후, 파일 안에 저장된 데이터를
	 * JavaBeans 객체로 변환하여 List에 담아 리턴한다.
	 * @return Grade형의 객체가 담긴 컬렉션
	 */
	public List<Grade> getGradeList();

}

<클래스> - file 타입

package study.java.dao.impl;

import java.util.ArrayList;
import java.util.List;
import study.java.dao.GradeDao;
import study.java.helper.FileHelper;
import study.java.model.Grade;

public class FileGradeDaoImpl implements GradeDao {
	
	/**
	 * 어떤 객체가 생성되었는지 확인하기 위하여 클래스 이름을
	 * 출력한다.
	 */
	
	public FileGradeDaoImpl() {
		String clsName = this.getClass().getCanonicalName();
		System.out.println("------" + clsName + "------");
	}

	@Override
	public List<Grade> getGradeList() {
		// 리턴할 객체 선언
		List<Grade> list = null;
		
		// CSV파일 읽기
		String source = FileHelper.getInstance().readString(
				"res/grade.csv", "euc-kr");
		
		// 읽은 내용이 없다면 강제 종료
		if (source == null) {
			return null;
		}
		
		// 읽은 내용이 있다면 리턴할 List를 할당
		list = new ArrayList<Grade>();
		
		/** (1) 라인단위로 분리 --> 학생별 데이터 */
		String[] data = source.split("\n");
		
		/** (2) 학생 수 만큼 반복 처리 */
		for (int i = 0; i < data.length; i++) {
			/** (3) 한 명의 데이터 추출 */
			String line_string = data[i].trim();
			
			/** (4) 한 명의 데이터를 과목별로 분리 */
			String[] line_data = line_string.split(",");
			
			/** (5) 분리한 데이터 추출 */
			String name = line_data[0];
			int kor = Integer.parseInt(line_data[1]);
			int eng = Integer.parseInt(line_data[2]);
			int math = Integer.parseInt(line_data[3]);
			
			/** (6) 분리한 데이터를 객체로 변환 후 컬렉션에 추가 */
			Grade item = new Grade(name, kor, eng, math);
			list.add(item);
		}
		
		// 리턴값을 선언된 객체로 변경
		return list;
		
	}

}

<클래스> - json 타입

package study.java.dao.impl;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONObject;

import study.java.dao.GradeDao;
import study.java.helper.FileHelper;
import study.java.model.Grade;

public class JsonGradeDaoImpl implements GradeDao {
	
	/**
	 * 어떤 객체가 생성되었는지 확인하기 위하여 클래스 이름을
	 * 출력한다.
	 */
	public JsonGradeDaoImpl() {
		String clsName = this.getClass().getCanonicalName();
		System.out.println("------" + clsName + "-------");
	}

	@Override
	public List<Grade> getGradeList() {
		// 리턴할 객체 선언
		List<Grade> list = null;
		
		// JSON파일 읽기
		String source = FileHelper.getInstance().readString(
				"res/grade.json", "utf-8");
		
		// 읽은 내용이 없다면 강제 종료
		if (source == null) {
			return null;
		}
		
		// 읽은 내용이 있다면 리턴할 List를 할당
		list = new ArrayList<Grade>();
		
		/** (1)읽은 내용을 JSON 객체로 변환 */
		JSONObject json = new JSONObject(source);
		
		/** (2) 배열데이터 추출 --> 학생별 데이터 */
		JSONArray grade = json.getJSONArray("grade");
		
		/** (3) 학생 수 만큼 반복처리 */
		for (int i = 0; i < grade.length(); i++) {
			/** (4) 한명의 데이터 추출 */
			JSONObject temp = grade.getJSONObject(i);
			
			/** (5) 데이터 분리 */
			String name = temp.getString("name");
			int kor = temp.getInt("kor");
			int eng = temp.getInt("eng");
			int math = temp.getInt("math");
			
			/** (6) 분리된 데이터를 객체로 변환 후 컬렉션에 추가 */
			Grade item = new Grade(name, kor, eng, math);
			list.add(item);
		}
		
		// 리턴값을 선언된 객체로 변경
		return list;
	}

}

<file 형식 읽어주기 위한 helper>

package study.java.helper;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

public class FileHelper {
	// ------ 싱글톤 객체 생성 시작 ------
	private static FileHelper current;

	public static FileHelper getInstance() {
		if (current == null) {
			current = new FileHelper();
		}
		return current;
	}

	public static void freeInstance() {
		// 객체에 null을 대입하면 메모리에서 삭제된다.
		current = null;
	}

	// 기본 생성자를 private으로 은닉하게 되면 new를 통한 객체 생성이 금지된다.
	private FileHelper() {
		super();
	}
	// ------ 싱글톤 객체 생성 끝 ------

	/**
	 * 주어진 경로에 data 값을 기록하고 저장한다.
	 * 
	 * @param filePath - 저장할 파일 경로
	 * @param data     - 저장할 내용을 담은 스트림
	 * @return boolean - 성공시 true, 실패시 false
	 */
	public boolean write(String filePath, byte[] data) {
		boolean result = false;
		/** 파일 저장 절차 시작 */
		// finally 블록에서 인식하기 위해서 선언부를 위로 이동시킨다.
		OutputStream out = null;
		try {
			out = new FileOutputStream(filePath);
			// 파일쓰기
			out.write(data);
			System.out.println("[INFO] 파일 저장됨 >> " + filePath);
			// 저장에 성공하였으므로, 결과값을 true로 변경
			result = true;
		} catch (FileNotFoundException e) {
			System.out.println("[ERROR] 지정된 경로를 찾을 수 없음. >> " + filePath);
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("[ERROR] 파일 저장 실패 >> " + filePath);
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("[ERROR] 알 수 없는 에러 >> " + filePath);
			e.printStackTrace();
		} finally {

			if (out != null) {
				try {
					out.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	/**
	 * 주어진 경로의 파일을 읽고, 내용을 스트림으로 리턴한다.
	 * 
	 * @param filePath - 읽어야 할 파일의 경로
	 * @return 읽혀진 내용
	 */
	public byte[] read(String filePath) {
		byte[] data = null;
		/** 파일 읽기 */
		InputStream in = null;
		try {
			in = new FileInputStream(filePath);

			// 읽은 내용을 담기 위한 배열은 파일의 용량만큼 사이즈를 할당한다.
			// in.available() --> 열고 있는 파일의 크기
			data = new byte[in.available()];

			// 파일 읽기 - 파라미터로 전달된 배열 안에, 파일의 내용을 담아준다.
			in.read(data);

			System.out.println("[INFO] 파일 읽기 성공 >> " + filePath);
		} catch (FileNotFoundException e) {
			System.out.println("[ERROR] 지정된 경로를 찾을 수 없음. >>" + filePath);
			e.printStackTrace();
		} catch (IOException e) {
			System.out.println("[ERROR] 파일 읽기 실패 >> " + filePath);
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("[ERROR] 알 수 없는 에러 >> " + filePath);
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} // end try~catch~finally

		return data;
	}

	/**
	 * 파일을 저장한다.
	 * 
	 * @param filePath - 저장할 파일 경로
	 * @param content  - 저장할 내용
	 * @param encType  - 인코딩 형식
	 * @return boolean - 성공시 true, 실패시 false
	 */
	public boolean writeString(String filePath, String content, String encType) {
		boolean result = false;
		byte[] data = null;
		
		try {
			data = content.getBytes(encType);
		} catch (UnsupportedEncodingException e) {
			System.out.println("[ERROR] 인코딩 지정 에러");
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("[ERROR] 알 수 없는 에러 >> " + filePath);
			e.printStackTrace();
		}
		
		result = this.write(filePath, data);
		return result;
	}

	
	/**
	 * 파일의 내용을 문자열로 리턴한다.
	 * 
	 * @param filePath - 읽어야 할 파일의 경로
	 * @param encType  - 인코딩 형식
	 * @return String - 읽은 내용에 대한 문자열
	 */
	public String readString(String filePath, String encType) {
		String content = null;

		byte[] data = this.read(filePath);

		// 내용을 문자열로 변환한다.

		try {
			content = new String(data, encType);
			content = content.trim();
		} catch (UnsupportedEncodingException e) {
			System.out.println("[ERROR] 인코딩 지정 에러");
			e.printStackTrace();
		} catch (Exception e) {
			System.out.println("[ERROR] 알 수 없는 에러 >> filePath");
			e.printStackTrace();
		}

		return content;
	}

}

file 용

import java.util.List;

import study.java.dao.GradeDao;
import study.java.dao.impl.FileGradeDaoImpl;
import study.java.model.Grade;

public class Main01 {
	public static void main(String[] args) {
		GradeDao dao = new FileGradeDaoImpl();
		List<Grade> list = dao.getGradeList();
		
		for (int i=0; i<list.size(); i++) {
			Grade item = list.get(i);
			System.out.println(item.toString());
		}

	}

}

json 용

import java.util.List;
import study.java.dao.GradeDao;
import study.java.dao.impl.JsonGradeDaoImpl;
import study.java.model.Grade;

public class Main02 {
	public static void main(String[] args) {
		GradeDao dao = new JsonGradeDaoImpl();
		List<Grade> list = dao.getGradeList();
		
		for (int i=0; i<list.size(); i++) {
			Grade item = list.get(i);
			System.out.println(item.toString());
		}

	}

}
                                 ```

이 포스트는 itpaper.co.kr에서 제공되는 강의자료를 바탕으로 작성되었습니다.

profile
그림 그리는 백엔드 개발자

0개의 댓글