DB에서 회원 수가 늘어났을 때 아이디를 찾는 시간에 대한 문제에 대해서 배웠다.
DB에 저장된 데이터 량이 많아질 수록 데이터를 조회하는데 소요되는 시간이 길어지는데, 이 문제를 어떻게 해결할 것인가에 대한 문제다. 실제 네이버 회원수는 7000만명이 넘어가는 것으로 보이는데 우리가 회원가입을 하려고 할 때 특정 아이디를 치면 그 아이디를 사용할 수 있는지 없는지는 순식간에 판별된다. 이걸 어떻게 하느냐가 관건이다.
그걸 문제를 풀면서 생각했고 우리가 할 수 있는 방법은 UNIQUE INDEX이다. 데이터에 UNIQUE를 걸어준다는 것은 그 값은 유일하다는 것이고 중복이 있을까? 를 생각하며 데이터 전체를 들여다 볼 필요없이 유일한 값 하나만 찾으면 검색은 끝이 난다. 그리고 INDEX는 데이터를 노드화 시켜서 이진 트리 구조로 만들어 탐색을 원할하게 만든다. 데이터를 저렇게 처리하고 특정 값을 찾는 것과 그냥 찾는 것에는 횟수 차이가 많이 나게 된다. 따라서 값을 찾아내는 시간 또한 횟수가 적은 인덱스화 한 데이터가 더 짧다.
# 데이터베이스 a4가 존재하면 삭제
DROP DATABASE IF EXISTS a4;
# 데이터베이스 a4 생성
CREATE DATABASE a4;
# 데이터베이스 a4 선택
USE a4;
# 회원 테이블 생성, loginId, loginPw, `name`
## 조건 : loginId 칼럼에 UNIQUE INDEX 없이
CREATE TABLE `member`(
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
regDate DATETIME NOT NULL,
loginId char (100) NOT NULL,
loginPw char (200) NOT NULL, #복호화 대비로 값을 크게 잡음.
`name` char (100) NOT NULL
);
SELECT *
FROM `member`;
# 회원 2명 생성
## 조건 : (loginId = 'user1', loginPw = 'user1', `name` = '홍길동')
## 조건 : (loginId = 'user2', loginPw = 'user2', `name` = '홍길순')
INSERT INTO `member` (regDate, loginId, loginPw, `name`)
VALUES (NOW(), 'user1', 'user1', '홍길동');
INSERT INTO `member` (regDate, loginId, loginPw, `name`)
VALUES (NOW(), 'user2', 'user2', '홍길순');
# 회원 2배 증가 쿼리만들고 회원이 만명 넘을 때 까지 반복 실행
## 힌트1 : INSERT INTO `tableName` (col1, col2, col3, col4)
## 힌트2 : SELECT NOW(), UUID(), 'pw', '아무개'
INSERT INTO `member` (regDate, loginId, loginPw, `name`)
SELECT NOW(), UUID(), 'pw', '아무개-'
FROM `member`;
# 회원수 확인
SELECT COUNT(*)
FROM `member`;
# 검색속도 확인
## 힌트 : SQL_NO_CACHE
SELECT SQL_NO_CACHE *
FROM `member`
WHERE loginId = 'user1';
# 유니크 인덱스를 loginID 칼럼에 걸기
## 설명 : mysql이 loginId의 고속검색을 위한 부가데이터를 자동으로 관리(생성/수정/삭제) 한다.
## 설명 : 이게 있고 없고가, 특정 상황에서 어마어마한 성능차이를 가져온다.
## 설명 : 생성된 인덱스의 이름은 기본적으로 칼럼명과 같다.
ALTER TABLE `member` ADD INDEX(loginId);
# 인덱스 삭제, `loginId` 이라는 이름의 인덱스 삭제
DELETE FROM `member`
WHERE loginId = 'user1';
# 회원 테이블 삭제
DROP TABLE `member`;
# 회원 테이블을 생성하는데, loginId에 uniqueIndex 까지 걸어주세요.
CREATE TABLE `member`(
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
regDate DATETIME NOT NULL,
loginId char (100) NOT NULL,
loginPw char (100) NOT NULL,
`name` char (100) NOT NULL,
UNIQUE INDEX ux_loginId (loginId), INDEX ix_loginId (loginId)
);
이후 로그인, 아웃 기능을 만들었다.
public void doLogin() {
int i = 0;
while (true) {
System.out.println("== 로그인 ==");
System.out.print("로그인 아이디 : ");
String loginId = sc.nextLine().trim();
System.out.print("비밀번호 : ");
String loginPw = sc.nextLine().trim();
boolean isValidUser = memberService.doLogin(loginId, loginPw);
if (isValidUser) {
System.out.println("== 로그인 완료 ==");
return;
} else {
i++;
System.out.printf("== 로그인 실패 (로그인 시도 %d, 3번 실패시 out) ==\n", i);
if (i == 3) {
System.out.println("!!!! OUT OF USER !!!!");
return;
}
}
세번 실패시 강제 명령어 전환 기능도 넣었다.
public boolean doLogin(String loginId, String loginPw) {
return memberDao.doLogin(loginId, loginPw);
}
서비스에서 다오로,
public boolean doLogin(String loginId, String loginPw) {
SecSql sql = new SecSql();
sql.append("SELECT COUNT(*) > 0");
sql.append("FROM member");
sql.append("WHERE loginId = ? AND loginPw = ?;", loginId, loginPw);
return DBUtil.selectRowBooleanValue(conn, sql);
}
다오에서 DB로 쿼리를 날려줘서 데이터를 받아온다.
로그 아웃을 만들려면 로그인 헀을 때 로그인이 되어있다는 일종의 표식같은게 필요하다고 했었는데 그게 기억이 잘 안나서 예전 코드를 살펴봐야될 것 같다.
public void doLogin() {
if (isLogined()) {
System.out.println("이미 로그인 되어있음.");
return;
}
int i = 0;
while (true) {
System.out.println("== 로그인 ==");
System.out.print("로그인 아이디 : ");
String loginId = sc.nextLine().trim();
System.out.print("비밀번호 : ");
String loginPw = sc.nextLine().trim();
boolean isValidUser = memberService.doLogin(loginId, loginPw);
if (isValidUser) {
System.out.println("== 로그인 완료 ==");
loginedMember = memberService.isLogined(loginId);
System.out.println(loginedMember);
return;
} else {
i++;
System.out.printf("== 로그인 실패 (로그인 시도 %d, 3번 실패시 out) ==\n", i);
if (i == 3) {
System.out.println("!!!! OUT OF USER !!!!");
return;
}
}
}
}
public void doLogout() {
if (isLogined()) {
loginedMember = 0;
System.out.println("== 로그 아웃 완료 ==");
} else {
System.out.println("로그인 부터 해.");
}
}
private boolean isLogined() {
return loginedMember != 0;
}
}
예전 코드를 참고해서 힌트를 좀 얻어서 해결했다. 로그인할 때 완료후에 쿼리를 한번 더 쏴서 int값을 받아오는 것으로 로그인의 참과 거짓을 구별하게끔 만들었다.
이제 작성자를 글에 표시하는 것이다. 로그인을 했을 때만 글을 쓸 수 있게 해야되고, 글을 쓸 때 작성자 이름이 글에 남아야 하며, 글의 권한은 작성자만 가질 수 있도록 만들어야한다.
그 전에 회원 정보 상세보기를 만들다가 로그인도 강사님 코드로 고쳤다.
public void showMemberDetail() {
System.out.println("== 회원 정보 확인 ==");
String loginId = null;
String loginPw = null;
while (true) {
System.out.print("로그인 아이디 : ");
loginId = sc.nextLine().trim();
if (loginId.isEmpty() || loginId.contains(" ")) {
System.out.println("아이디 똑바로 써");
continue;
}
boolean isLoindIdDup = memberService.isLoginIdDup(loginId);
if (!isLoindIdDup) {
System.out.println(loginId + "는(은) 없어");
continue;
}
break;
}
Member member = memberService.getMemberByLoginId(loginId);
int tryMaxCount = 3;
int tryCount = 0;
while (true) {
if (tryCount >= tryMaxCount) {
System.out.println("비번 다시 확인하고 시도해");
break;
}
System.out.print("비밀번호 : ");
loginPw = sc.nextLine().trim();
if (loginPw.isEmpty() || loginPw.contains(" ")) {
tryCount++;
System.out.println("비번 똑바로 입력해");
continue;
}
if (!member.getLoginPw().equals(loginPw)) {
tryCount++;
System.out.println("일치하지 않아");
continue;
}
System.out.println(member.getName() + "님 정보 확인");
break;
}
System.out.println("번호 : " + member.getId());
System.out.println("아이디 : " + member.getLoginId());
System.out.println("이름 : " + member.getName());
System.out.println("가입 일자 : " + member.getRegDate());
System.out.println("정보 수정일자 : " + member.getUpdateDate());
}
}
로그인도 같은 매커니즘인데 로그인 아이디만 따로 가져와서 비밀번호만 칠 수 있게 고치고 싶은데 내 머리로는 다시 로그인 해서 대조하는 방법밖에는 떠올지 않는다.
그래서 고민중에 강사님이 공유자원을 컨테이너 클래스에 모두 옮기는 작업을 시작하셨다. 이러면 해결 가능할 것 같다. 일단 컨테이너 구조부터 살펴보자. 컨테이너를 도입함으로서 코드의 전반적인 거의 모든 클래스에서 수정이 필요했다.
겨우 다 했다.. 계속 오류가 생겨서 헤맸는데 컨테이너가 창고긴 해도 중요한거였다. 수정한 코드 전문을 올리면 너무 커져서 일단 내가 겼었던 문제만 말해보자면.
package org.koreait.container;
import org.koreait.controller.ArticleController;
import org.koreait.controller.MemberController;
import org.koreait.dao.ArticleDao;
import org.koreait.dao.MemberDao;
import org.koreait.service.ArticleService;
import org.koreait.service.MemberService;
import org.koreait.session.Session;
import java.sql.Connection;
import java.util.Scanner;
public class Container {
public static ArticleController articleController;
public static MemberController memberController;
public static ArticleService articleService;
public static MemberService memberService;
public static ArticleDao articleDao;
public static MemberDao memberDao;
public static Scanner sc;
public static Connection conn;
public static Session session;
public static void init(){
sc = new Scanner(System.in);
session = new Session();
articleDao = new ArticleDao();
memberDao = new MemberDao();
articleService = new ArticleService();
memberService = new MemberService();
articleController = new ArticleController();
memberController = new MemberController();
}
}
지금은 올바르게 작동하는 코드인데 밑의 init 메서드에서 Dao, Service Controller 위치가 뒤죽박죽이였다. 이거 때문에 프로그램이 제대로 실행되지 않았다. 이게 원인이란 걸 인지하기까지 오래 걸렸다. 아무튼 일단은 여기까지.
'Coding History' 카테고리의 다른 글
국비 지원 IT(웹앱개발) 취업반 강의 29일차 (DB, JDBC) (0) | 2024.07.16 |
---|---|
2024. 07. 15 JDBC 수정 삭제 권한 부여 (4) | 2024.07.15 |
2024. 07. 14. JDBC 리팩토링 (0) | 2024.07.14 |
JDBC 회원 기능 추가중 (1) | 2024.07.12 |
국비 지원 IT(웹앱개발) 취업반 강의 27일차 (DB, JDBC) (1) | 2024.07.12 |