Coding 공부/DBMS

[DBMS_Example] Board_DBMS

CBJH 2024. 3. 21. 14:14
728x90
반응형

 

1. 연습문제 요약

 



1. 문제명 : 게시판(board) HashMap으로 만들었던 것을 SQL로 저장하기

2. 각 클래스 역할
 1) DatabaseUtil 클래스 >> 데이터베이스 기능 클래스(connect, disconnect, insert, search, update, delete 메서드)
 2) BoardMain 클래스 >> 메뉴 보여주기, 메뉴 고르면 메서드 호출하기
 3) BoardVO 클래스 : DB에서 받아온 정보 받아와 객체화하는 멤버(id, writer, passwd, subject, email)를 가진 클래스 
 4) BoardSVC 클래스 : 등록, 수정, 삭제, 목록, 검색 기능이 있는 클래스(메인에서 불러올 기능들)

3. 클래스 세부 설명
 1) BoardSVC 클래스 기능 설명
  1.1) 등록 >> 게시글 정보 받아와 DB로 전송하기
  1.2) 수정 >> 목록을 불러온 후 몇 번 인덱스 수정할지 물어보기 >> 비밀번호 물어봐서 맞다면>> writer, passwd, subject, email 중 어떤 항목 수정할지 물어보고 수정하기
  1.3) 삭제 >> 목록을 불러온 후 몇 번 인덱스 선택할지 물어보기 >> 비밀번호 물어봐서 맞다면 삭제하기
  1.4) 게시글 목록 보여주기 
  1.5) 검색 >> 제목, 작성자, 이메일 검색 기능 만들기
  (로그인 기능은 안해도 됨)

 2) BoardVO 클래스 수정사항
  2.1) 멤버(register, subject, email, passwd)를 사용해 데이터 베이스와 연결하기
  2.2) 기존 BoardVO register를 데이터베이스의 writer로 바꾸기
  2.3) 기존 BoardVO에 id를 추가해 글 작성때마다 인덱스로 사용하기.(pk값)

 3) DatabaseUtil 클래스
  3.1) 데이터베이스 기능을 담은 클래스.
  3.2) disconnect 메서드(추가 됨, connect 끊는 메서드) >> 매개 변수가 stmt, pstmt, con일때 각각 오버로딩하여 connect를 close()할 수 있는 메서드 만들기

4. 연습문제 목적 : 메인 클래스에서 메뉴 보여주고 메뉴를 고르면 메서드를 호출하도록 작성하기
모든 메뉴를 데이터 베이스와 연결하기

 

2. 깃 허브 주소

https://github.com/cbjh-4/Board_Database/

 

GitHub - cbjh-4/Board_Database

Contribute to cbjh-4/Board_Database development by creating an account on GitHub.

github.com

 

3. 코드 세부 내용 코멘트

  • DatabaseUtil, BoardMain, BoardVO, BoardSVC 총 4개의 클래스를 만들었다.
  • 각각의 클래스를 모듈화하여 기능을 세분화했다.
  • 기능을 세분화하다보니 코드를 좀 더 가독성 있고 간결화 할 수 있는 방법에 대해 생각해보는 기회가 되었다. (이전에 만들었던 코드들을 다시 보게되면서 쓸데없는 부분이나 private, public으로 메서드를 만들어서 관리하는 이유에 대해 생각하게 되었다.)

3.1 BoardMain 클래스

  • 깔끔하게 while문과 switch-case문만 사용해서 가독성을 높였다.

 

 

3.2 BoardVO 클래스

  • BoardSVC와 DataBaseUtil의 기능을 나누기 위해 DBMS에서 받아온 자료를 멤버에 저장해 객체화해서 값을 받아올 수 있게 테이블에 저장된 열의 이름들을 멤버로 갖는다.

  • SQL Developer에서 Board의 열 이름과 데이터 유형을 보면서 만들어야 데이터 유형 차이 때문에 생기는 오류를 막을 수 있다.
  • 앞으로 계속 확인하게 될 테이블 내용이다.

 

3.3 BoardSVC 클래스

package Board_DB;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Scanner;

public class BoardSVC {
	DatabaseUtil databaseUtil = new DatabaseUtil();
	Scanner sc = new Scanner(System.in);	
	static int menu;	
	
	public int selectMenu() {		
		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.println("6.종료");
		System.out.println("========================================");
		System.out.print("메뉴를 입력하세요: ");
		return Integer.parseInt(sc.nextLine());			
	}
	
	public void writeArticle() {
		BoardVO boardVO;
		while(true){
			System.out.println("게시판 등록창입니다.");
			System.out.print("작성자를 입력하세요(20byte 이내로 입력하세요.)>>");
			String writer = sc.nextLine();
			if(checkLength(writer,20)) continue;	
//			String id; //id는 작성할때마다 sql에서 자동으로 1씩 증가해 생성되므로 생성할 땐 필요없다.	
			System.out.print("passwd를 입력하세요(20byte 이내로 입력하세요.)>>");
			String passwd = sc.nextLine();
			if(checkLength(passwd,20)) continue;			
			System.out.print("제목을 입력하세요(50byte 이내로 입력하세요.)>>");
			String subject = sc.nextLine();
			if(checkLength(subject,50)) continue;
			System.out.print("email을 입력하세요(30byte 이내로 입력하세요.)>>");
			String email = sc.nextLine();
			if(checkLength(email,30)) continue;
			boardVO = new BoardVO(writer, subject, email, passwd);		//boardVO객체 생성
			break;		//여기까지 continue하지 않고 실행했다면 반복문 종료
		}		
		databaseUtil.connect();			//Connection 객체 연결
		databaseUtil.insert(boardVO);	//데이터베이스에 게시판 DB 생성		
	}
	
	private boolean checkLength(String inputString, int size) {
		byte[] bytesCheck;
		try {	            
			bytesCheck = inputString.getBytes("UTF-8");	// 문자열을 UTF-8 바이트 배열로 변환
            if(bytesCheck.length > size) {
				System.out.println("\n\n\n"+size+"byte를 초과했습니다. 처음부터 다시 입력하세요.");
				return true;
            }
        } catch (UnsupportedEncodingException e) { e.printStackTrace();} 	// 인코딩이 지원되지 않는 경우의 예외 처리
        return false;		//try-catch문 모두 통과하면 byte초과하지도 않았고 예외도 없으므로 false를 반환. 반복문 재실행 안함
	}
	
	public void printArticles() {	//DB에서 게시글 목록 출력
		databaseUtil.connect();		//Connection 객체 연결
		databaseUtil.select();		//데이터베이스 ResertSet으로 읽어와 sysout으로 출력		
	}
	
	public void removeArticles() {	//DB에 있는 게시글 삭제
		BoardVO boardVO = new BoardVO();
		printArticles();
		databaseUtil.connect();		//sizeOfArticles 실행을 위해 연결
		int size = databaseUtil.sizeOfArticles();
		if(size == 0) {
			System.out.println("게시글이 없으므로 종료합니다.");
			return;
		}
		System.out.println("작성된 게시글의 수: "+size);
		System.out.print("제거할 글의 인덱스를 입력하세요: ");
		int index = Integer.parseInt(sc.nextLine());
		databaseUtil.connect();
		databaseUtil.setArticle(boardVO, index);
		if(boardVO.getPasswd() == null) {		//게시글이 없으면 boardVO 생성자에서 값이 정해지지않아 null값을 반환
			System.out.println("인덱스: "+index+" 게시글이 존재하지 않습니다.");
			return;
		}
		System.out.print("제거할 게시글의 비밀번호를 입력하세요: ");
		System.out.print("비밀번호 : ");
		String passwd = sc.nextLine();
		if(boardVO.getPasswd().equals(passwd)) {
			databaseUtil.connect();				//Connection 객체 연결
			databaseUtil.deleteBoard(index);	//DB에 id와 비교해서 해당 게시글 삭제
		}else {System.out.println("비밀번호가 다릅니다.");}		
	}
	
	public void searchArticle() {
		System.out.println("=====================================");
		System.out.println("게시글 검색 창 입니다.");	
		System.out.print("검색어: ");
		String searchInfo = sc.nextLine();
		databaseUtil.connect();
		databaseUtil.searchBoard(searchInfo);				
	}
	
	
	public void changeArticle() {		
		BoardVO boardVO = new BoardVO();
		printArticles();
		databaseUtil.connect();		//sizeOfArticles 실행을 위해 연결
		int size = databaseUtil.sizeOfArticles();
		if(size == 0) {
			System.out.println("게시글이 없으므로 종료합니다.");
			return;
		}
		System.out.println("작성된 게시글의 수: "+size);
		System.out.print("제거할 글의 인덱스를 입력하세요: ");
		System.out.print("수정할 글의 인덱스를 입력하세요: ");
		int index = Integer.parseInt(sc.nextLine());
		databaseUtil.connect();
		databaseUtil.setArticle(boardVO, index);
		if(boardVO.getPasswd() == null) {		//게시글이 없으면 boardVO 생성자에서 값이 정해지지않아 null값을 반환
			System.out.println("인덱스: "+index+" 게시글이 존재하지 않습니다.");
			return;
		}
		System.out.print("수정할 게시글의 비밀번호를 입력하세요: ");
		System.out.print("비밀번호 : ");
		String passwd = sc.nextLine();		
		if(boardVO.getPasswd().equals(passwd)) {	//인덱스, 비밀번호 일치
			changeMenu(boardVO.getId());			//메뉴창 실행 + util의 update 실행 메서드 불러오기
		}else {System.out.println("비밀번호가 다릅니다.");}	
	}
	
	private void changeMenu(int id) {	//메뉴창 출력 및 DB에 정보 수정
		String boardInfo;
		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("5.종료");
		System.out.println("========================================");
		System.out.print("메뉴를 입력하세요: ");
		String menu = sc.nextLine();
		switch(menu) {
		case "1":
			System.out.print("작성자명 입력: ");
			boardInfo = sc.nextLine();
			databaseUtil.connect();
			databaseUtil.updateBoard("writer", boardInfo, id);
			break;
		case "2":
			System.out.print("제목 입력: ");
			boardInfo = sc.nextLine();
			databaseUtil.connect();
			databaseUtil.updateBoard("subject", boardInfo, id);
			break;
		case "3":
			System.out.print("이메일 입력: ");
			boardInfo = sc.nextLine();
			databaseUtil.connect();
			databaseUtil.updateBoard("email", boardInfo, id);
			break;
		case "4":
			System.out.print("비밀번호 입력: ");
			boardInfo = sc.nextLine();
			databaseUtil.connect();
			databaseUtil.updateBoard("passwd", boardInfo, id);
			break;
		case "5":
			System.out.println("게시글 수정 창 종료...");
			return;
	}
	}
}
  • 이제 메서드명을 가독성 있게 만드는 것도 조금씩 실력이 늘고 있다. 주석 없이도 남이 봤을 때 이해할 수 있도록 만드려고 노력하고 있다.
  • 멤버에 DatabaseUtil databaseUtil = new DatabaseUtil();를 객체화해서 메서드를 불러와 DBMS에 있는 데이터 정보를 불러오거나 수정하거나 새로 등록하거나 삭제한다.
  • DBMS에 있는 데이터 정보를 불러오기 위해서 BoardVO객체를 얕은 복사하여 DatabaseUtil에서 멤버를 수정하고 받아와 값을 비교했다.
  • connect()와 disconnect()를 언제 할 것인지에 대해 고민 했고, BoardSVC 클래스 내에서  DatabaseUtil의 인스턴스에 접근해서 연결과 리소스 해제를 하기로 했다.
  • 삭제, 수정 메서드에서 게시글이 없으면 삭제와 수정을 하지 않아야 되는 부분을 고민해봤다. >> DatabaseUtil에서 SQL문을 사용해 저장된 데이터가 몇개인지 확인 할 수 있어 그 부분을 추가해줬다.
  • int size = databaseUtil.sizeOfArticles();로 저장된 데이터가 몇개인지 정수형으로 받아와 size에 저장하고 활용했다.

 

 

3.4 DatabaseUtil 클래스

 

package Board_DB;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;

public class DatabaseUtil {	
	Connection con;			
	static {		//static 초기화 블록을 시작합니다. 이 블록 내의 코드는 클래스가 처음 로딩될 때 단 한 번만 실행됩니다.
		try {		//해당 드라이버가 있는지 확인
			Class.forName("oracle.jdbc.driver.OracleDriver");		//Class.forName 메소드를 호출하여 클래스를 메모리에 로드. JDBC 드라이버를 초기화하는 표준 방법 		
		}catch(ClassNotFoundException e) {		//해당 클래스를 찾을 수 없으면 예외처리
			e.printStackTrace();
		}
	}	
		
	public void insert(BoardVO boardVO) {
		PreparedStatement pstmt = null;
		try {
			System.out.println(boardVO);
			String sql ="INSERT INTO BOARD VALUES(board_seq.nextval,?,?,?,?)";
			pstmt = con.prepareStatement(sql);
			pstmt.setString(1, boardVO.getWriter());
			pstmt.setString(2, boardVO.getSubject());
			pstmt.setString(3, boardVO.getEmail());
			pstmt.setString(4, boardVO.getPasswd());
			
			int count = pstmt.executeUpdate();	//insert가 성공하면 0이 아닌 정수 값을 반환한다.
			if(count > 0){
				System.out.println("insert success..");
			}else {
				System.out.println("insert fail");
			}
		}catch(SQLException e) {
			System.out.println("insert fail...");
			e.printStackTrace();
		}finally{
			disconnect(con);
			disconnect(pstmt);
		}
	}
	
	public void select() {
		ResultSet rs = null;
		Statement stmt = null;
		try {
			String sql = "SELECT * FROM BOARD";
			stmt = con.createStatement();
			rs = stmt.executeQuery(sql);
//			if(!rs.next()) System.out.println("작성한 게시글이 없습니다!");			//게시글 있는지 체크
			while(rs.next()){	//ResultSet.nest(); 데이터를 한 줄 가져온다. 데이터가 없으면 false 반환
				System.out.println("인덱스: "+rs.getInt("id")+", 작성자: "+rs.getString("writer")+", 제목: "+rs.getString("subject")	//.getString() 매개변수에 몇번째 컬럼인지 인덱스를 적거나 id를 쌍따옴표 안에 적으면 된다.
				+", 이메일: "+rs.getString("email")+", 비밀번호: "+rs.getString("passwd"));
			}
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(stmt);
			disconnect(rs);
	}}
	
	public void setArticle(BoardVO boardVO, int id) {
		ResultSet rs = null;
		Statement stmt = null;
		try {
			String sql = "SELECT * FROM BOARD";
			stmt = con.createStatement();
			rs = stmt.executeQuery(sql);
			while(rs.next()) {	//ResultSet.nest(); 데이터를 한 줄 가져온다. 데이터가 없으면 false 반환
				int TempId = rs.getInt("id");
				if(TempId == id) {	//아이디가 같은 게시글을 찾아 boardVO의 멤버를 수정한다.
					boardVO.setId(rs.getInt("id"));
					boardVO.setWriter(rs.getString("writer"));
					boardVO.setSubject(rs.getString("subject"));
					boardVO.setEmail(rs.getString("email"));
					boardVO.setPasswd(rs.getString("passwd"));
				}
			}			
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(stmt);
			disconnect(rs);
		}				
	}		
	
	public void searchBoard(String searchInfo) {
		Statement stmt = null;
		ResultSet rs = null;
		BoardVO boardVO;
		try {
			String sql = "SELECT * FROM BOARD";
			stmt = con.createStatement();
			rs = stmt.executeQuery(sql);
			while(rs.next()) {	//ResultSet.nest(); 데이터를 한 줄 가져온다. 데이터가 없으면 false 반환
				String writer = rs.getString("writer");
				String subject = rs.getString("subject");
				String email = rs.getString("email");				
				if(searchInfo.equals(writer) || searchInfo.equals(subject) || searchInfo.equals(email)) {
					System.out.print(searchInfo+"로 검색한 결과값>> ");
					System.out.println("인덱스: "+rs.getInt("id")+", 작성자: "+rs.getString("writer")+", 제목: "+rs.getString("subject")	//.getString() 매개변수에 몇번째 컬럼인지 인덱스를 적거나 id를 쌍따옴표 안에 적으면 된다.
					+", 이메일: "+rs.getString("email")+", 비밀번호: "+rs.getString("passwd"));
				}
			}
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(stmt);
			disconnect(rs);
		}
	}
	
	public int sizeOfArticles() {	//작성된 게시글 숫자 반환 
		int size = 0;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			String sql = "SELECT COUNT(*) FROM BOARD";
			stmt = con.createStatement();
			rs = stmt.executeQuery(sql);
			rs.next();
			size = rs.getInt(1);// 1번째 컬럼의 값을 가져옵니다.
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(stmt);
			disconnect(rs);
		}		
		return size;
	}
	
	public void deleteBoard(int id) {
		PreparedStatement pstmt = null;
		try {
			String sql = "DELETE FROM BOARD where id = ?";
			pstmt = con.prepareStatement(sql);
			pstmt.setInt(1, id);
			int count = pstmt.executeUpdate();
			if(count>0) {System.out.println("DELETE 성공!");
			}else {System.out.println("DELETE 실패...");}			
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(pstmt);
		}
	}	
	
	public void updateBoard(String column, String boardInfo, int id) {
		PreparedStatement pstmt = null;				
		try {
			String sql = "UPDATE BOARD SET " + column + " = ? WHERE id = ?" ;	//SQL 문에서 UPDATE 구문을 사용할 때는 FROM 키워드를 사용하지 않는다.
			pstmt = con.prepareStatement(sql);									//UPDATE 구문에서는 컬럼 이름을 매개변수화해서 바인딩할 수 없다.
			pstmt.setString(1, boardInfo);
			pstmt.setInt(2, id);			
			int count = pstmt.executeUpdate();
			if(count>0) {System.out.println("UPDATE 성공!");
			}else {System.out.println("UPDATE 실패...");}			
		}catch(SQLException e) {e.printStackTrace();
		}finally {
			disconnect(con);
			disconnect(pstmt);
	}}		
	
	public void connect() {		
		con = null;
		try {
			con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE",	//오라클 드라이버 이름, 오라클 계정명, 오라클 비밀번호
					"c##test1", "1234");												//Oracle 데이터베이스에 c##test1유저로 연결을 시도
		}catch(SQLException e) {System.out.println("Connection 실패"); e.printStackTrace();}		
	}
	
	public void disconnect(Statement stmt) {        // Statement 자원 해제 메서드
        try {
            if (stmt != null) stmt.close();
        } catch (SQLException e) {System.out.println("Statement가 연결 해제 되지 않음"); e.printStackTrace();}
    }	
	public void disconnect(PreparedStatement pstmt) {        // PreparedStatement 자원 해제 메서드 오버로딩
        try {
            if (pstmt != null) pstmt.close();
        } catch (SQLException e) {System.out.println("PreparedStatement가 연결 해제 되지 않음"); e.printStackTrace();}
    }
	public void disconnect(Connection con) {        // PreparedStatement 자원 해제 메서드 오버로딩
        try {
            if (con != null) con.close();
        } catch (SQLException e) {System.out.println("Connection이 연결 해제 되지 않음"); e.printStackTrace();}
    }
	public void disconnect(ResultSet rs) {        // ResultSet 자원 해제 메서드 오버로딩
        try {
            if (rs != null) rs.close();
        } catch (SQLException e) {System.out.println("ResultSet이 연결 해제 되지 않음"); e.printStackTrace();}
    }
}

 

  • disconnect 메서드는 오버로딩하여 매개변수가 다를 때 close하도록 Statement, PreparedStatement, Connection, ResultSet 각각 만들어줬다.
  • 매개변수를 받지 않고 disconnect() 했을 때 각 리소스값이 null이 아니라면 close하는 방식으로 하나의 disconnect()메서드로 만들 수도 있다. 이렇게 하려면 Statement, PreparedStatement, Connection, ResultSet을 DatabaseUtil 클래스의 멤버로 만들어야 한다.
  • String sql = "UPDATE BOARD SET " + column + " = ? WHERE id = ?" ; //SQL 문에서 UPDATE 구문을 사용할 때는 FROM 키워드를 사용하지 않는다. UPDATE 구문에서는 컬럼 이름을 매개변수화해서 바인딩할 수 없다.  PreparedStatement를 사용해 SQL로 명령문을 보낼 때 주의하자.
  • public int sizeOfArticles() 메서드에서 SELECT COUNT(*) FROM 테이블명  >>명령문을 SQL로 보내서 첫번째 컬럼의 값을 ResultSet으로 가져와 테이블 안에 있는 자료의 행 갯수를 반환받았다.

 

 

4. Advanced...

  • 만약에 DatabaseUtil 클래스에 접근하는 Client가 많다면 값을 수정하는 중에 자원을 공유하는 것에 문제가 생길 수 있다. 따라서 DatabaseUtil 인스턴스를 싱글톤으로 하나만 만들어 모든 객체가 공유하는 것이 좋을 것 같다.
  • SQL명령문을 묶어서 하나의 작업으로 처리하려면 AutoCommit을 사용해 데이터의 정합성과 안정성을 부여할 수 있을 것이다.