본문 바로가기
Java/네트워크 통신

[Java] 121. JDBC를 활용한 CRUD와 SOLID 원칙 (2)

글: Song hyun 2024. 6. 14.
728x90
반응형

[Java] 121. JDBC를 활용한 CRUD와 SOLID 원칙 (2)

콘솔을 활용한 퀴즈 게임 만들기 - 리팩토링하기
*https://whatsthatsound.tistory.com/337 에서 작성한 코드를 리팩토링해보자.
 
* 리팩토링= 코드를 보기 좋게 정리하는 것 or 성능을 향상시키는 것.
** DB 연결을 처리하는 클래스를 따로 분리하면, 재사용성과 유지보수성이 높아진다


(1) 리팩토링 1단계 : 클래스를 분리한다. (=DBConnectionManager)

*static {} 블록 - 정적 초기화 블록:
/클래스가 처음 로드될 때 한 번 실행된다. 정적 변수의 초기화나 복잡한 초기화 작업을 수행할 때 사용되며, static{} 블록 안에 예외를 던질 수도 있다.
 

  • 메서드는 따로 분리
  • 자주 쓰이는 객체는 클래스화

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;

import com.mysql.cj.x.protobuf.MysqlxPrepare.Execute;

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() limit 1 ";
	

	public static void main(String[] args) {
		
		try (Connection conn = DBConnectionManager.getConnection();
				Scanner scanner = new Scanner(System.in);) {

			while (true) {
				printMenu();

				// 블로킹 처리
				int choice = scanner.nextInt(); // 블로킹

				// 실행의 흐름
				if (choice == 1) {
					// 퀴즈 문제 추가
					// 사용자 퀴즈와 답을 입력받아야 함
					// connection을 활용해서 query를 날려야 한다.
					addQuizQuestion(conn, scanner);

				} else if (choice == 2) {
					// 퀴즈 문제 조회
					viewQuizQuestion(conn);
					
				} else if (choice == 3) {
					// 퀴즈 게임 시작
					playQuizQuestion(conn,scanner);
					
				} else if (choice == 4) {
					// 게임 종료
					System.out.println("게임을 종료합니다.");
					return;
					
				} else {
					System.out.println("잘못된 값입니다. 다시 입력해 주세요.");

				}

			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	} // 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.println("옵션을 선택하세요.");
	}


	private static void addQuizQuestion(Connection conn, Scanner scanner) {
		System.out.println("퀴즈 문제를 입력하세요: ");
		scanner.nextLine();
		String question = scanner.nextLine();
		System.out.println("퀴즈 정답을 입력하세요: ");
		String answer = scanner.nextLine();

		try (PreparedStatement pstmt = conn.prepareStatement(ADD_QUIZ)) {
			pstmt.setString(1,question);
			pstmt.setString(2, answer);
			int rowsInsertedCount=pstmt.executeUpdate();
			System.out.println("추가한 행의 수 : "+rowsInsertedCount);
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}
	
	private static void viewQuizQuestion(Connection conn) {
		try (PreparedStatement pstmt= conn.prepareStatement(VIEW_QUIZ);){
			ResultSet resultSet = pstmt.executeQuery(); // ResultSet을 반환한다.
			while(resultSet.next()) { // 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) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	

	private static void playQuizQuestion(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(answer)) {
					System.out.println("정답입니다! 점수를 얻었습니다.");
				} else {
					System.out.println("오답입니다.");
					System.out.println("퀴즈 정답 : "+answer);
				}
			} else {
				System.out.println("죄송합니다. 아직 퀴즈 문제를 만들고 있습니다.");
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
		
	}
	
	

}
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?serverTimezon=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);
	}
	
}

(2) 리팩토링 2단계 : SOLID 원칙에 따라 리팩토링 해보기

 
*SOLID 원칙이란?

  • 단일 책임 원칙 (Single Responsibility Principle/SRP): 클래스는 하나의 책임만 가져야 한다.
  • 개방-폐쇄 원칙 (Opne-Closed Principle/OCP): 소프트웨어 개체는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.
  • 리스코프 치환 원칙 (Liskov Substitution Principle/LSP): 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서, 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
  • 인터페이스 분리 원칙 (Interface Segregation Principle/ISP): 특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.
  • 의존성 역전 원칙 (Dependency Inversion Principle/DIP): 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.

 
1. 추상화하기 (인터페이스)

게임의 기능을 인터페이스화(좌측) 퀴즈를 클래스화(우측)

 

 
 
(1) QuizRepository (인터페이스)

package com.tenco.quiz.ver2;

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

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

}

 
(2) QuizDTO (구현클래스)

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;

}

 
(3) QuizRepository (인터페이스 상속 클래스)

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.ver1.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() 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 playQuizQuestion() 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);
			}
		} catch (Exception e) {
		}
		return quizDTO;
	}

}

 
(4) MainTest (시범 클래스)

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.playQuizQuestion();
			System.out.println(dto);
			System.out.println("정답을 맞춰주세요");
			System.out.println(dto.getQuestion());
			String userInput="한국";
			if(dto.getAnswer().equalsIgnoreCase(userInput)) {
				System.out.println("정답입니다");
			} else {
				System.out.println("오답");
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
		
	}

}
728x90
반응형