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
연락처 테이블 변경에 맞춰 페이지 컨트롤러 클래스를 변경한다
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();
같은 이름으로 여러 개의 데이터가 넘어올 때 배열로 받아라!
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;
}
@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/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 태그
Author And Source
이 문제에 관하여(2022-03-18(금)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@banana/2022-03-18금저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)