JDBC를 활용한 CRUD 와 SOLID 원칙 - 7

목차

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

    1. DB, 테이블 설계
    2. 기본 데이터 입력(정규화)
    3. 자바측 라이브러리 설정
    4. 자바측 기능 구현 및 테스트
    5. 리팩토링
    -- 데이터베이스 생성
    
    create database quizdb;
    use quizdb;
    
    create table quiz(
    	id int AUTO_INCREMENT PRIMARY KEY,
        question varchar(500) not null,
        answer varchar(500) not null
    );
    
    desc quiz;
    
    -- 기본 샘플 데이터 입력
    insert into quiz(question, answer) 
                values ('대한민국의 수도는?', '서울'),
                        ('한반도 남쪽에 위치한 나라는?', '대한민국'),
                        ('세계에서 가장 높은 산은?', '에베레스트'),
                        ('태양계의 세 번째 행성은?', '지구'),
                        ('한국의 전통 명절 중 하나로, 음력 8월 15일에 해당하는 날은?', '추석'),
                        ('임진왜란 종전 년도?', '1598'),
                        ('고기압과 저기압에서 바람이 부는 방향은?', '고기압');

     

    2. 자바측 기본 코드 입력

    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 드리아버 로드 <-- 인터페이스 <--- 구현 클래스 필요 
    		try {
    			Class.forName("com.mysql.cj.jdbc.Driver");
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			return;
    		}
    		
    		try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
    			  Scanner scanner = new Scanner(System.in)) {
    			
    			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("잘못된 선택입니다. 다시 시도하세요.");
    				}
    			}
    			
    		} catch (Exception e) {
    			
    		}
    		
    	} // end of main 
    
    }

    3. 1단계 코드 구현 완료

    package com.tenco.quiz;
    
    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 드라이버 로드 <-- 인터페이스 <-- 구현 클래스 필요
    		try {
    			Class.forName("com.mysql.cj.jdbc.Driver");
    		} catch (ClassNotFoundException e) {
    			e.printStackTrace();
    			return;
    		}
    
    		try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); //
    				Scanner sc = new Scanner(System.in)) {
    			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 = sc.nextInt(); // 블로킹
    				sc.nextLine(); // 버그 방지용
    				switch (choice) {
    				case 1:
    					// 퀴즈 문제 추가 --> 함수로 만들기
    					addQuizQuestion(conn, sc);
    					break;
    				case 2:
    					// 퀴즈 문제 조회 --> 함수로 만들기
    					viewQuizQuestion(conn);
    					break;
    				case 3:
    					// 퀴즈 게임 시작 --> 함수로 만들기
    					playQuizGame(conn, sc);
    					break;
    				case 4:
    					// 종료
    					return;
    				default:
    					System.out.println("잘못된 선택입니다. 다시 시도하세요");
    					break;
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	} // end of main
    
    	
    	private static void playQuizGame(Connection conn, Scanner sc) {
    		// String sql = " select * from quiz order by rand() limit 1";
    		String sql = " select * from quiz";
    		try (PreparedStatement ptmt = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
    			ResultSet resultSet = ptmt.executeQuery();
    			resultSet.last();
    			int last = resultSet.getInt("id");
    			List<Integer> correct = new ArrayList<>(last); // 맞춘문제 row를 저장
    			while (true) {
    				int questionId; // 문제 row
    				// 랜덤으로 문제 row를 뽑고 맞춘 문제라면 다시 뽑기
    				while(true) {
    					questionId = new Random().nextInt(last) + 1;
    					if(!correct.contains(questionId)) {
    						break;
    					}
    				}
    				resultSet.absolute(questionId);
    				System.out.println("퀴즈 문제 : " + resultSet.getString("question"));
    				System.out.println("당신의 답 : ");
    				String userAnswer = sc.nextLine();
    				if(userAnswer.equalsIgnoreCase(resultSet.getString("answer"))) {
    					System.out.println("정답입니다.");
    					correct.add(questionId);
    					if (correct.size() == last) {
    						System.out.println("모든 문제를 풀었습니다.");
    						return;
    					}
    				} else if (userAnswer.equals("")){
    					System.out.println("퀴즈 게임 종료");
    					return;
    				} else {
    					System.out.println("오답입니다.");
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    
    	// 퀴즈를 추가하는 함수 만들기
    	public static void addQuizQuestion(Connection conn, Scanner sc) {
    		System.out.print("퀴즈 문제를 입력하세요 : ");
    		String question = sc.nextLine();
    		System.out.print("퀴즈 정답을 입력하세요 : ");
    		String answer = sc.nextLine();
    		
    		String sql = " insert into quiz(question, answer) values (?, ?) ";
    		try (PreparedStatement ptmt = conn.prepareStatement(sql)){
    			ptmt.setString(1, question);
    			ptmt.setString(2, answer);
    			int rowsInsertedCount = ptmt.executeUpdate();
    			System.out.println("추가된 행의 수 : " + rowsInsertedCount);
    			
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    		
    	}
    	
    	private static void viewQuizQuestion(Connection conn) {
    		System.out.println("");
    		String sql = " select * from quiz ";
    		try (PreparedStatement ptmt = conn.prepareStatement(sql)){
    			ResultSet resultSet = ptmt.executeQuery();
    			
    			while (resultSet.next()) {
    				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();
    		}
    	}
    
    }

    4. 코드 리팩토링 - 1단계

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

     

    1단계 핵심

    DBConnectionManager 클래스 만들어 보기

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

    package com.tenco.quiz;
    
    public class DBConnectionManger {
    	
    	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 {
    		
    		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;
    
    public class QuizGame {
    	
    	private static final String SELCELT_ALL = " select * from quiz ";
    	private static final String INSERT_QUESTION = " insert into quiz(question, answer) values (?, ?) ";
    
    	public static void main(String[] args) {
    
    		try (Connection conn = DBConnectionManger.getConnection(); //
    				Scanner sc = new Scanner(System.in)) {
    			while (true) {
    				System.out.println();
    				printMenu();
    
    				int choice = sc.nextInt(); // 블로킹
    				sc.nextLine(); // 버그 방지용
    				switch (choice) {
    				case 1:
    					// 퀴즈 문제 추가 --> 함수로 만들기
    					addQuizQuestion(conn, sc);
    					break;
    				case 2:
    					// 퀴즈 문제 조회 --> 함수로 만들기
    					viewQuizQuestion(conn);
    					break;
    				case 3:
    					// 퀴즈 게임 시작 --> 함수로 만들기
    					playQuizGame(conn, sc);
    					break;
    				case 4:
    					// 종료
    					return;
    				default:
    					System.out.println("잘못된 선택입니다. 다시 시도하세요");
    					break;
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	} // end of main
    
    	private static void printMenu() {
    		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 sc) {
    		try (PreparedStatement ptmt = conn.prepareStatement(SELCELT_ALL, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
    			ResultSet resultSet = ptmt.executeQuery();
    			resultSet.last();
    			int last = resultSet.getInt("id");
    			List<Integer> correct = new ArrayList<>(last); // 맞춘문제 row를 저장
    			while (true) {
    				int questionId; // 문제 row
    				// 랜덤으로 문제 row를 뽑고 맞춘 문제라면 다시 뽑기
    				while (true) {
    					questionId = new Random().nextInt(last) + 1;
    					if (!correct.contains(questionId)) {
    						break;
    					}
    				}
    				resultSet.absolute(questionId);
    				System.out.println("퀴즈 문제 : " + resultSet.getString("question"));
    				System.out.println("당신의 답 : ");
    				String userAnswer = sc.nextLine();
    				if (userAnswer.equalsIgnoreCase(resultSet.getString("answer"))) {
    					System.out.println("정답입니다.");
    					correct.add(questionId);
    					if (correct.size() == last) {
    						System.out.println("모든 문제를 풀었습니다.");
    						return;
    					}
    				} else if (userAnswer.equals("")) {
    					System.out.println("퀴즈 게임 종료");
    					return;
    				} else {
    					System.out.println("오답입니다.");
    				}
    			}
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    
    	// 퀴즈를 추가하는 함수 만들기
    	public static void addQuizQuestion(Connection conn, Scanner sc) {
    		System.out.print("퀴즈 문제를 입력하세요 : ");
    		String question = sc.nextLine();
    		System.out.print("퀴즈 정답을 입력하세요 : ");
    		String answer = sc.nextLine();
    
    		try (PreparedStatement ptmt = conn.prepareStatement(INSERT_QUESTION)) {
    			ptmt.setString(1, question);
    			ptmt.setString(2, answer);
    			int rowsInsertedCount = ptmt.executeUpdate();
    			System.out.println("추가된 행의 수 : " + rowsInsertedCount);
    
    		} catch (SQLException e) {
    			e.printStackTrace();
    		}
    
    	}
    
    	private static void viewQuizQuestion(Connection conn) {
    		System.out.println("");
    		try (PreparedStatement ptmt = conn.prepareStatement(SELCELT_ALL)) {
    			ResultSet resultSet = ptmt.executeQuery();
    
    			while (resultSet.next()) {
    				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();
    		}
    	}
    
    }

    5. 코드 리팩토링 - 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): 고수준 모듈은 저수준 모듈에 의존해서는 안되며, 둘 다 추상화에 의존해야 한다.
    package com.tenco.quiz.ver2;
    
    @Getter
    @Setter
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class QuizDTO {
    	private int id;
    	private String question;
    	private String answer;
    }
    package com.tenco.quiz.ver2;
    
    import com.tenco.quiz.DBConnectionManger;
    
    public class QuizRespositoryImpl implements QuizRespository{
    
    	public static final String SELCELT_ALL = " select * from quiz ";
    	public static final String SELCELT_RANDOM = " select * from quiz order by rand() limit 1 ";
    	public static final String INSERT_QUESTION = " insert into quiz(question, answer) values (?, ?) ";
    	
    	@Override
    	public int addQuizQuestion(String question, String answer) throws SQLException {
    		
    		int resultRowCount = 0;
    		
    		try (Connection conn = DBConnectionManger.getConnection()){
    			PreparedStatement pstmt = conn.prepareStatement(INSERT_QUESTION);
    			// 트랜잭션 처리 가능 - 수동모드 커밋 사용 가능
    			pstmt.setString(1, question);
    			pstmt.setString(2, answer);
    			resultRowCount = pstmt.executeUpdate();
    		} 
    		return resultRowCount;
    	}
    
    	@Override
    	public List<QuizDTO> viewQuizQuestion() throws SQLException {
    		List<QuizDTO> list = new ArrayList<>();
    		try (Connection conn = DBConnectionManger.getConnection()){
    			PreparedStatement pstmt = conn.prepareStatement(SELCELT_ALL);
    			ResultSet rs = pstmt.executeQuery();
    			while(rs.next()) {
    				list.add(new QuizDTO(rs.getInt("id"), rs.getString("question"), rs.getString("answer")));
    			}
    		} 
    		
    		return list;
    	}
    
    	@Override
    	public QuizDTO playQuizGame() throws SQLException {
    		QuizDTO quizDTO = null;
    		try (Connection conn = DBConnectionManger.getConnection()){
    			PreparedStatement pstmt = conn.prepareStatement(SELCELT_RANDOM);
    			ResultSet rs = pstmt.executeQuery();
    			if (rs.next()) {
    				quizDTO = new QuizDTO(rs.getInt("id"), rs.getString("question"), rs.getString("answer"));
    			}
    		}
    		return quizDTO;
    	}
    
    }
    package com.tenco.quiz.ver2;
    
    public class QuizRepositoryTest1 {
    	static final int ADD_QUESTION = 1;
    	static final int VIEW_QUESTION = 2;
    	static final int PLAY_GAME = 3;
    	static final int END = 0;
    	static int size = 0;
    	static Set<Integer> correct = new HashSet<>();
    	static boolean check = false;
    	
    	public static void main(String[] args) {
    		
    		
    		// 실행의 흐름은 직접 만들기
    		try (Scanner sc = new Scanner(System.in)){
    			QuizRespositoryImpl quizImpl = new QuizRespositoryImpl();
    			while (true) {
    				System.out.println();
    				printMenu();
    				int choice = sc.nextInt();
    				sc.nextLine();
    				switch (choice){
    				case ADD_QUESTION :
    					System.out.print("문제를 입력하세요. : ");
    					String question = sc.nextLine();
    					System.out.print("정답을 입력하세요. : ");
    					String answer = sc.nextLine();
    					quizImpl.addQuizQuestion(question, answer);
    					break;
    				case VIEW_QUESTION :
    					printQuiz(quizImpl.viewQuizQuestion());
    					check = true;
    					break;
    				case PLAY_GAME : 
    					if (size == 0) {
    						System.out.println("문제 조회를 먼저 해야합니다.");
    						break;
    					} else if (size == correct.size()) {
    						System.out.println("모든 문제를 풀었습니다.");
    						break;
    					}
    					QuizDTO quizDTO;
    					do {
    						quizDTO = quizImpl.playQuizGame();
    					} while (correct.contains(quizDTO.getId()));
    					playQuiz(quizDTO, sc);
    					break;
    				case END :
    					return;
    				}
    			}
    		} catch (Exception e){
    			e.printStackTrace();
    		}
    	}
    	private static void printMenu() {
    		System.out.println("---------------------------------------------");
    		System.out.println("1. 퀴즈 문제 추가");
    		System.out.println("2. 퀴즈 문제 조회");
    		System.out.println("3. 퀴즈 게임 시작");
    		System.out.println("0. 종료");
    		System.out.print("옵션을 선택하세요 : ");
    	}
    	private static void printQuiz(List<QuizDTO> list) {
    		size = list.size();
    		for (QuizDTO quizDTO : list) {
    			System.out.println(quizDTO);
    		}
    	}
    	private static void playQuiz(QuizDTO quiz, Scanner sc) {
    		System.out.println("문제 : " + quiz.getQuestion());
    		System.out.print("정답을 입력하세요 : ");
    		String answer = sc.nextLine();
    		if (quiz.getAnswer().equalsIgnoreCase(answer)) {
    			System.out.println("정답입니다.");
    			correct.add(quiz.getId());
    		} else {
    			System.out.println("오답입니다.");
    		}
    	}
    
    }

    자료 구조(Data Structure) - 4으로 돌아가기

     

    'Java > 자료구조' 카테고리의 다른 글

    JDBC에서의 예외 처리 - 9  (1) 2024.06.17
    JDBC 성능 최적화 - 8  (0) 2024.06.17
    JDBC 트랜잭션 관리와 배치 처리 - 6  (0) 2024.06.12
    JDBC 데이터 기본 조작(CRUD) - 5  (0) 2024.06.11
    JDBC 기본 사용법 - 4  (0) 2024.06.11