데이터를 JSON으로 주고 받기

07.5 ~ 6 바이트 스트림 클래스를 활용한 데이터 읽고 쓰기

90-MyList프로젝트1 / 62 페이지

⑤ 07.5 바이너리 형식으로 출력

DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("boards.data")));
 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("boards.data")));

FileInputStream
• read()
• read(byte[])

↑ call

<<decorator>> BufferedInputStream
• read()
• ­+ 버퍼

↑ call

<<decorator>> DataInputStream
• readInt()
• readBoolean()
• readUTF()

DataInputStream 메서드가 내부적으로 BufferedInputStream 메서드를 call한다
BufferedInputStream 메서드가 내부적으로 FileInputStream 메서드를 call한다

파일 입출력 할 일은 그렇게 많지 않음
파일 입출력에 사용된 데코레이터 패턴을 경험하는 게 중요

⑥ 07.6 인스턴스를 serialize(직렬화) / deserialize(역직렬화)

ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream("boards.ser2")));
ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("boards.ser2")));

FileInputStream
• read()
• read(byte[])

↑ call

<<decorator>> BufferedInputStream
• read()
• + 버퍼

↑ call

<<decorator>> ObjectInputStream
• readObject()
• readInt()
• readUTF()
• readBoolean()

통해서 읽어온다
스스로 뭔가 작업하는 게 아니라 도움을 받아서 작업
FileInputStream가 읽어온 걸 버퍼에 담고 버퍼에서 읽어온 걸 가공해서 객체로 만들어서 리턴

07.7 JSON 형식으로 데이터 입출력 하기

⑦ 07.7 GSON 라이브러리를 활용하여 입출력 JSON 형식의 데이터 다루기

json은 문자열이므로 FileWriter를 써야 된다
텍스트를 다룰 때는 FileWriter를 쓴다
그냥 쓰면 느리니까 버퍼를 붙인다 (BufferedWriter)
문자열 왕창 출력 (PrintWriter)

FileWriter     →     JSON 파일    →    FileReader
     ↑                                     ↑
<<decorator>>                         <<decorator>> 
BufferedWriter                        BufferedReader
     ↑
<<decorator>>
 PrintWriter

JSON 파일 읽어들이는 건 FileReader
GSON 객체를 사용해서
JSON 문자열로 만들어서 출력한다
println(JSON 문자열)
readLine()으로 한 줄을 읽는다
리턴값을 사용해서 GSON이 객체를 만들어준다

데이터 형식

① CSV
객체 → 파일
character 스트림 I/O
‐ csv는 표준 파일 형식 → 언어나 OS에 비종속
‐ 계층적인 구조의 데이터를 표현하기 어렵다.(번거롭다)
불가능한 건 아니지만 어렵다(번거롭다)

② binary
객체 → 출력 순서에 따라 저장된 byte[]
byte 스트림 I/O
‐ 언어나 OS에 비종속
‐ App. 전용 형식으로 입/출력 → App마다 I/O 처리
이름 저장하고 나이 저장하고 전화번호 저장하고 재직여부를 저장했으면
저장한 순서로 읽어야 됨
출력한 형식마다 다르게 읽어야 됨

③ serialize
객체 → 자바 serialize 규칙에 따라 byte[]배열로 저장
바이트 배열에는 클래스 정보, 필드 정보, 값이 들어 있다.
자바 규칙 + byte 스트림 I/O
‐ 자바에 종속 (다른 프로그래밍 언어에서 I/O 어렵다)
‐ 다른 언어로 만든 App과 데이터를 주고 받기 어렵다
⟹ SW 운영의 유연성이 떨어진다

④ JSON
JSON 규칙에 따라 텍스트로 저장
character 스트림 I/O
‐ JSON은 국제 표준 데이터 형식이다
‐ 프로그래밍 언어나 OS에 비종속
‐ 계층적인 구조의 데이터를 손쉽게 다룰 수 있다.
XML도 그렇다
태그 형식으로 계층적인 구조를 다룬다
‐ XML보다 다루기가 편하다
‐ 텍스트 형식으로 직접 편집 가능
바이트는 직접 편집 불가능

Application 간의 데이터 교환 표준 포맷

App ----- XML 또는 JSON -----> App

날씨앱 기상청 DBMS
① 날씨 데이터 요청
② 날씨 데이터 조회
③ 결과
④ XML 또는 JSON 형식으로 날씨 데이터 리턴

JSON 형식으로 받거나 XML 형식으로 받거나

Asynchronous JavaScript And XML (AJAX)

com.eomcs.openapi.json

gradle 빌드 도구는 maven 빌드 도구와 호환한다.
maven 빌드 도구에 있는 서버를 공유한다.
gradle 라이브러리를 따로 관리하는 서버가 없음
maven에서 관리하는 라이브러리 서버를 공유해서 쓴다

https://search.maven.org/

https://mvnrepository.com/

부트스트랩은 jackson을 기본으로 쓴다

gradle eclipse

'Referenced Libraries' 노드에서 jackson-databind 라이브러리 파일이 추가된 것을 확인한다.

의존하는 라이브러리는 자동으로 다운받음

FasterXML/jackson-databind
https://github.com/FasterXML/jackson-databind

기존에 이미 그런 기능을 하는 클래스가 있는지 찾아보고 있으면 그걸 먼저 쓰기
가장 간단한 거 말고 가장 유명한 거 쓰기
유지보수가 잘 되고 설명도 잘 되어 있는 거
질문답변이 올라와 있는 거

Plain Old Java Object ("POJO")

com.eomcs.openapi.json.jackson.Exam0110.java

JSON 처리 객체 준비
ObjectMapper mapper = new ObjectMapper();
따로 파라미터에 주는 거 없음

JavaScript Object Notation
자바스크립트 객체 표기 문법을 빌려와서 만듦
문자열은 반드시 더블 쿼테이션으로
프로퍼티명은 반드시 문자열로 표현해야 한다.

객체의 값을 JSON 문자열로 얻기

객체의 값을 JSON 문자열로 얻기
String jsonString = mapper.writeValueAsString(myResultObject);

예외가 발생할 수 있으니 throws Exception 추가

GSON

jackson

"registeredDate":1642990668669
밀리세컨드로 나옴

com.eomcs.openapi.json.gson.Exam0120.java
JSON 문자열을 가지고 객체 만들기

더블 쿼테이션 안에서 더블 쿼테이션을 일반 문자로 쓰려면 역슬래시 써야 됨

Member m = gson.fromJson(jsonStr, Member.class);
두 번째 파라미터에 어떤 객체를 만들어서 넣을 거냐 클래스 정보 줘야 됨
class ← 스태틱 변수. 클래스 정보가 들어 있다.

GSON의 fromJson() 메서드는 JSON 형식의 문자열을 입력 받아서
Member 객체를 만들어서 Member 객체에 담아서 리턴해준다

com.eomcs.openapi.json.jackson.Exam0120.java
JSON 문자열을 가지고 객체 만들기

파일을 줄 수도 있고, url을 줄 수도 있다
API 주소 줘도 json 데이터 받을 수 있다

예외 발생할 수도 있으니까 throws Exception 추가

InvalidFormatException
날짜 형식때문에 에러남

밀리세컨드 값 복사해서 사용하기
문자열 아니니까 더블 쿼테이션 붙이지 말기

jackson-databind ← 입출력 할 때 날짜는 밀리세컨드 형식으로 다룬다.

밀리세컨드로 출력하고 밀리세컨드로 읽으면 된다

Gson 라이브러리는 날짜를 프로그램을 실행하는 국가 언어에 맞춰서 다룬다.

com.eomcs.openapi.json.jackson.Exam0130.java
JSON 문자열을 가지고 객체 만들기

JSON 문자열에는 serialize와 다르게 클래스 정보가 없다.

serialize로 출력할 때는 클래스 정보, 필드 정보 있음

JSON 문자열에는 필드 정보는 있지만 클래스 정보는 없다

JSON 문자열에서 프로퍼티 이름과 일치하는 필드가 객체에 있다면 값을 설정해 준다. 없으면 설정 안 해줌. 에러가 뜨는 게 아니라 값이 안 들어감.

Member 정보를 Board 객체에 담으라고 해본다
Board b = mapper.readValue(jsonStr, Board.class);

throws Exception 추가

무시하라고 표시 안 했다고 에러남
Gson은 에러 안 나고 있는 값은 넣고 없는 값은 안 넣는데
jackson은 에러남

주의!
Gson과 달리 JSON 프로퍼티에 해당하는 객체 프로퍼티가 없다면 예외가 발생한다.
@JsonIgnoreProperties 애너테이션을 사용하여 JSON 프로퍼티 중에서 무시할 항목을 지정해야 한다.

com.eomcs.openapi.json.jackson.Board.java

JSON 문자열로 넘어온 값을 객체로 만들 때
JSON 프로퍼티의 이름과 일치하는 객체 프로퍼티가 없다면 예외가 발생한다.
그럴 경우 다음과 같이
JSON으로 넘어온 값 중에서 객체에 저장할 대상이 아닌 프로퍼티에 대해 표시를 해야 한다.
다음과 같이 애너테이션으로 무시할 JSON 프로퍼티의 이름을 명시하라!

@JsonIgnoreProperties(value= {"name", "email", "password", "photo", "tel"})

ObjectMapper 클래스를 사용하여 객체를 JSON 형식으로 출력한다

07.7 파일 API 활용: 데이터를 JSON 형식으로 읽고 쓰기

jackson-databind 를 사용하여 JSON 형식으로 데이터를 읽고 쓰기

에러남
no properties

com.eomcs.util.ArrayList.java 변경

jackson-databind의 ObjectMapper는 프로퍼티만 입출력한다.
현재 ArrayList 클래스에는 프로퍼티가 없다.

  @RequestMapping("/board/save")
  public Object save() throws Exception {
    // 1) 바이트 스트림 객체 준비
    PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("boards.json")));

    // JSON 형식의 문자열을 다룰 객체를 준비한다.
    ObjectMapper mapper = new ObjectMapper();

    // 1) 객체를 JSON 형식의 문자열로 생성한다.
    String jsonStr = mapper.writeValueAsString(boardList);

    //2)JSON 형식으로 바꾼 문자열을 파일로 출력한다.
    out.println(jsonStr);

    out.close(); // 데코레이터에서 close()하면 그 데코레이터와 연결된 모든 객체도 자동으로 close() 한다.
    return boardList.size();
  }

필드와(field) 프로퍼티(property)

90-MyList프로젝트1 / 66 페이지

class Score {
  String name;
  int sum;
  float aver;
  
  void setName(String n) {name = n;}
  String getName() {return name;}
  
  void setAverage(float a) {aver = a;}
  float getAverage() {return aver;}
}

프로퍼티(property) == 게터/세터

setName(String n) ← write property
getName() ← read property

프로퍼티명은 setName에서 set을 제외하고 첫 번째 알파벳을 소문자로 만든 거

프로퍼티명은 getName에서 get을 제외하고 첫 번째 알파벳을 소문자로 만든 거

이 경우 read property와 write property 이름이 같다.
값을 저장할 때 사용하는 프로퍼티 이름과 값을 읽을 때 사용하는 프로퍼티 이름이 같다.

read / write 프로퍼티명이 같다!

void setAverage(float a)
float getAverage()

필드명은 aver인데 프로퍼티명은 average

프로퍼티명이 반드시 필드명과 같을 필요 없다

프로퍼티명은 setter getter 이름으로 결정된다

이클립스에서 Generate 하면 필드 이름이랑 같게 만들어짐

이 경우 필드 이름과 프로퍼티 이름이 다르다.

ArrayList에 프로퍼티 만들기

ArrayList에 프로퍼티가 없어서 에러난거

ArrayList 버전이 달라짐

ArrayList를 통째로 저장해서 그렇다

클래스가 바뀌면서 serialVersionUID가 바뀌어버림
버전이 달라져서 이전에 저장한 파일을 읽을 수 없음
가능하면 경고가 떴을 때 경고를 신중하게 생각
serialVersionUID 직접 입력했으면 클래스가 바뀌더라도 안 바뀜
서로 버전이 안 맞으니까 에러가 뜬 거
경고를 무시하지 말자...
다음부터는 serialVersionUID 직접 지정하기

PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("boards.json")));

// JSON 형식의 문자열을 다룰 객체를 준비한다.
ObjectMapper mapper = new ObjectMapper();
    
// 1) 객체를 JSON 형식의 문자열로 생성한다.
String jsonStr = mapper.writeValueAsString(boardList); 

// 2) JSON 형식으로 바꾼 문자열을 파일로 출력한다.
out.println(jsonStr);

save 하고 boards.json 확인해보기

{"list":[{"title":"aaa","content":"ok","viewCount":7,"createdDate":1642994598724},{"title":"bbb","content":"ok","viewCount":1,"createdDate":1642994602943},{"title":"ccc","content":"ok","viewCount":4,"createdDate":1642994607179},null,null],"size":3}

배열이 총 5개
나머지 2개는 비어 있어서 null

BufferedReader in = new BufferedReader(new FileReader("boards.json"));

// JSON 문자열을 다룰 객체 준비
ObjectMapper mapper = new ObjectMapper();

// 1) JSON 파일에서 문자열을 읽어 온다.
String jsonStr = in.readLine();

System.out.println(jsonStr);

한 줄 출력해보기
데이터 끝까지 읽었는지 확인하기
중간에 줄바꿈 기호 없고 맨 끝에 하나 있음
이 문자열을 객체로 바꾼다 readValue()

BufferedReader in = new BufferedReader(new FileReader("boards.json"));

// JSON 문자열을 다룰 객체 준비
ObjectMapper mapper = new ObjectMapper();

// 1) JSON 파일에서 문자열을 읽어 온다.
String jsonStr = in.readLine();

// 2) JSON 문자열을 가지고 자바 객체를 생성한다.
boardList = mapper.readValue(jsonStr, ArrayList.class);

등록일이 밀리세컨드로 나옴...ㅎ

Board 객체에서 문자를 어떻게 다루길래 밀리세컨드 그대로 나오는지 확인해보자

board의 클래스가 뭐길래

      boardList = mapper.readValue(jsonStr, ArrayList.class);

      for (int i = 0; i < boardList.size(); i++) {
        Object board = boardList.get(i);
        System.out.println(board.getClass());
      }

Board 객체로 만들어진 게 아니라 자기 마음대로 만들어짐
이 데이터는 Board 객체에 담으세요 라고 안 해줘서

LinkedHashMap 객체가 만들어짐

리스트로 뽑을 때는 추가적으로 해줘야 될 게 있음
따로 타입을 지정해줘야 됨
이러면 골치아파짐
우리가 만든 ArrayList는 기능이 허접해서 쓰기가 불편함
한 번 변환하는 방법밖에 없음

ArrayList에서 Board 배열을 꺼낸 후 JSON 문자열로 만든다.
boardList에 들어 있는 데이터를 배열로 꺼내서 출력한다
boardList.toArray()

String jsonStr = mapper.writeValueAsString(boardList.toArray()); 

ArrayList 통째로 저장하지 말고 ArrayList에 들어 있는 배열만 쏙 꺼내서 배열만 출력하겠다

ArrayList에서 getter setter 다시 지우기

PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter("boards.json")));

// JSON 형식의 문자열을 다룰 객체를 준비한다.
ObjectMapper mapper = new ObjectMapper();
    
// 1) 객체를 JSON 형식의 문자열로 생성한다.
// => ArrayList 에서 Board 배열을 꺼낸 후 JSON 문자열로 만든다.
String jsonStr = mapper.writeValueAsString(boardList.toArray());

// 2) JSON 형식으로 바꾼 문자열을 파일로 출력한다.
out.println(jsonStr);

boardList를 통째로 저장할 필요가 없기 때문에
아까 ArrayList에 만들었던 getter setter 지우기
boardList에서 아예 배열을 꺼낸다
배열이 3개면 3개만 꺼낸다

[{"title":"aaa","content":"aaaa","viewCount":3,"createdDate":1642995591429},{"title":"bbb","content":"bbbb","viewCount":0,"createdDate":1642995595244},{"title":"ccc","content":"cccc","viewCount":5,"createdDate":1642995598692}]

처음부터 배열이 나옴
배열 안에 딱 3개가 들어 있다
배열에 들어 있는 개수만큼만 출력

BufferedReader in = new BufferedReader(new FileReader("boards.json"));

// JSON 문자열을 다룰 객체 준비
ObjectMapper mapper = new ObjectMapper();

// 1) JSON 파일에서 문자열을 읽어 온다.
// => 읽어 온 문자열은 배열 형식이다.
String jsonStr = in.readLine();

// 2) JSON 문자열을 가지고 자바 객체를 생성한다.
// => 배열 형식의 JSON 문자열에서 Board의 배열 객체를 생성한다.
Board[] boards = mapper.readValue(jsonStr, Board[].class); // [{},{},{}]

Board[] ← Board 배열 타입
Board[].class ← Board 배열 타입 클래스 정보

제대로 읽었는지 확인해보자

for (Board board : boards) {
  System.out.println(board.getClass());
}

제대로 읽음

JSON에서 자바 객체를 만들 때는 serialize와는 다르게 생성자를 호출한다

생성자를 호출한다

Board 객체의 toString을 호출해서 리턴값을 확인해보자

for (Board board : boards) {
  System.out.println(board);
}

제대로 읽음

처음에 만든 빈 ArrrayList에 읽어온 걸 담자

BufferedReader in = new BufferedReader(new FileReader("boards.json"));

// JSON 문자열을 다룰 객체 준비
ObjectMapper mapper = new ObjectMapper();

// 1) JSON 파일에서 문자열을 읽어 온다.
// => 읽어 온 문자열은 배열 형식이다.
String jsonStr = in.readLine();

// 2) JSON 문자열을 가지고 자바 객체를 생성한다.
// => 배열 형식의 JSON 문자열에서 Board의 배열 객체를 생성한다.
Board[] boards = mapper.readValue(jsonStr, Board[].class); // [{},{},{}]

// 3) 배열 객체를 ArrayList 에 저장한다.
// => 다음과 같이 배열에서 한 개씩 꺼내 목록에 추가할 수 있다.
for (Board board : boards) {
  boardList.add(board);
}

save 할 때 boardList 라는 ArrayList에 들어 있는 값만 배열로 뽑아서 JSON 문자열로 만들고 그 JSON 문자열을 출력하면 대괄호로 시작해서 맨 끝에 대괄호로 끝난다. 그 안에 들어 있는 값이 객체라면 중괄호. 콤마 그 다음 객체.

읽을 때는 거꾸로 배열을 읽는다.
readValue(jsonStr, Board[].class)
Board 배열 객체를 리턴해준다
각 항목에 있는 건 Board 객체를 만들어서 각 항목의 값을 저장한 다음에 Board 객체를 배열에 저장한다
그걸 누가 한다?
jackson-databind에 있는 ObjectMapper가 다 한다
다만 BoardController가 내부적으로 ArrayList를 쓰니까
ArrayList에 다시 담아줘야 한다
배열에 있는 걸 ArrayList에 담아줘야 한다

JSON 데이터 출력하고 읽기

90-MyList프로젝트1 / 67 페이지

ArrayList에 들어 있는 Board 배열을 꺼내서
↑ toArray() 호출해서 꺼냄

이걸 JSON 문자열로 바꿔서
↑ mapper.writeValueAsString()
String으로 바꿔서 리턴

이걸 boards.json 이라는 파일에 저장하고
↑ PrintWriter의 println()

JSON 문자열로 받은 걸 다시 꺼내서
↑ BufferedReader의 readLine()
한 줄 읽기

JSON 문자열을 다시 Board 배열 객체로 바꿔서
↑ mapper.readValue()

그걸 다시 ArrayList에 담았다
↑ add() 반복

나중에 회원가입할 때 사진 첨부할 때
FileOutputStream에 BufferedOutputStream 붙여서

데이터베이스
네트워크 통신
네트워크로 들어온 데이터를 받아서 파일로 저장
데이터베이스 연결하는 기술
파일입출력/네트워킹/스레딩
3가지가 이해가 된 상태에서 DBMS 배우면 안 됨

네트워킹 동시 접속 문제를 해결하기 위해서 스레딩

08.1 DAO 역할 도입: 데이터 보관 처리 코드를 별도의 클래스로 분리

외부 라이브러리

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ArrayList.html

com.eomcs.util.ArrayList.java

  public void addAll(Object[] arr) {
    for (Object obj : arr) {
      this.add(obj);
    }
  }

배열을 주면 배열에서 한 개씩 꺼내서
내부에 있는 add()를 호출해서 한 개씩 추가하자

addAll()를 만들어 놓으면 편한 게
굳이 여기서 직접 할 필요 없음

// 3) 배열 객체를 ArrayList에 저장한다.
//      for (Board board : boards) {
//        boardList.add(board);
//      }
boardList.addAll(boards);

여기서 add() 여러 번 호출하는 게 아니라 한 번에 끝남

목록을 다루는 건 ArrayList가 할 일이니까
목록을 add 시키는 일은 ArrayList가 할 일이니까
메서드는 ArrayList에 있는 게 맞음
Information expert (정보 전문가)
목록을 다루는 전문가가 가지고 있어야 됨

자바에서 제공해주는 ArrayList는 생성자가 독특하다

일반 생성자도 있지만
Collection을 주면 아예 Collection이 들어간 걸 만들어준다
Collection으로 초기화시킴

생성자 오버라이드 하기

처음부터 특정 배열을 주면서 ArrayList를 만들어 달라고 요구

  public ArrayList() {}

  public ArrayList(Object[] arr) {
    this.addAll(arr);
  }

생성자가 호출되면 addAll()을 호출한다
생성자에서 또 다른 메서드를 호출해서
배열에 한 개씩 꺼내서 add

// 3) 배열 객체를 ArrayList 에 저장한다.
// => 다음과 같이 배열에서 한 개씩 꺼내 목록에 추가할 수 있다.
//      for (Board board : boards) {
//        boardList.add(board);
//      }

// => 다음과 같이 addAll()을 호출하여 배열을 목록에 추가할 수 있다.
//      boardList.addAll(boards);

// => 다음과 같이 생성자를 통해 배열을 목록에 추가할 수 있다.
boardList = new ArrayList(boards);

변수 만들지 말고 한 줄에 쓰기 ↓

//  String jsonStr = in.readLine();
//  Book[] books = mapper.readValue(jsonStr, Book[].class);
//  bookList = new ArrayList(books);

bookList = new ArrayList(mapper.readValue(in.readLine(), Book[].class));

기본 생성자로도 만들어보고
여러 생성자로 객체 생성해보기

질문 들어옴

class 스태틱 변수는 타입이 Class
클래스 객체 주소가 들어 있다
우리가 Class 변수 못 만듦
우리가 this 라는 이름의 변수를 못 만드는 것처럼

Board[] : 자바에서 제공하는 타입
배열 타입

90-MyList프로젝트1 / 68 페이지

객체  ----인코딩(serialize)----> 어떤 포맷의 데이터
객체 <----디코딩(deserialize)---- 어떤 포맷의 데이터

객체를 다른 형식의 데이터로 변환하고 거꾸로 다시 객체로 만드는 것을
일반적인 표현으로 serialize / deserialize 라고 한다.

Bread[] obj = mapper.readValue(jsonStr, Bread[].class);

08.1 DAO 역할 도입: 데이터 보관 처리 코드를 별도의 클래스로 분리

DAO(Data Access Object)의 등장 이유

90-MyList프로젝트1 / 69 페이지

🔹 DAO(Data Access Object)
데이터의 persistence(지속성, 데이터 보관)를 관리하는 역할 수행

BoardController ♢→ ArrayList (포함) → 파일

데이터 보관을 ArrayList에 함
ArrayList에 데이터를 임시 보관한다.
ArrayList를 파일에 저장하고 로딩

BoardController 하는 일
① 클라이언트의 요청 처리
② 데이터 보관 처리

🔹 저장 방식
1. CSV 포맷으로 저장
2. App. 전용 바이너리 형식으로 저장
3. 객체 직렬화(serialize)
4. JSON 형식으로 저장
현재 클래스 구조의 문제점
저장 방식이 바뀔 때마다 Controller 클래스를 변경해야 한다.

🔹 BoardController가 하는 일
1. 클라이언트의 요청 처리
2. 데이터 보관 처리

🔹 현재 클래스 구조의 문제점
저장 방식이 바뀔 때마다 Controller 클래스를 변경해야 한다.
→ 생성자 변경, save() 변경

✅ 해결책?
데이터 보관 처리 작업을 별도의 클래스로 분리한다.
유지보수하기 쉬워진다.

DAO(Data Access Object)의 등장

90-MyList프로젝트1 / 70 페이지

                  BoardController
               ↙                 ↘
 <<controller>>                   <<dao>>
 BoardController                  BoardDao
 • 클라이언트 요청 처리              • 데이터 보관 처리

GRASP
High Cohesion 설계 기법을 도입하여
BoardController 클래스를 더 작은 역할 단위로 쪼개서 전문화시킨다.

GRASP(General Responsibility Assignment Software Patterns)
일반적인 책임 할당 소프트웨어 기법

GRASP 패턴의 High Cohesion(높은 응집력=전문화) 설계 기법

GRASP
1. High Cohesion
2. Low Coupling
3. Information Expert
이렇게 3개 기억하기

역할이 쪼개지면 쪼개질수록 유지보수가 편하다.
전문화시키면 시킬수록
더 전문화된 역할로 쪼갠다
High Cohesion 패턴

✅ 역할을 쪼갠다
→ 코드를 이해하기 쉽다.
→ 재사용이 쉽다.
→ 해당하는 기능을 교체가 쉽다.
⟹ 결국 유지보수가 쉽다.

DAO 역할을 별도의 객체로 분리할 때 이점?

✓ 데이터 보관 방식이 바뀌더라도 교체하기 쉽다.

BoardController

FileBoardDao <-----> 파일
JsonBoardDao <-----> 파일
MySQLBoardmDao <-----> MySQL (DBMS)
OracleBoardDao <-----> Oracle (DBMS)

데이터 보관처리를 수행하는 코드를 별도의 클래스로 분리한다.

BoardController 클래스에서

M1 : CPU 안에 RAM이 박혀 있음

데스크탑 : 메모리를 메인보드에 꽂았다 뺐다 할 수 있다
재사용이 쉽다
교체하기 쉽다

더 전문화된 역할로 쪼갠다

1단계 - BoardController 클래스에서 데이터 보관 처리 코드를 분리하여 CsvBoardDao 클래스로 정의한다.

• com.eomcs.mylist.dao.CsvBoardDao 클래스 생성
‐ BoardController에서 데이터 보관 처리와 관련된 코드를 가져온다.

• com.eomcs.mylist.controller.BoardController 클래스 변경
‐ 데이터 처리 코드를 CsvBoardDao 클래스로 옮긴다

dao 패키지 만들기

실무에서는 dao 패키지 안에 데이터 보관·처리를 수행하는 코드를 담아둔다.
Controller에 없고 다 분리되어 있음

버퍼에 담아놨다가 버퍼가 꽉 차면

마지막 남은 걸 flush()를 명시적으로 호출
close()에서 flushBuffer() 해주기는 함
그래도 명시적으로 해주자

readLine() ← 스트림의 끝에 도달해서 더 이상 읽을 게 없으면 null 리턴

이제 BoardController는 ArrayList를 모른다

countAll() ← 현재 ArrayList에 몇 개가 들어 있는지 카운트

나중에 데이터베이스에서 countAll 많이 씀

시작

'java JPA dao 예제' 검색

https://nowonbun.tistory.com/551

findAll() ← 모든 게시글 갖고 와라

  public Object[] findAll() {
    return boardList.toArray();
  }

BoardController가 직접 ArrayList를 다루는 게 아님
BoardDao 통해서 간접적으로 다룬다

  @RequestMapping("/board/list")
  public Object list() {
    return boardDao.findAll(); 
  }

나중에 데이터베이스 들어가면 insert 한 후에 리턴되는 값은
현재까지 저장된 객체 수가 아니라
한 번에 insert 한 객체 수
1개 insert 했으면 1
2개 insert 했으면 2

  public void insert(Board board) throws Exception {
    boardList.add(board);
    save();
  }
  @RequestMapping("/board/add")
  public Object add(Board board) throws Exception {
    board.setCreatedDate(new Date(System.currentTimeMillis()));
    boardDao.insert(board);
    return boardDao.countAll();
  }
  public Board findByNo(int no) {
    if (no < 0 || no >= boardList.size()) {
      return null;
    }
    return (Board) boardList.get(no);
  }

못 찾았으면 null 리턴
찾았으면 리턴

  @RequestMapping("/board/get")
  public Object get(int index) throws Exception {
    Board board = boardDao.findByNo(index);
    if (board == null) {
      return "";
    }
    boardDao.increaseViewCount(index);
    return board;
  }

못 찾았으면 빈 문자열을 리턴한다.
ViewCount 증가시킨 다음에 board 리턴
자바스크립트에 null을 리턴할 수 없으니까 빈 문자열을 리턴한다.

아 저렇게 할 수 있구나
이게 정답이라는 게 아님
정답은 풀이과정이 필요함
이거는 정답이 아니라
문법 훈련이 목적

  @RequestMapping("/board/update")
  public Object update(int index, Board board) throws Exception {
    Board old = boardDao.findByNo(index);
    if (old == null) {
      return 0;
    }

    board.setViewCount(old.getViewCount());
    board.setCreatedDate(old.getCreatedDate());

    return boardDao.update(index, board);
  }

메서드 호출 사이에 다른 사람이 게시글을 지울 수도 있음
인덱스가 무효하기 때문에 return 0 한다
100번 게시물을 지우면 그 100번은 무효한 거

  public int delete(int no) throws Exception {
    if (no < 0 || no >= boardList.size()) {
      return 0;
    }
    boardList.remove(no);
    save();
    return 1;
  }

인덱스가 무효하면 0

내일 BinaryBoardDao, SerialBoardDao, JsonBoardDao 만들 거
BoardDao를 4종류 만들 거
종류를 바꿀 때마다 BoardController에 손을 댔지만
이제 별도 클래스로 분리했기 때문에 손을 대더라도 최소화시킬 수 있음
내일 인터페이스 한 번 더 배운다
인터페이스를 사용하는 이유
스프링 부트의 IoC Container에 대해서 배운다
BoardController가 자기가 사용할 Dao를 직접 만드는 게 귀찮다
스프링 부트가 자동으로 만들어준다
자동으로 만드는 기술 배울 거
인터페이스를 도입해야 됨
인터페이스를 만들어야 하는 이유를 배울 거
인터페이스에 따라서 호출하는 클래스도 다 다루는
인터페이스 문법의 의미

  @RequestMapping("/board/delete")
  public Object delete(int index) throws Exception {
    return boardDao.delete(index);
  }

boards.csv 파일을 읽다가 오류가 발생하면
예외를 던지면 객체 생성이 안 됨
생성자에서 예외 발생하면 아예 객체가 생성이 안 돼서 없앰

  @Autowired
  BoardDao boardDao;

스프링 부트의 IoC Container가 만든다

인터페이스가 적용이 안 된 상태에서는 이 방식에 한계가 있다
규칙을 만들 필요가 있다
인터페이스의 중요성
그 규칙을 준수하는 객체가 있다면 교체 가능
Dependency Injection을 자동화 시키는 도구가 스프링 부트의 IoC Container
그리고 네트워킹으로 넘어갈 거

0개의 댓글