데이터베이스가 등장한 이유
데이터베이스 목적
데이터베이스를 사용할 수 밖에 없는 상황
90-MyList프로젝트2 / 1 페이지
① 1단계 - Standalnoe
App → 파일
데이터는 파일에 저장한다.
교사
성적 관리 시스템을 사용하는 사람 : 교사
교사가 입력한 학생 정보
교사가 성적을 입력, 조회, 변경, 삭제한다.
성적을 입력 - create
조회 - read, retrieve
변경 - update
삭제 - delete
또 다른 교사가 등록, 조회, 수정, 삭제
A반 교사, B반 교사, C반 교사
설치하는 데 다른 프로그램의 도움은 필요 없음
홀로 설치 가능
인터넷 연결 안 되어 있어도 사용 가능
예) MS-Office, Photoshop
메신저는 안 됨
메신저 서버에 연결되어 있어야 함
Local 컴퓨터 (PC - Personal Computer) : 사용자가 사용하는 컴퓨터
Remote 컴퓨터 (Server 컴퓨터) : 서비스를 제공하는 컴퓨터
클라이언트 메신저 <-----------> 메신저 서버
C/S Application
성적 관리 프로그램을 Standalone으로 만들었을 때 문제점
-> 데이터 교환이 힘들다
🔹 문제점 1
① Application끼리 데이터 공유가 힘들다. (불편하다)
전체 학생의 총점, 평균 구하려면 모아서
USB로 합쳐서
못 하는 건 아닌데 힘들다는 거
데이터 동기화 (synchronize)
내가 다른 사람한테 돈을 보내면
내 통장에서는 마이너스가 되고, 다른 사람 통장에서는 플러스 되어야 함
일치하다 = synchronize
동시에 발생하다
직원을 추가했으면 조회가 돼야 하는데
파일을 업데이트 할 때까지 조회를 못 함
파일을 합칠 때까지는 조회를 못 함
데이터가 일치하지 않는 문제
🔹 문제점 2
② 데이터 동기화가 어렵다.
실시간 동기화가 안 된다.
synchronous ← 동시 발생하는, 일치하게 하는
🔹 Standalone 문제점
각 로컬 컴퓨터마다 데이터를 따로 보관하고 있어서
실시간 동기화가 안 되고
실시간 공유가 안 돼서 불편하다.
90-MyList프로젝트2 / 3 페이지
② 2단계 - 데이터 처리 기능을 분리 => 실시간 데이터 공유
데이터 처리 기능을 분리함으로써 실시간 데이터 공유가
데이터 관리 App. (데이터 I/O)
↑ 이 프로그램에서 데이터를 가져온다.
이렇게 하면 데이터 공유가 가능하다.
데이터를 실시간 공유 가능
=> 데이터 동기화가 이루어진다 (데이터가 일치)
이런 목적으로 만들어진 프로그램
Database Management System (DBMS)
mkdir project-app2
cd project-app2
DSL (Domain-Specific Language)
build.gradle 명령어 설정
이 영역에서 사용하는 프로그래밍 언어
Specific 이 형용사가 앞쪽을 수식해줘서 -을 붙인다
예전에는 Groovy만 됐었는데 요즘은 Kotlin도 된다
.jsp
안에서는 Java를 DSL로 쓴다.
*.html
JavaScript, CSS를 DSL로 쓴다
build.gradle 파일에 이클립스 관련 플러그인을 추가한다.
gradle eclipse
com.eomcs.app2.App.java
package com.eomcs.app2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
while (true) {
System.out.print("명령> ");
String input = keyScan.nextLine();
if (input.equals("quit") || input.equals("exit")) {
break;
}
}
System.out.println("종료!");
keyScan.close();
}
}
등록, 조회, 수정, 삭제
목록을 주자
메뉴를 제시하자
package com.eomcs.app2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
while (true) {
System.out.println("메뉴: ");
System.out.println("1. 등록");
System.out.println("2. 목록");
System.out.println("3. 상세");
System.out.println("4. 변경");
System.out.println("5. 삭제");
System.out.print("명령> ");
String input = keyScan.nextLine();
if (input.equals("quit") || input.equals("exit")) {
break;
}
}
System.out.println("종료!");
keyScan.close();
}
}
package com.eomcs.app2;
import java.util.Scanner;
public class App {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
while (true) {
System.out.println("메뉴: ");
System.out.println("1. 등록");
System.out.println("2. 목록");
System.out.println("3. 상세");
System.out.println("4. 변경");
System.out.println("5. 삭제");
System.out.print("명령> ");
String input = keyScan.nextLine();
if (input.equals("quit") || input.equals("exit")) {
break;
}
switch (input) {
case "1":
break;
case "2":
break;
case "3":
break;
case "4":
break;
case "5":
break;
default:
System.out.println("올바른 메뉴 번호를 입력하세요!");
}
}
System.out.println("종료!");
keyScan.close();
}
}
quit 해주는 거 잊지 말기
유닉스는 LF
윈도우는 CRLF
gradle이 자동 생성한 파일은 유닉스 방식으로 줄바꿈 코드를 집어넣는다.
윈도우는 줄바꿈할 때 CRLF 2바이트를 집어넣지만
유닉스는 LF만 집어넣는다.
LF를 CRLF로 바꿀 거라고 알려주는 거
스태틱 메서드보다는 인스턴스 메서드를 많이 사용한다
이번에는 조금 다르게 하자
왜요
package com.eomcs.app2;
import java.util.Scanner;
public class App {
Scanner keyScan = new Scanner(System.in);
public static void main(String[] args) {
new App().service();
}
public void service() {
while (true) {
printMenu();
String input = prompt();
if (input.equals("quit") || input.equals("exit")) {
break;
}
switch (input) {
case "1":
break;
case "2":
break;
case "3":
break;
case "4":
break;
case "5":
break;
default:
System.out.println("올바른 메뉴 번호를 입력하세요!");
}
}
System.out.println("종료!");
keyScan.close();
}
public void printMenu() {
System.out.println("메뉴: ");
System.out.println("1. 등록");
System.out.println("2. 목록");
System.out.println("3. 상세");
System.out.println("4. 변경");
System.out.println("5. 삭제");
}
public String prompt() {
System.out.print("명령> ");
return keyScan.nextLine();
}
}
따로 주석을 달 필요가 없음
각각 부속품을 따로따로
오토 언박싱 wrapper 클래스 primivite 타입
nextInt()로 받으면 뒤에 엔터까지 넘어오는 거 처리까지 해줘야 돼서
nextLine()으로 받는다
기존의 prompt를 개선하자
오버로딩 조건
파라미터의 타입, 개수, 순서가 달라야 한다
리턴 타입만 다른 건 같은 이름을 가질 수 없다
호출할 메서드를 구분할 때는 파라미터 값으로 구분
private int promptInt(String title) {
System.out.print(title);
return Integer.parseInt(prompt(title));
}
Source - Generate Getters and Setters 클릭
name, kor, eng, math
sum이랑 average는 외부에서 설정하는 게 아니므로 get만
조회하는 것만
this.
생략 된 거 잊지 말기
인스턴스 메서드는 호출할 때 반드시 메서드 앞쪽에 인스턴스 주소를 줘야 한다
인스턴스 주소 없이 호출할 방법은 없다
this.
이 생략된 거다
domain class = value object
이 객체 안에 어떤 값이 들어 있나 궁금할 때가 있음
간단하게 출력하기 위해서 Object로부터 상속받은 toString()을 오버라이딩한다.
Source - Generate toString()
@Override
public String toString() {
return "Score [name=" + name + ", kor=" + kor + ", eng=" + eng + ", math=" + math + ", sum="
+ sum + ", average=" + average + "]";
}
key로 쓸 거면 hashCode(), equals() 오버라이딩
score.setKor(promptInt("국어? "));
세터 메서드가 먼저 호출되는 게 아니라 제일 안쪽부터 실행된다.
괄호 안이 먼저 실행되고 그 다음 setKor()이 실행된다.
ArrayList<Score> scores = new ArrayList<>();
com.eomcs.lang.ex06.Exam0450.java
흐름 제어문 - for(:) 와 배열
배열 전체를 반복하거나
컬렉션 객체(java.util.Iterable 구현체) 전체를 반복할 때 유용한다.
배열의 일부만 반복할 수 없다.
for (변수 선언 : 배열, Iterable 구현체)
변수의 타입은 배열이나 Iterable 구현체의 항목 타입과 같아야 한다.
private void listScore() {
for (Score score : scores) {
System.out.printf("%s, %d, %d, %d, %d, %.1f\n",
score.getName(),
score.getKor(),
score.getEng(),
score.getMath(),
score.getSum(),
score.getAverage());
}
}
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ArrayList.html
ArrayList는 Interable 구현했다.
모든 클래스는 Object의 자식이므로 toString() 메서드를 가지고 있다
맨 끝에 try - catch에 걸릴 거 아니야
거기에 걸릴 때까지 기다리지 말고 여기서 그냥 적절한 메시지를 띄워버린다.
예외까지 가지 않고 걸러내는 방법
예외가 발생하기 전에 사전 조치를 취할 수 있음
public void update() {
int no = Prompt.promptInt("번호? ");
if (no < 0 || no >= scores.size()) {
System.out.println("번호가 유효하지 않습니다.");
return;
}
세터 게터 왜 쓰는지
직접 필드에 access 못 하게
직접 변수에 access 하는 방법이 아닌
만약에 내가 국어 점수를 바꾸고 싶어
직접 필드에 값을 넣을 수 있잖아
근데 이 경우에 문제가 뭐냐면
국어 값을 바꾸면 합계랑 평균을 다시 계산해야 됨
그럼 합계와 평균을 계산하기 위해서 score에 대해서 compute()를 따로 호출해야 됨
그런데 세터 메서드를 호출하면 세터 메서드 안에서 상황에 따라서 적절한 조치를 취할 수 있다.
국어 점수를 300점을 넣는다 막을 방법이 없음
세터 메서드에는 지금 또는 나중이라도 조건을 붙일 수 있음
필드를 직접 사용하게 되면 코드를 다 고쳐야 됨
필드에 직접 access 하는 것보다는 세터 메서드 통해서 access 하는 게 낫다.
필드에 직접 설정하면 그 필드에 유효하지 않은 값을 설정할 수도 있음
필드에 유효하지 않은 값을 설정하는 것을 막기 위한 문법이 캡슐화(접근 제어)
적절하게 접근을 제어함으로써 메서드를 통해서 값을 넣도록 유도함으로써
유효하지 않은 값이 들어가는 걸 어느 정도 막을 수 있다
가변 파라미터 = variable parameter
varialble arguments..?
Formal Parameters
https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.4.1
promptString(String titleFormat, Object... args)
Object... args ← 0개 이상의 값이 온다. (안 와도 됨)
Object로 하면 int도 담을 수 있고 String도 담을 수 있고 다 받을 수 있음
private String promptString(String titleFormat, Object... args) {
System.out.print(String.format(titleFormat, args));
return keyScan.nextLine();
}
private int promptInt(String titleFormat, Object... args) {
return Integer.parseInt(promptString(titleFormat, args));
}
하나의 클래스에 수많은 메서드가 있다
역할에 따라서 분리하자
클래스로 분리시키자
Score를 다루는 메서드 create, list, detail, update, delete 를 분리시키자
ScoreHandler 클래스 만들기
create, list, detail, update, delete 메서드 옮기기
private -> public
ScoreHandler 이름에 이미 Score가 들어가 있으니까
create, list, detail, update, delete 메서드 이름에서 Score 빼주기
prompt 분리시키자
Prompt 클래스 만들기
prompt 관련 메서드 옮기기
promptString(), promptInt() 메서드 옮기기
메서드가 keyScan을 사용하고 있으므로 keyScan도 옮긴다
굳이 인스턴스 메서드가 될 필요가 없음
public static으로 만든다
프로그램 전체적으로 prompt는 1개만 유지해도 됨
Prompt 객체는 한 개만 있어도 되니까 static 으로 하자
ScoreHandler : Score 등록, 조회, 수정, 삭제
ScoreHandler도 static으로 해도 되지 않습니까?
A반 scoreHandler
B반 scoreHandler
C반 scoreHandler ...
각 반마다 scoreHandler를 만들어야 되니까
파일 입출력 해보자
Score 객체를 생성하는 건 Score 객체에 맡긴다
fromCSV() ← 객체를 생성하는 메서드 (팩토리 메서드)
가장 무식한 방법
뭐 할 때마다 save();
내부적으로 인스턴스 변수를 안 씀
toCSV()
3시 15분까지
파일 입출력을 별로 프로그램으로 떼어내고
네트워크 통신
jdbc API 만들 거
다른 사람이 한 작업은 내 컴퓨터에 없음
다른 사람 컴퓨터에서 등록한 데이터는 다른 사람은 모름
데이터 공유 못 함
수시로 다른 사람이 작업한 걸 조회해야 되는 경우가 있음
1.5단계가 있었음
하드디스크를 공유한다.
여러 Application에서 같은 파일을 공유 디스크 / 폴더
디렉토리 공유
여러 App.에서 공유 폴더를 통해 같은 파일을 읽고 쓴다.
-> 데이터 공유 가능
어느 정도 데이터를 공유할 수 있음
🔹 공유 폴더를 이용한 파일 공유 방식의 문제점?
① 동시에 파일에 접근할 때
어떤 App에서 데이터를 변경하는 동안 다른 App에서 데이터를 삭제할 때!
② 접근 권한 제어 불가
누구는 읽기만 가능하고 누구는 읽고 쓰기가 가능하고 제어하고 싶은데 그게 안 됨
여러 App.에서 한 개의 파일에 직접 접근할 때 문제점
파일 I/O 기능을 별도의 App으로 분리하는 방식을 쓰게 되면
① 동시 입출력을 제어할 수 있다.
② 접근 권한을 제어할 수 있다.
DBMS가 무엇인지 맛만 보자
com.eomcs.app2.ServerApp.java
파일로 입출력 하는 일
출력할 때는 객체를 출력?
데이터 입출력
클라이언트가 csv 형식으로 데이터를 보내
데이터를 던지면 몇 개를 insert 했는지 개수를 리턴한다
개수를 다시 클라이언트에게 리턴한다
목록을 달라고 요청을 하면
번호를 받으니까 readInt()
switch문은 한 덩어리가 하나의 영역이기 때문에
switch문 안에서 변수는 한 번만 선언하면 된다.
ScoreHandler → ScoreTable 이름 변경하기
static 초기화 블럭
return scores.toArray(new Score[scores.size()]);
return scores.toArray(new Score[0]);
길이가 충분하지 않으면 toArray는 새로운 배열을 만들고
save(); 넣어주기
out.flush(); 습관 들이기
서버와 데이터를 입출력하기 위해서
ScoreHandler는 단순히 ArrayList에 저장하는 일을 하면 안 됨
데이터를 꺼내고 싶으면 서버에서 꺼내기
클라이언트에서 "insert"를 보내면 그 다음에 Score 객체를 보낼 거라고 생각하고 읽을 거
count를 꼭 받아야 합니까?
흉내내는 거
insert 한 개수
delete 한 개수
데이터를 먼저 보내면 출력 스트림을 먼저 꺼내라
한 번에 한 요청만 처리
스레드를 추가해야지 동시 접속이 가능
프로그램이 순차적으로 클라이언트 요청을 처리한다
전체적인 그림
옛날에는 직접 파일에 저장했는데 별도의 서버 프로그램으로 분리했다
데이터 공유
서버에서 기다리는 상태가 될 수 있음
서버에서 읽을 준비가 되어 있느냐
서로 맞대응이 되어야 함
블로킹 상태가 된다
서버에서 입력을 준비하면 클라이언트에서는 출력을 준비해라