Java

2024.06.14 Data Structure(자료구조) JDBC를 활용한 CRUD 와 SOLID 원칙 - 7

정훈5 2024. 6. 14. 08:52

class_35.sql

my_quiz 프로젝트 --> src --> com.tenco.quiz 

 

class_35.sql
0.00MB

 

콘솔을 활용한 간단한 퀴즈 게임 만들어 보기

 

1. DB 테이블 설계

2. 기본 데이터 입력 (정규화)

3. 자바측 라이브러리 설정을 해야한다.

4. 자바측 기능 구현 및 테스트를 해야한다.

5. 리팩토링 

 

SQL

더보기
-- 데이터베이스 생성
create database quizdb;

-- 데이터 베이스 사용
use quizdb;

-- quiz 테이블 생성 --
create table quiz(
	id int auto_increment primary key,
    question varchar(500) not null,
    answer varchar(500) not null
);

-- quiz 테이블의 속성값 확인
desc quiz;

-- 기본 샘플 데이터 입력
Insert into quiz(question, answer)
	values ('대한민국의 수도는?', '서울'),
		('한반도의 남쪽에 위치한 나라는?', '대한민국'),
		('세계에서 가장 높은 산은?', '에베레스트'),
        ('태양계의 3번째 행성은?', '지구'),
        ('한국의 전통 명절 중 하나로 음력 8월 15일에 해당하는 날은?', '추석'),
        ('임진왜란 종전 년도는?', '1598'),
        ('고기압과 저기압에서 바람이 부는 방향은?', '고기압');

-- quiz 테이블의 모든 속성 및 데이터을 출력
select *
from quiz;

 

my_quiz 자바 프로젝트 라이브러리 설정

 

lombok 적용

https://wjdgns5.tistory.com/119

 

2024.05.27 Sprint Tool Suite lombok 적용

SpringToolSuite4에 lombok을 적용하는 방법 lombok 을 다운받는다.https://projectlombok.org/download Download projectlombok.org SpringToolSuite4에 들어와 폴더에 마우스 우클릭 후  lombok을 적용시킬 자바 프로젝트 폴

wjdgns5.tistory.com

mysql-connector 적용

https://wjdgns5.tistory.com/152

 

2024.06.10 JAVA 와 MySQL 연결셋팅 및 설정

mySQL 검색https://www.mysql.com/ MySQLMySQL HeatWave is a fully managed database service for transactions, real- time analytics across data warehouses and data lakes, and machine learning services, without the complexity, latency, and cost of ETL duplic

wjdgns5.tistory.com

 

패키지 이름을 com.tenco.quiz로 지정

 

 

자바 기본 코드

더보기
package com.tenco.quiz;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Scanner;

public class QuizGame {

	// 준비물 부터 준비
	private static final String URL = "jdbc:mysql://localhost:3306/quizdb?serverTimezone=Asia/Seoul";
	private static final String USER = "root";
	private static final String PASSWORD = "asd123";
	
	public static void main(String[] args) {
		
		// 필요한게 jdbc 드라이버 로드 --> 인터페이스 라서 바로 구현이 안됨 --> 구현 클래스가 필요
		// JDBC 드라이버 로드 <-- 인터페이스 <-- 구현 클래스 필요
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return;
		} // end of try - catch
		
		// try -catch - resource : 자원을 자동으로 닫아준다.
		try (
				Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
				Scanner scanner = new Scanner(System.in);
				){
			// C R U D 구현 
			while(true) {
				System.out.println();
				System.out.println("----------------------------------------");
				System.out.println("1번 퀴즈 문제 추가");
				System.out.println("2번 퀴즈 문제 조회");
				System.out.println("3번 퀴즈 게임 시작");
				System.out.println("4번 종료");
				System.out.print("옵션을 선택하세요: ");
				
				int choice = scanner.nextInt(); // 블록킹 
				
				if(choice == 1) {
					// 퀴즈 문제 추가 --> 함수로 만들기
					
				} else if(choice == 2) {
					// 퀴즈 문제 조회 --> 함수로 만들기
				} else if(choice == 3) {
					// 퀴즈 게임 시작 --> 함수로 만들기
				} else if(choice == 4) {
					// 퀴즈 종료
					System.out.println("프로그램을 종료합니다.");
					break;
				} else {
					// 그 외의 입력이 들어왔을 때
					System.out.println("잘못된 선택입니다. 다시 시도하세요.");
				}
				
			} // end of while() 
			
			
		} catch (Exception e) {
			
		} // end of try - catch
		
	} // end of main

 

 

 

1단계 코드 구현 완료

더보기
package com.tenco.quiz;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class QuizGame {

	// 준비물 부터 준비
	private static final String URL = "jdbc:mysql://localhost:3306/quizdb?serverTimezone=Asia/Seoul";
	private static final String USER = "root";
	private static final String PASSWORD = "asd123";
	
	public static void main(String[] args) {
		
		// 필요한게 jdbc 드라이버 로드 --> 인터페이스 라서 바로 구현이 안됨 --> 구현 클래스가 필요
		// JDBC 드라이버 로드 <-- 인터페이스 <-- 구현 클래스 필요
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			return;
		} // end of try - catch
		
		// try -catch - resource : 자원을 자동으로 닫아준다.
		try (
				Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
				Scanner scanner = new Scanner(System.in);
				){
			// C R U D 구현 
			while(true) {
				System.out.println();
				System.out.println("----------------------------------------");
				System.out.println("1번 퀴즈 문제 추가");
				System.out.println("2번 퀴즈 문제 조회");
				System.out.println("3번 퀴즈 게임 시작");
				System.out.println("4번 종료");
				System.out.print("옵션을 선택하세요: ");
				
				int choice = scanner.nextInt(); // 블록킹 
				
				if(choice == 1) {
					// 퀴즈 문제 추가 --> 함수로 만들기
					addQuizQuestion(conn, scanner);
				} else if(choice == 2) {
					// 퀴즈 문제 조회 --> 함수로 만들기
					viewQuizQuestion(conn); // 블록 잡아서 alt + shift + m 또는 ctrl + 1 클릭
				} else if(choice == 3) {
					// 퀴즈 게임 시작 --> 함수로 만들기
					playQuizGame(conn, scanner); // ctrl + 1 클릭
				} else if(choice == 4) {
					// 퀴즈 종료
					System.out.println("프로그램을 종료합니다.");
					break;
				} else {
					// 그 외의 입력이 들어왔을 때
					System.out.println("잘못된 선택입니다. 다시 시도하세요.");
				}
				
			} // end of while() 
			
			
		} catch (Exception e) {
			
		} // end of try - catch
		
	} // end of main

	private static void playQuizGame(Connection conn, Scanner scanner) {
		// 퀴즈 게임 시작 --> 함수로 만들기
		String sql = " select * from quiz order by rand() -- rand() limit 1 ";
		
		try(
				PreparedStatement pstmt = conn.prepareStatement(sql);
				ResultSet rs = pstmt.executeQuery();
				) {
					if(rs.next()) {
						String question = rs.getString("question");
						String answer = rs.getString("answer");
						
						System.out.println("퀴즈 문제 : " + question);
						// 버그처리
						scanner.nextLine();
						System.out.print("당신의 답 : ");
						String userAnswer = scanner.nextLine();
						
						if(userAnswer.equalsIgnoreCase(userAnswer)) { // equalsIgnoreCase : 대, 소문자 무시
							System.out.println("정답입니다! 점수를 얻었습니다.");
						} else {
							System.out.println("오답입니다! ");
							System.out.println("퀴즈의 정답은 -->" + answer);
						}
						
					} else {
						System.out.println("죄송합니다 아직 퀴즈 문제를 만들고 있습니다.");
					}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	} // end of playQuizGame

	private static void viewQuizQuestion(Connection conn) {
		// 2. Connection 객체를 활용해서 Query를 날려야 한다.
		String sql = " select * from quiz ";
		try {
			PreparedStatement psmt = conn.prepareStatement(sql);
			ResultSet resultSet = psmt.executeQuery();
			
			while(resultSet.next()) { // next 다음이 있니 없니 true false 
				System.out.println("문제 ID : " + resultSet.getInt("id"));
				System.out.println("문제 : " + resultSet.getString("question"));
				System.out.println("정답 : " + resultSet.getString("answer"));
				if(!resultSet.isLast()) {					
					System.out.println("----------------------------------------");
				}
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	} // end of viewQuizQuestion

	private static void addQuizQuestion(Connection conn, Scanner scanner) {
		// 1. 사용자 퀴즈와 답을 입력 받아야 하는 기능이 필요하다.
		System.out.println("퀴즈 문제를 입력하세요.");
		scanner.nextLine(); // 1번 누르고 enter 누르면 1 + \n이 실행되는데 여기서 걸려서 멈춘다.
		String question = scanner.nextLine();
		
		System.out.println("퀴즈 정답을 입력하세요.");
		String answer = scanner.nextLine();
		
		// 2. Connection 객체를 활용해서 Query를 날려야 한다.
		String sql = " insert into quiz(question, answer) values (?,?) ";
		
		try(
				PreparedStatement pstmt = conn.prepareStatement(sql);
				) {
					pstmt.setString(1, question);
					pstmt.setString(2, answer);
					int rowInsertedCount = pstmt.executeUpdate(); // 출력하려면 사용
					System.out.println("추가된 행의 수 : " + rowInsertedCount);
		} catch (SQLException e) {
			e.printStackTrace();
		} // end of try - catch
				
	} // end of addQuizQuestion()

	
	
	
	// 퀴즈를 추가하는 함수 만들기
	// 기능 만들기
	
	// 1. 사용자 퀴즈와 답을 입력 받아야 하는 기능이 필요하다.
	// 2. Connection 객체를 활용해서 Query를 날려야 한다.
	
	
	
	
	

} // end of class

 

코드 리팩토링 - 1단계

DB 연결을 처리하는 클래스를 따로 분리하면 재사용성 유지보수성이 높아집니다.

 

단계 핵심

DBConnectionManager 클래스 만들어 보기

데이터베이스 연결을 관리하는 DBConnectionManager 클래스를 만들어 봅시다.

 

더보기
package com.tenco.quiz;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnectionManager {

	
	private static final String URL = "jdbc:mysql://localhost:3306/quizdb?serverTimezone=Asia/Seoul";
	private static final String USER = "root";
	private static final String PASSWORD = "asd123";
	
	// static {} 블록 - 정적 초기화 블록이라고 한다.
	// 클래스가 처음 로드될때 한번 실행된다.
	// 정적 변수(static)의 초기화나 복잡한 초기화 작업을 수행할 때 사용할 수 있다.
	// static {} 블록안에 예외를 던질 수도 있다.
	static {
		
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}	
	} 
	
	// 정적 메서드(함수) 커넥션 객체를 리턴하는 함수를 만들어 보자
	public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection(URL, USER, PASSWORD);
	}
	

} // end of class

 

사용

더보기
package com.tenco.quiz.ver1;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

import com.tenco.quiz.DBConnectionManager;

public class QuizGame {
	
	private static final String ADD_QUIZ = " insert into quiz(question, answer) values (?,?) ";
	private static final String VIEW_QUIZ = " select * from quiz ";
	private static final String RANDOM_QUIZ = " select * from quiz order by rand() -- rand() limit 1  ";

	public static void main(String[] args) {
		
		// try -catch - resource : 자원을 자동으로 닫아준다.
		try (
				Connection conn = DBConnectionManager.getConnection(); // 자바프로그램의 
				Scanner scanner = new Scanner(System.in);
				){
			// C R U D 구현 
			while(true) {
				PrintMenu(); // ctrl + 1 
				
				int choice = scanner.nextInt(); // 블록킹 
				
				if(choice == 1) {
					// 퀴즈 문제 추가 --> 함수로 만들기
					addQuizQuestion(conn, scanner);
				} else if(choice == 2) {
					// 퀴즈 문제 조회 --> 함수로 만들기
					viewQuizQuestion(conn); // 블록 잡아서 alt + shift + m 또는 ctrl + 1 클릭
				} else if(choice == 3) {
					// 퀴즈 게임 시작 --> 함수로 만들기
					playQuizGame(conn, scanner); // ctrl + 1 클릭
				} else if(choice == 4) {
					// 퀴즈 종료
					System.out.println("프로그램을 종료합니다.");
					break;
				} else {
					// 그 외의 입력이 들어왔을 때
					System.out.println("잘못된 선택입니다. 다시 시도하세요.");
				}
				
			} // end of while() 
			
			
		} catch (Exception e) {
			
		} // end of try - catch
		
	} // end of main

	private static void PrintMenu() {
		System.out.println();
		System.out.println("----------------------------------------");
		System.out.println("1번 퀴즈 문제 추가");
		System.out.println("2번 퀴즈 문제 조회");
		System.out.println("3번 퀴즈 게임 시작");
		System.out.println("4번 종료");
		System.out.print("옵션을 선택하세요: ");
	}

	private static void playQuizGame(Connection conn, Scanner scanner) {
		// 퀴즈 게임 시작 --> 함수로 만들기
		
		try(
				PreparedStatement pstmt = conn.prepareStatement(RANDOM_QUIZ);
				ResultSet rs = pstmt.executeQuery();
				) {
					if(rs.next()) {
						String question = rs.getString("question");
						String answer = rs.getString("answer");
						
						System.out.println("퀴즈 문제 : " + question);
						// 버그처리
						scanner.nextLine();
						System.out.print("당신의 답 : ");
						String userAnswer = scanner.nextLine();
						
						if(userAnswer.equalsIgnoreCase(userAnswer)) { // equalsIgnoreCase : 대, 소문자 무시
							System.out.println("정답입니다! 점수를 얻었습니다.");
						} else {
							System.out.println("오답입니다! ");
							System.out.println("퀴즈의 정답은 -->" + answer);
						}
						
					} else {
						System.out.println("죄송합니다 아직 퀴즈 문제를 만들고 있습니다.");
					}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	} // end of playQuizGame

	private static void viewQuizQuestion(Connection conn) {
		// 2. Connection 객체를 활용해서 Query를 날려야 한다.
		try {
			PreparedStatement psmt = conn.prepareStatement(VIEW_QUIZ);
			ResultSet resultSet = psmt.executeQuery();
			
			while(resultSet.next()) { // next 다음이 있니 없니 true false 
				System.out.println("문제 ID : " + resultSet.getInt("id"));
				System.out.println("문제 : " + resultSet.getString("question"));
				System.out.println("정답 : " + resultSet.getString("answer"));
				if(!resultSet.isLast()) {					
					System.out.println("----------------------------------------");
				}
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
		}
	} // end of viewQuizQuestion

	private static void addQuizQuestion(Connection conn, Scanner scanner) {
		// 1. 사용자 퀴즈와 답을 입력 받아야 하는 기능이 필요하다.
		System.out.println("퀴즈 문제를 입력하세요.");
		scanner.nextLine(); // 1번 누르고 enter 누르면 1 + \n이 실행되는데 여기서 걸려서 멈춘다.
		String question = scanner.nextLine();
		
		System.out.println("퀴즈 정답을 입력하세요.");
		String answer = scanner.nextLine();
		
		// 2. Connection 객체를 활용해서 Query를 날려야 한다.
		try(
				PreparedStatement pstmt = conn.prepareStatement(ADD_QUIZ);
				) {
					pstmt.setString(1, question);
					pstmt.setString(2, answer);
					int rowInsertedCount = pstmt.executeUpdate(); // 출력하려면 사용
					System.out.println("추가된 행의 수 : " + rowInsertedCount);
		} catch (SQLException e) {
			e.printStackTrace();
		} // end of try - catch
				
	} // end of addQuizQuestion()


	// 퀴즈를 추가하는 함수 만들기
	// 기능 만들기
	
	// 1. 사용자 퀴즈와 답을 입력 받아야 하는 기능이 필요하다.
	// 2. Connection 객체를 활용해서 Query를 날려야 한다.
	
} // end of class

 

코드 리팩토링 - 2단계

QuizGame을 SOLID 원칙에 따라 리팩토링해보기

 

  1. 단일 책임 원칙 (Single Responsibility Principle, SRP)
    클래스는 하나의 책임만 가져야 합니다.

  2. 개방-폐쇄 원칙 (Open/Closed Principle, OCP)
    소프트웨어 개체는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 합니다.

  3. 리스코프 치환 원칙 (Liskov Substitution Principle, LSP)
    프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 합니다.

  4. 인터페이스 분리 원칙 (Interface Segregation Principle, ISP)
    특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.

  5. 의존성 역전 원칙 (Dependency Inversion Principle, DIP)
    고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.

 

DBConnectionManager.java

더보기
package com.tenco.quiz;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnectionManager {

	
	private static final String URL = "jdbc:mysql://localhost:3306/quizdb?serverTimezone=Asia/Seoul";
	private static final String USER = "root";
	private static final String PASSWORD = "asd123";
	
	// static {} 블록 - 정적 초기화 블록이라고 한다.
	// 클래스가 처음 로드될때 한번 실행된다.
	// 정적 변수(static)의 초기화나 복잡한 초기화 작업을 수행할 때 사용할 수 있다.
	// static {} 블록안에 예외를 던질 수도 있다.
	static {
		
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}	
	} 
	
	// 정적 메서드(함수) 커넥션 객체를 리턴하는 함수를 만들어 보자
	public static Connection getConnection() throws SQLException {
		return DriverManager.getConnection(URL, USER, PASSWORD);
	}
	

} // end of class

 

QuizRepository.java

더보기
package com.tenco.quiz.ver2;

import java.sql.SQLException;
import java.util.List;

public interface QuizRepository {
	
	int addQuizQuestion(String question, String answer) throws SQLException;
	
	// todo 수정 예정
	List<QuizDTO> viewQuizQuestion() throws SQLException;
	QuizDTO playQuizGame() throws SQLException;

}

 

QuizDTO.java

더보기
package com.tenco.quiz.ver2;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class QuizDTO {
	
	private int id;
	private String question;
	private String answer;
	
}

 

QuizRepositoryImpl.java

더보기
package com.tenco.quiz.ver2;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.tenco.quiz.DBConnectionManager;

public class QuizRepositoryImpl implements QuizRepository {
	
	public static final String ADD_QUIZ = " insert into quiz(question, answer) values (?,?) ";
	public static final String VIEW_QUIZ = " select * from quiz ";
	public static final String RANDOM_QUIZ = " select * from quiz order by rand() -- rand() limit 1  ";

	@Override
	public int addQuizQuestion(String question, String answer) throws SQLException {
		
		int resultRowCount = 0;
		
		
		try(
				Connection conn = DBConnectionManager.getConnection();
			) {
				PreparedStatement pstmt = conn.prepareStatement(ADD_QUIZ);
				// 트랜잭션 처리 가능 - 수동모드 커밋 사용 가능
				pstmt.setString(1, question);
				pstmt.setString(2, answer);
				pstmt.executeUpdate();
		}
		return resultRowCount;
	}

	@Override
	public List<QuizDTO> viewQuizQuestion()throws SQLException {
		
		List<QuizDTO> list = new ArrayList<>();
		
		try(
				Connection conn = DBConnectionManager.getConnection()
			) {
				PreparedStatement pstmt = conn.prepareStatement(VIEW_QUIZ);
				ResultSet rs = pstmt.executeQuery();
				while(rs.next()) {
					int id = rs.getInt("id");
					String question = rs.getString("question");
					String answer = rs.getString("answer");
					
					list.add( new QuizDTO(id, question, answer) );
				}
			
		} catch (Exception e) {
			// TODO: handle exception
		}
		
		return list;
	}

	@Override
	public QuizDTO playQuizGame()throws SQLException {
		
		QuizDTO quizDTO = null;
		
		try (Connection conn = DBConnectionManager.getConnection()) {
			PreparedStatement pstmt = conn.prepareStatement(RANDOM_QUIZ);
			ResultSet rs = pstmt.executeQuery();
			if(rs.next()) {
				
				int id = rs.getInt("id");
				String question = rs.getString("question");
				String answer = rs.getString("answer");
				quizDTO = new QuizDTO(id, question, answer);
			}
	
		} 
		
		return quizDTO;
	}
	
} // end of class

 

MainTest1.java

더보기
package com.tenco.quiz.ver2;

import java.sql.SQLException;
import java.util.List;

public class MainTest1 {

	public static void main(String[] args) {

		QuizRepositoryImpl quizRepositoryImpl = new QuizRepositoryImpl();
		try {
			
			List<QuizDTO> quizDtos = quizRepositoryImpl.viewQuizQuestion();
			
			for (QuizDTO quizDTO : quizDtos) {
				System.out.println(quizDTO);
			}

			QuizDTO dto = quizRepositoryImpl.playQuizGame();
			System.out.println(dto);

			System.out.println("정답을 맞춰 주세요");
			System.out.println(dto.getQuestion());
			String userInput = "대한민국";
			if (dto.getQuestion().equalsIgnoreCase(userInput)) {
				System.out.println("정답입니다.");
			} else {
				System.out.println("오답.");
			}

		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	} // end of main

} // end of class

package com.tenco.quiz.ver2;

public class QuizRepsoitoryTest1 {

	public static void main(String[] args) {
		
		// 메서드 호출해서 실행 확인 디버깅 확인 테스트 반드시 한다 !! 
		// QuizRepsoitory 구현 클래스 테스트 
		
		// 주말 과제 
		// 실행에 흐름 여러분 직접 만들어 보기 
	}

}