나중에 Lombok 사용할 거임
지금은 게터 세터 직접 만들기

스프링 부트가 Contact 객체를 만든다
각각의 변수에 클라이언트가 보낸 데이터를 넣은 후에 Contact 객체를 넘겨준다
각각의 변수에 값을 어떻게 설정하느냐
세터 메서드를 호출해서 설정
세터 메서드를 호출해서 요청 파라미터 값을 객체에 저장
세터 메서드가 없으면 스프링 부트는 객체를 생성한 후 값을 넣을 방법이 없다
세터 메서드는 반드시 필요하다
스프링 부트 사용법에 따라서 써라

BookController

­1. 게터 세터 메서드 만들기

­2. BoardController 복붙해서 수정하기
readDate 자동생성 아님
다 입력 받아야 되는 거

http://localhost:8080/book/add?title=book1&author=aaa&press=aaa&page=1&price=1&feed=good&readDate=2022-01-05

http://localhost:8080/book/list

http://localhost:8080/book/get?index=0

http://localhost:8080/book/update?index=1&title=a1&author=a1&press=a1&page=1&price=1&feed=good&readDate=2022-01-05

http://localhost:8080/book/delete?index=0

독서일(readDate)은 선택사항으로 함

input type="number" 숫자만 입력할 수 있음

페이지: <input id="x-page" type="number" value="0"><br>
가격: <input id="x-price" type="number" value="0"><br>

빈 문자열은 날짜 타입으로 못 바꾸기 때문에 에러남
ConversionFailedException 에러남

클라이언트에서 넘어온 게 String

클라이언트에서 보내는 건 다 String

웹 브라우저에서 서버로 보내는 건 다 String

서버에서는 그 String을 int로 바꾸고 boolean으로 바꾸고 Date 타입으로 바꾸는 거

무조건 날짜를 붙이지 말고 if 만약 xReadDate.value가 빈 문자열이 아니라면 qs에 추가해라

var qs = `title=${xTitle.value}&author=${xAuthor.value}&press=${xPress.value}`;
qs += `&page=${xPage.value}&price=${xPrice.value}&feed=${xFeed.value}`;

// 독서일을 입력했을 때만 서버에 보낸다.
if (xReadDate.value != "") {
  qs += `&readDate=${xReadDate.value}`;
}

fetch(`/book/add?${qs}`)
  .then(function(response) {
    return response.text();
  })
  .then(function(text) {
    console.log(text);
    location.href = "index.html";
  });

readDate을 안 보내면 null이 된다
클라이언트에서 값을 안 보냈기 때문에 null이 된다

클라이언트에서 서버쪽으로 날짜값을 보낼 때는 문자열로 된 날짜값을 진짜 Date 객체로 바꿀 수 있어야 되는데 빈 문자열인 경우에는 못 바꿈. 에러가 떠버림

빈 문자열은 날짜 타입으로 못 바꿔서 에러가 뜸
빈 문자열을 보낼 바에는 차라리 보내지 말아야 됨
빈 문자열을 보내면 날짜 객체로 못 바꿔서 에러가 뜬다

readDate 안 보내니까 null로 되어 있음

요청 파라미터와 객체 매핑

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

요청 파라미터 : 요청할 때 보내는 데이터

요청이 들어오면 파라미터 값을 객체에 담는다

파라미터 값은 항상 문자열이다. 그래서 변환이 필요하다.

① 객체 생성
new Book()

② 요청 파라미터 값을 객체에 저장
요청 파라미터 이름으로 setter를 찾는다.

파라미터 값은 항상 String(문자열)이다. 그래서 변환이 필요하다.

title, author, press는 String이니까 setter 파라미터에 그대로 들어감

  public void setPage(int page) {
    this.page = page;
  }

setPage()는 파라미터로 int 타입을 넘겨줘야 한다.
Integer.valueOf()로 String 타입을 int 타입으로 바꿔준 다음
setPage() 파라미터에 넘겨준다.

  public void setPrice(int price) {
    this.price = price;
  }

setPrice()도 파라미터로 int 타입을 넘겨줘야 한다.
Integer.valueOf()로 int 타입으로 바꿔준 다음
setPrice() 파라미터에 넘겨준다.

  public void setReadDate(Date readDate) {
    this.readDate = readDate;
  }

setReadDate()는 파라미터로 Date 타입 객체를 받는다.

Date.valueOf(String s)로 String을 Date 객체로 바꿔준 다음
setReadDate() 파라미터에 넘겨준다.

날짜 파라미터 값 변환

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

"2022-1-1" → Date.valueOf("2022-1-1") → Date 객체

http://localhost:8080/book/add?title=book1&....&readDate=

요청 파라미터는 있지만 값이 없는 경우 그 파라미터의 값은 빈 문자열이 된다.

Date.valueOf("") ← 빈 문자열("")이 들어감
⇒ 빈 문자열은 Date 객체로 만들 수 없다.
⇒ 실행 오류!

readDate 파라미터가 없으면 setReadDate()를 호출할 필요가 없다.
따라서 값을 변환할 이유가 없기 때문에 실행 오류가 발생하지 않는다.

백엔드 개발 실습

1단계 - 데이터 저장 요청을 받았을 때 파일로 데이터를 저장한다.

1단계 - 데이터 저장 요청을 받았을 때 파일로 데이터를 저장한다.

ContactController

save() 메서드 추가

  @RequestMapping("/contact/save")
  public Object save() {
    return 0;
  }

http://localhost:8080/contact/save

데이터를 파일로 출력하기 : 텍스트로 출력

FileWriter 라는 도구가 있음
write(String str)
FileWriter가 지정된 파일로 출력
write() → FileWriter → 파일

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/Writer.html

  @RequestMapping("/contact/save")
  public Object save() throws Exception {
    FileWriter out = new FileWriter("contacts.csv");
    out.write("Hello");
    out.close();
    return 0;
  }

save() 하면 파일이 만들어지고 그 파일에 Hello 라는 문자열을 출력

따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.

http://localhost:8080/contact/save

  @RequestMapping("/contact/save")
  public Object save() throws Exception {
    FileWriter out = new FileWriter("contacts.csv"); // 따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.
    Object[] arr = contactList.toArray();
    for (Object obj : arr) {
      Contact contact = (Contact) obj;
      out.write(contact.getName()+","+contact.getEmail()+","+contact.getTel()+","+contact.getCompany()+"\n");
    }
    out.close();
    return 0;
  }

이 데이터를 save 해줘

http://localhost:8080/contact/save

String.format()

com.eomcs.oop.ex04.Exam0210.java 여기에 나왔었음

    // String 클래스에도 특정 인스턴스가 아닌 일반용으로 
    // 문자열을 다룰 수 있는 메서드를 제공한다.
    // 즉 "클래스 메서드(스태틱 메서드)"를 제공한다.

    // 형식을 갖춘 문자열을 만들기
    String s4 = String.format("%s님 반갑습니다", "홍길동");
    System.out.println(s4); // 홍길동님 반갑습니다
  @RequestMapping("/contact/save")
  public Object save() throws Exception {
    FileWriter out = new FileWriter("contacts.csv"); // 따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.
    Object[] arr = contactList.toArray();
    for (Object obj : arr) {
      Contact contact = (Contact) obj;
      out.write(String.format("%s,%s,%s,%s\n", contact.getName(), contact.getEmail(), contact.getTel(), contact.getCompany()));
    }
    out.close();
    return 0;
  }

문자열을 형식에 맞춰서 줘야 됨
한 줄씩 \n

http://localhost:8080/contact/save

인스턴스 메서드를 만들어보자

어떤 형식으로 출력할 건지 내가 결정하고 있음
근데 가장 좋은 건 정보를 갖고 있는 객체가 하는 게 가장 정확함
어떤 식으로 출력할 건지는 그 정보를 갖고 있는 그 객체가 하는 게 가장 정확함

GRASP(General Responsibility Assignment Software Patterns)
일반적인 책임 할당을 위한 소프트웨어 패턴

일반적으로 어떤 클래스가 어떤 일을 해야 되는지 책임을 할당하는 데 사용되는 설계 기법

객체에게 책임을 할당할 때 지침으로 삼을 수 있는 원칙들의 집합을 패턴 형식으로 정리

GRASP - Information Expert
어떤 정보를 다루는 메서드를 만들 때는 그 정보를 갖고 있는 객체가 직접 그 정보를 다루게 한다.

Information Expert(정보 전문가)에게 책임을 할당하라

연락처 정보를 콤마로 구분된 데이터로 한줄씩 만들어서 출력하는데 이름이 먼저 오는지 이메일이 먼저 오는지
연락처 정보에 Fax를 추가하는 순간 String.format() 부분 수정해야 됨. 연락처 정보가 바뀔 때마다 수정해야 됨.

데이터를 갖고 있는 쪽에서 어떤 식으로 데이터를 만들어야 되는지 결정해야 됨

데이터를 갖고 있는 쪽에서 데이터 순서도 결정해야 됨

근데 지금은 데이터를 사용하는 쪽에서 결정하고 있음. 잘못됨!!

개선하자

Information Expert(정보 전문가)

어떤 데이터를 다룰 때는 그 데이터를 갖고 있는 객체가 그 데이터를 다루게 메서드를 그 객체(클래스)에게 준다.

정보를 갖고 있는 객체에게 정보를 다루게 한다.

Contact.java

전치사구 형태의 메서드로 만들자
toCsvString()

인스턴스 변수를 써야 되므로 static을 붙이지 않는다.
현재 연락처 정보를 가지고 CSV로 만드는 거
인스턴스 메서드이기 때문에 앞쪽에서 주소를 받는다
인스턴스 주소를 담는 변수 this
\n ← 엔터를 집어 넣는 건 파일에 write() 하는 쪽에서 할 일

  // 적용 기술
  // => 인스턴스 메서드: 특정 인스턴스를 사용한다면 인스턴스 메서드로 만들라!  
  // => GRASP의 Information Expert 패턴
  //    데이터를 가공하는 기능은 그 데이터를 갖고 있는 클래스에 둬야 한다.
  public String toCsvString() {
    return String.format("%s,%s,%s,%s", 
        this.getName(), 
        this.getEmail(), 
        this.getTel(), 
        this.getCompany());
  }

인스턴스 메서드 + GRASP의 Information Expert 패턴

적용 기술
⇒ 인스턴스 메서드: 특정 인스턴스를 사용한다면 인스턴스 메서드로 만들라!
⇒ GRASP의 Information Expert 패턴
데이터를 가공하는 기능은 그 데이터를 갖고 있는 클래스에 둬야 한다.

클래스 관계 UML ← 90-MyList프로젝트1 / 33 페이지
클래스 관계 5개를 예를 들어서 설명할 수 있어야 됨

  @RequestMapping("/contact/save")
  public Object save() throws Exception {
    FileWriter out = new FileWriter("contacts.csv"); // 따로 경로를 지정하지 않으면 파일은 프로젝트 폴더에 파일이 생성된다.

    Object[] arr = contactList.toArray();
    for (Object obj : arr) {
      Contact contact = (Contact) obj;
      out.write(contact.toCsvString() + "\n");
    }

    out.close();
    return 0;
  }

\n 엔터를 추가하는 일은 여기서 해야 된다.
toCsvString() 역할은 데이터를 CSV 형식으로 주는 것 뿐
엔터를 추가하는 일은 하지 않는다.

누가 하는 게 맞는지 역할 따져보기

데이터를 가공하는 기능은 그 데이터를 갖고 있는 클래스에 둬야 한다.

서버를 스타트할 때 save된 파일을 읽어와야 됨

2단계 - 페이지 컨트롤러 객체가 생성될 때 파일에서 데이터를 로딩한다.

2단계 - 페이지 컨트롤러 객체가 생성될 때 파일에서 데이터를 로딩한다.

파일에서 데이터를 로딩하는 코드를 생성자에 추가한다.

저장했으니까 저장된 거 읽어와야 됨

저장하는 형식에 맞춰서 읽어야 됨

저장할 때 파워포인트 형식으로 저장했으면 읽을 때도 그 형식대로 읽어야 됨

파일 포맷 : 파일을 저장할 때 사용한 형식

코드화시킨다 : 인코딩

인코딩할 때 사용한 규칙 : 알고리즘

알고리즘에 따라서 A에서 → B 상태로 바꿨으면
다시 B에서 → A로 바꾸려면 알고리즘 역으로 해서 바꿔야 됨

읽을 때는 CSV(Comma-separated values) 형식으로 읽어야 됨

역할(role) = 책임(responsibility)

항상 클래스는 role, responsibility 이런 말들이 나옴

하나의 클래스는 하나의 역할을 주는 거

하나의 클래스가 여러 역할을 하게 되면 유지보수가 힘들어진다

한 클래스가 한 역할을 하게 한다

기능이 아님

ContactController의 역할(책임) : 연락처 관리 요청 처리

이 역할을 하는 데 필요한 기능들이 있음
역할(책임)과 관련된 기능 function
연락처를 다루는 기능
add()
list()
get()
update()
delete()

역할과 기능을 잘 구분해라

핸드폰 역할 : 의사소통
핸드폰 기능 : 통화, 메시지

하나의 클래스는 하나의 역할(책임) 있다.
그 책임을 다 하기 위한 기능들이 들어 있다.

API ← 애플리케이션에서 프로그래밍을 하는 동안 사용할 도구

API(Application Programming Interface) : 애플리케이션을 작성할 때 사용하는 도구

File API ← 파일을 다루는 도구
Network API ← 네트워킹 다루는 도구
암호 API
JDBC API ← 데이터베이스 연결하고 소통하는 도구
Servlet API ← 작은 서버 조각을 만들 때 사용하는 도구
Web API ← 웹 프로그램 짤 때 사용하는 도구
DOM API ← 태그를 다룰 때 사용하는 도구
AJAX API ← 비동기로 다른 컴퓨터에 연결해서 요청하고 응답받는

FileWriter : 파일로 데이터를 쓰는 일(역할=책임)을 한다.
write(문자열(String 객체)) ← 파라미터 : 문자열(String 객체)

FileReader : 파일을 읽는다.
read()

  public ContactController() throws Exception {
    contactList = new ArrayList();
    System.out.println("ContactController() 호출됨!");

    FileReader in = new FileReader("contacts.csv");

    StringBuilder buf = new StringBuilder();
    int c;
    while (true) {
      c = in.read(); // 파일에서 한 문자를 읽는다.
      if (c == -1) { // 더 이상 읽을 문자가 없다면 반복문을 종료한다.
        break;
      }
      if (c == '\n') { // 만약 읽은 문자가 줄바꿈 명령이라면, 지금까지 읽은 CSV 데이터를 분석하여 Contact 객체에 담는다.
        String csvStr = buf.toString(); // 예) "홍길동,hong@test.com,010-1111-2222,비트캠프"
        String[] values = csvStr.split(","); // 예) ["홍길동","hong@test.com","010-1111-2222","비트캠프"]

        Contact contact = new Contact(); // 파일에서 읽은 데이터를 담을 객체를 준비한다.
        contact.setName(values[0]); // 배열에 들어 있는 각 항목을 객체의 필드에 저장한다. 
        contact.setEmail(values[1]);
        contact.setTel(values[2]);
        contact.setCompany(values[3]);

        contactList.add(contact); // 데이터를 담은 객체를 목록에 추가한다.

        buf.setLength(0); // 다음 데이터를 읽기 위해 버퍼를 초기화시킨다.
      } else { // 문자를 읽을 때마다 버퍼에 임시 보관한다.        
        buf.append((char)c);
      }
    }

    in.close();
  }

파일에서 csv 데이터를 읽어 Contact 객체에 담기

String 클래스는 immutable

StringBuilder는 변경 가능함

StringBuilder buf = new StringBuilder();
버퍼 생성

① buf.append((char)c)
버퍼에 계속 담는다
엔터를 만날 때까지 반복

② buf.toString()
엔터를 만나면
지금까지 버퍼에 쌓은 걸 하나의 문자열로 만든다

③ csvStr.split(",")
문자열을 콤마로 쪼개서 배열로 리턴한다

Contact contact = new Contact();
파일에서 읽은 데이터를 담을 객체를 준비한다.

④ setter 메서드 호출
배열에 들어 있는 각 항목을 객체의 필드에 저장한다.

⑤ contactList.add(contact);
contactList(ArrayList 객체)에 contact(주소)를 넣는다.
데이터를 담은 객체를 목록에 추가한다.

지가 만든 CSV 지가 읽어

왜 이렇게 바꾸는지 배워야 됨

인스턴스를 생성해주는 메서드를 정의
팩토리 메서드

복잡한 걸 메서드로 만들고 그 메서드를 클래스로 감추는 거

  // 적용기술
  // => 스태틱 메서드 : 특정 인스턴스에 종속되지 않고 사용하는 메서드
  // => GoF의 'Factory Method' 패턴
  //    객체 생성 과정이 복잡할 경우 new 명령을 통해 직접 객체를 생성하는 대신에
  //    메서드를 통해 객체를 리턴받는다.
  //    이렇게 객체를 만들어주는 메서드를 '공장 메서드(factory method)'라 부른다.
  //    보통 스태틱 메서드로 정의한다.
  //
  public static Contact valueOf(String csvStr) {
    // 예) csvStr => "홍길동,hong@test.com,010-1111-2222,비트캠프"

    String[] values = csvStr.split(","); // 예) ["홍길동","hong@test.com","010-1111-2222","비트캠프"]

    Contact contact = new Contact();
    contact.setName(values[0]); // 배열에 들어 있는 각 항목을 객체의 필드에 저장한다. 
    contact.setEmail(values[1]);
    contact.setTel(values[2]);
    contact.setCompany(values[3]);

    return contact;
  }
  public ContactController() throws Exception {
    contactList = new ArrayList();
    System.out.println("ContactController() 호출됨!");

    FileReader in = new FileReader("contacts.csv");

    StringBuilder buf = new StringBuilder();
    int c;
    while (true) {
      c = in.read(); // 파일에서 한 문자를 읽는다.
      if (c == -1) { // 더 이상 읽을 문자가 없다면 반복문을 종료한다.
        break;
      }
      if (c == '\n') { // 만약 읽은 문자가 줄바꿈 명령이라면, 지금까지 읽은 CSV 데이터를 분석하여 Contact 객체에 담는다.
        contactList.add(Contact.valueOf(buf.toString())); // 파일에서 읽은 CSV 데이터로 객체를 초기화시킨 후 목록에 등록한다.
        buf.setLength(0); // 다음 데이터를 읽기 위해 버퍼를 초기화시킨다.
      } else { // 문자를 읽을 때마다 버퍼에 임시 보관한다.        
        buf.append((char)c);
      }
    }

    in.close();
  }

while문 정리!

  public ContactController() throws Exception {
    contactList = new ArrayList();
    System.out.println("ContactController() 호출됨!");

    FileReader in = new FileReader("contacts.csv");

    StringBuilder buf = new StringBuilder();
    int c;
    while ((c = in.read()) != -1) { // 파일에서 한 문자를 읽는다. 더 이상 읽을 문자가 없으면 반복문을 종료한다.
      if (c == '\n') { // 만약 읽은 문자가 줄바꿈 명령이라면, 지금까지 읽은 CSV 데이터를 분석하여 Contact 객체에 담는다.
        contactList.add(Contact.valueOf(buf.toString())); // 파일에서 읽은 CSV 데이터로 객체를 초기화시킨 후 목록에 등록한다.
        buf.setLength(0); // 다음 데이터를 읽기 위해 버퍼를 초기화시킨다.
      } else { // 문자를 읽을 때마다 버퍼에 임시 보관한다.        
        buf.append((char)c);
      }
    }

    in.close();
  }

예외 처리 배워야 되는 이유
존재 이유
예외 상황이 발생했을 때 시스템을 멈추지 않고 계속 실행하게 만드는
예외 처리 배우기 전까지는 그냥 New File 해서 csv 파일 만들기...

매번 save 해야 되는 것도 나중에 고칠 거

0개의 댓글