2022-03-18(금)

DAO와 테이블 관계

하나의 DAO가 여러 테이블
다른 테이블에 있는 데이터를 조회할 수는 있어도
데이터 조회를 할 순 있어도 등록, 변경, 삭제는 안 된다
다른 DAO가 담당하고 있는 테이블에 대해서 등록, 변경, 삭제는
하나의 DAO가 한 개 이상의 테이블에 오너십을 가질 순 있지만
한 테이블은 여러 DAO에 의해서 등록, 변경, 삭제가 되면 안 된다.
조회는 할 수 있다.
유지보수가 힘들어진다.
한 테이블에 대한 오너십은 하나의 DAO
오너십 CRUD
데이터를 등록, 변경, 삭제하는 건 하나의 DAO가 담당해야 한다

내일 서비스 객체 만들 거임
서비스 객체가 하는 일 : 업무 처리, 트랜잭션 제어(commit, rollback)
MemberService
BoardService
ApplyService
하나의 서비스 객체는 한 개 이상의 DAO를 사용해서 업무를 처리할 수 있다

컨트롤러 객체 : 클라이언트 요청을 처리 (요청에 대한 응답을 담당)
MemberController : MemberService를 이용해서 클라이언트 요청을 처리
BoardController
ApplyController

Controller가 DAO를 쓰는 거 안 됨

컨트롤러 객체 → 서비스 객체 → DAO → 테이블

유지보수를 쉽게 하는 방법이냐 유지보수를 어렵게 하는 방법이냐

한 테이블을 여러 DAO가 접근해서 쓸 수 있다
나머지 DAO는 조회만 하는 거

트랜잭션

보통 전화번호는 여러 개 저장함

전화번호 유형 테이블
• 전화
• 휴대폰
• 팩스
• 직장전화
• 직장팩스

연락처 테이블
전화번호 테이블
전화번호 유형 테이블

/Users/nana/git/bitcamp-20211108/mylist-boot/app-11.1/README.md

-- 연락처
drop table ml_contact;

create table ml_contact(
  contact_no int not null,
  name varchar(50) not null,
  email varchar(20) not null,
  company varchar(50)
);

alter table ml_contact
  add constraint primary key (contact_no),
  modify column contact_no int not null auto_increment;

-- 전화번호 유형
create table ml_tel_type(
  tt_no int not null,
  title varchar(20) not null
);

alter table ml_tel_type
  add constraint primary key (tt_no),
  modify column tt_no int not null auto_increment;
-- 연락처 전화번호
create table ml_cont_tel(
  ct_no int not null, -- 전화번호 PK
  contact_no int not null,
  tt_no int not null,
  tel varchar(20) not null
);

alter table ml_cont_tel
  add constraint primary key (ct_no),
  modify column ct_no int not null auto_increment;

alter table ml_cont_tel
  add constraint ml_cont_tel_fk1 
    foreign key (contact_no) references ml_contact(contact_no),
  add constraint ml_cont_tel_fk2 
    foreign key (tt_no) references ml_tel_type(tt_no);

연락처 테이블에 예제 데이터를 넣는다

연락처 데이터를 다룰 DAO를 만든다

ContactDao
ContactTelDao
TelTypeDao

전화번호 유형 정보를 다룰 DAO를 만들어도 되고
안 만들어도 된다

실무에서는 '전화번호 유형' 테이블처럼
테이블 생성할 때 데이터를 넣는 경우
따로 DAO를 생성하지 않는다
관리 기능에 포함된다면 만들 수 있다

각 테이블에 대해 DAO를 만들 수도 있고
다른 테이블과 연관된 경우

연락처 테이블 변경에 맞춰 도메인 클래스를 변경한다

연락처 테이블과 도메인 클래스

Contact    <------------------> 연락처
ContactTel <------------------> 전화번호
                                전화유형

전화유형 데이터를 직접 다룰 일이 없으면 도메인 클래스를 안 만들어도 된다.

포함 관계 (composition)
이 객체에 포함돼서 쓰임
이 객체가 사라지면 얘도 사라짐

주의!
객체(클래스)와 객체 사이의 관계는 단방향이어야 한다.

A 클래스 ----> B 클래스

class A {
  B obj;
  ...
}

class B { // No!!!!!!
  A obj;
  ...
}

쌍방참조 하지 말기

join이 복잡한 경우에는 HashMap 사용

연락처 테이블 변경에 맞춰 DAO 클래스를 변경한다

관리 기능에 포함된다면 (유형을 뺀다거나 추가한다거나) 만들 수 있다

  <select id="findByContactNo" parameterType="int">
    select
      ct_no,
      contact_no,
      tt_no,
      tel
    from
      ml_cont_tel
    where
      contact_no=#{no}
  </select>
  <select id="findByContactNo" parameterType="int" resultMap="contactTelMap">
    select
      ct_no,
      contact_no,
      tt_no,
      tel
    from
      ml_cont_tel
    where
      contact_no=#{no}
  </select>

ContactDao 사용

ContactController ------> ContactDao

연락처 테이블 변경에 맞춰 페이지 컨트롤러 클래스를 변경한다

http://localhost:8080/contact/add?name=aaa&[email protected]&tel=010-0000-0001&tel=02-0000-0002&tel=02-0000-0003

Spring Boot가
new Contact() ← Contact 객체 생성
name 필드, email 필드, company 필드에
각 필드에 쿼리 파라미터 값을 넣는다
tel은 여러 개 받을 수 있음
tel을 String[] 배열로 받을 수 있다
new String[]
쿼리 파라미터 값을 String[] 배열의
0번째 항목, 1번째 항목, 2번째 항목에 넣는다

Spring Boot가 ContactController 페이지 컨트롤러 호출 call
값을 받을 변수가 필요함
add(Contact contact, String[] tel) {...}
이름이 일치해야 되니까 tels가 아니라 tel로 한다

http://localhost:8080/contact/list

    System.out.println(contact);
    for (String t : tel) {
      System.out.println(t + ", ");
    }
    System.out.println();

http://localhost:8080/contact/add?name=bbb&[email protected]&tel=010-0000-0001&tel=02-0000-0002&tel=02-0000-0003

같은 이름으로 여러 개의 데이터가 넘어올 때 배열로 받아라!

add(Contact contact, String[] tel) {...}
같은 이름으로 파라미터 값이 여러 개 넘어올 때 배열로 받을 수 있다.

add(Contact contact, String[] tel) {...}
파라미터 값을 객체로 받을 수 있다. 단, 파라미터 이름과 일치하는 프로퍼티가 있어야 한다.

/contact/add?name=OOO
요청 파라미터라고 부른다.
요청 파라미터 이름과 값

/contact/add?name=OOO&email=OOO&tel=OOO
물음표부터 끝까지를 쿼리 스트링이라고 한다

요청 파라미터와 같은 프로퍼티명이 있다는 가정 하에서

ContactTel 생성자 만들기
객체 생성할 때 전화번호를 아예

  public ContactTel() {}

  public ContactTel(String tel) {
    this.tel = tel;
  }

생성자 하나 잘 만들어도 코드가 줄어든다 ↓

  @RequestMapping("/contact/add")
  public Object add(Contact contact, String[] tel) throws Exception {
    System.out.println(contact);
    for (String t : tel) {
      contactDao.insertTel(new ContactTel(t));
    }
    return 1;
  }

유형 까먹음...

문제점
전화번호의 유형을 파라미터로 받지 못했다

/contact/add?name=OOO&email=OOO&company=OOO
new Contact()로 받으면 됨

여태까지 우리가 배운 걸로 해결하는 방법은

/contact/add?name=OOO&email=OOO&company=OOO&tel=1,010-1111-2222&tel=2,02-1111-2222&tel=3,010-1111-3333
new String[]으로 받는다
0번째 항목에는 tel=1, 1번째 항목에는 tel=2, 2번째 항목에는 tel=3

add(Contact contact, String[] tel) {...}

new ContactTel(유형, 전화번호)

split() 해서 잘라서 넣기

  public ContactTel(int telTypeNo, String tel) {
    this.telTypeNo = telTypeNo;
    this.tel = tel;
  }

http://localhost:8080/contact/add?name=x1&[email protected]&tel=1,02-0000-0001&tel=2,02-0000-0002&tel=3,010-0000-0003

  @RequestMapping("/contact/add")
  public Object add(Contact contact, String[] tel) throws Exception {
    contactDao.insert(contact);
    for (int i = 0; i < tel.length; i++) {
      String[] value = tel[i].split(",");
      contactDao.insertTel(new ContactTel(Integer.parseInt(value[0]), value[1]));
    }
    return 1;
  }

생성자 수정하기

  public ContactTel(int contactNo, int telTypeNo, String tel) {
    this.contactNo = contactNo;
    this.telTypeNo = telTypeNo;
    this.tel = tel;
  }

insert를 해야지 PK 값을 알 수 있음
그리고 그 PK 값을 가지고 insert를 해야 됨

전에 JDBC 배울 때 했던 방법

com.eomcs.jdbc.ex4.Exam0110.java

insert 한 다음에 자동 증가하는 PK 컬럼 값을 알아내는 방법

자식 테이블의 데이터를 함께 입력할 때의 문제점

부모 게시글 번호를 알아야 됨
어? 앞에서 입력한 게시글의 번호가 뭐지?
문제는 바로 위에서 입력한 게시글의 PK 값이 자동 생성되는 컬럼이기 때문에

해결책

com.eomcs.jdbc.ex4.Exam0111.java

  <!-- 자동 증가된 PK 값을 받고 싶을 때! -->
  <insert id="insert" parameterType="contact" keyProperty="no" keyColumn="contact_no" useGeneratedKeys="true">
    insert into ml_contact(name,email,company) 
    values(#{name},#{email},#{company})
  </insert>

  @RequestMapping("/contact/add")
  public Object add(Contact contact, String[] tel) throws Exception {
    contactDao.insert(contact);
    for (int i = 0; i < tel.length; i++) {
      String[] value = tel[i].split(",");
      contactDao.insertTel(new ContactTel(contact.getNo(), Integer.parseInt(value[0]), value[1]));
    }
    return 1;
  }

http://localhost:8080/contact/add?name=x3&[email protected]&company=bitcamp&tel=1,02-0000-0001&tel=2,02-0000-0002&tel=3,010-0000-0003

http://localhost:8080/contact/add?name=x4&[email protected]&company=bitcamp&tel=1,02-0000-0001&tel=2,02-0000-0002&tel=3,010-0000-0003

데이터를 분산해서 테이블에 저장

http://localhost:8080/contact/index.html

연락처 입력 화면

전화유형 선택할 수 있는 셀렉트 박스 추가하기

  var xName = document.querySelector("#x-name");
  var xEmail = document.querySelector("#x-email");
  var xCompany = document.querySelector("#x-company");

  document.querySelector("#x-add-btn").onclick = function() {
    if (xName.value == "" || xEmail.value == "" /* || xTel.value == "" */) {
      window.alert("필수 입력 항목이 비어 있습니다.");
      return;
    }
    console.log(xName.value);
    console.log(xEmail.value);
    console.log(xCompany.value);

  var xName = document.querySelector("#x-name");
  var xEmail = document.querySelector("#x-email");
  var xCompany = document.querySelector("#x-company");
  var xTelDivList = document.querySelectorAll(".x-tel-div")
  
  for (var xTelDiv of xTelDivList) {
	  console.log(xTelDiv);
  }
  for (var xTelDiv of xTelDivList) {
	  var xTelType = xTelDiv.querySelector("select");
	  var xTel = xTelDiv.querySelector("input");
  }

http://localhost:8080/contact/list

get() 메서드 변경

연락처 상세보기 화면을 처리한다.

  @RequestMapping("/contact/get")
  public Object get(int no) {
    Contact contact = contactDao.findByNo(no);
    if (contact == null) {
      return "";
    }
    contact.setTels(contactDao.findTelByContactNo(no));
    return contact;
  }

      // 4) 연락처 상세 정보를 화면에 출력한다.
      xName.value = contact.name;
      xEmail.value = contact.email;
      xCompany.value = contact.company;
      for (var tel of contact.tels) {
    	  console.log(tel);
      }

      // 4) 연락처 상세 정보를 화면에 출력한다.
      xName.value = contact.name;
      xEmail.value = contact.email;
      xCompany.value = contact.company;
      for (var i = 0; i < contact.tels.length; i++) {
    	  var xTelType = xTelDivList[i].querySelector("select");
    	  var xTel = xTelDivList[i].querySelector("input");
    	  
    	  console.log()
    	  xTelType.value = contact.tels[i].telTypeNo;
    	  xTel.value = contact.tels[i].tel;
      }

상세페이지로 가면 셀렉트 박스에 해당하는 유형으로 나온다

update

  document.querySelector("#x-update-btn").onclick = function() {
	  var firstTel = xTelDivList[0].querySelector("input"); // 첫번째 전화번호
	  if (xName.value == "" || xEmail.value == "" || firstTel.value == "") {
      window.alert("필수 입력 항목이 비어 있습니다.");
      return;
    }

전화번호 업데이트
기존 전화번호를 밀어버린다
새로운 insert를 한다

int deleteTelByContactNo(int contactNo);

해당 연락처의 모든 연락처를 지워라

  @RequestMapping("/contact/update")
  public Object update(Contact contact, String[] tel) throws Exception {
    int count = contactDao.update(contact);
    if (count > 0) {
      contactDao.deleteTelByContactNo(contact.getNo());
      for (int i = 0; i < tel.length; i++) {
        String[] value = tel[i].split(",");
        if (value[1].length() == 0) {
          continue;
        }
        contactDao.insertTel(new ContactTel(contact.getNo(), Integer.parseInt(value[0]), value[1]));
      }
    }
    return count;
  }

delete

자식 테이블 데이터 먼저 삭제해야 됨

  @RequestMapping("/contact/delete")
  public Object delete(int no) throws Exception {
    contactDao.deleteTelByContactNo(no);
    return contactDao.delete(no);
  }

전화번호 입력 화면 동적으로 추가하기

<button type="button" onclick="deleteDiv(event)">삭제</button>

인라인으로 작성할 때는 e가 아니라 event로

EventTarget.dispatchEvent()

https://developer.mozilla.org/ko/docs/Web/API/EventTarget/dispatchEvent

테이블 조인하면 저 코드↓ 없이 한 번에 가져올 수 있음

resultMap에서 collection 태그 사용하는 건 다음주 월요일에..
association 태그

좋은 웹페이지 즐겨찾기