본문 바로가기
백엔드/자바

자바 - 21 (노래 관리 프로그램 분리하기)

by study_yeon 2023. 5. 31.

2023.05.26

* 인터페이스를 통해 틀을 잡을 수 있음
- 클래스에서 실체화 및 구체화

 

* DAO

데이터베이스의 data에 접근하기 위한 객체입니다. DataBase에 접근 하기 위한 로직 & 비지니스 로직을 분리하기 위해 사용

* DTO

계층 간 데이터 교환을 하기 위해 사용하는 객체로, DTO는 로직을 가지지 않는 순수한 데이터 객체(getter & setter 만 가진 클래스)

* VO

값 오브젝트로써 값을 위해 쓰입니다. read-Only 특징(사용하는 도중에 변경 불가능하며 오직 읽기만 가능)을 가짐
DTO와 유사하지만 DTO는 setter를 가지고 있어 값이 변할 수 있다


* DTO (VO)
- DTO는 값의 변화 가능, 레코드 하나
- 데이터 클래스
 -멤버변수는 직접 접근 안됨(get, set 활용)
- DAO에서 DTO를 부품으로 사용한다 
- VO는 값의 변화를 줄 수 없음


● 데이터베이스 개념
레코드 = 튜플
표 = 엔티티?

 

* 스키마 

- 데이터베이스의 구조와 제약조건에 관해 전반적인 명세를 기술한 것
- 개체의 특성을 나타내는 속성(Attribute)과 속성들의 집합으로 이루어진 개체(Entity), 개체 사이에 존재하는 관계(Relation)에 대한 정의와 이들이 유지해야 할 제약조건들을 기술한 것


* 물리적스키마 = 데이터베이스

- 실세계에 존재하는 데이터들을 어떤 형식, 구조, 배치 화면을 통해 사용자에게 보여줄 것인가

 

* 논리적 스키마

- 데이터 베이스의 물리적 저장구조를 정의
- 구체적으로 개념 스키마를 디스크 기억장치에 물리적으로 구현하기 위한 방법을 기술한 것

- 데이터 베이스에 넣기 전에 체계적으로 정리


* 개념적 스키마 

- 데이터 베이스의 전체적인 논리적 구조
- 데이터 베이스에 실제로 어떤 데이터가 저장되었으며 데이터간의 관계는 어떻게 되는가


2023.05.25 - [백엔드/자바] - 자바 - 20 (노래 관리 프로그램 만들기)

이어서

 

※ 초기화 기본타입
정수 : 0
실수 : 0.0
boolean : false
문자 : '\u0000'
객체 : null

* 자바의 규칙
객체는 힙에 생성
객체는 참조변수
- 참조: 원본을 준다
참조하는 변수의 내용을 바꾸면 원본의 내용이 변경됨

 

● 하나의 클래스에 담기

 

▶ SongDAOMemoApp 클래스 만들기

 

▷ main()  - 시작부분으로 함수 호출

- getConnection()을 호출하여 마리아디비 서버에 연결

- 처리 후 디비 닫기

public static void main(String[] args) {
	// getConnection()을 호출하여 마리아디비 서버에 연결
	Connection con = getConnection();
		
	// 복잡한 비즈니스 로직 처리
	// 처리 후 DB연결 닫기
	// 연결된 연결객체 conn을 close 메소드로 닫기
	close(con);
		
	}

- main()이 getConnection를 호출
-> 실행결과 결과값으로 conn변수가 나옴

 

▷ 메소드 getConnection() - 디비와 연결해서 연결객체를 만들어 반환

	public static Connection getConnection() {
		// 실행되기전에 반드시 초기화(0에 가까운 값) 해야함
		Connection conn = null; // 캡슐화
		
		// 디비작업은 예외가정
		try {
			String db_driver_name = "org.mariadb.jdbc.Driver";
			// 해당 이름의 라이브러리가 있는지 검사
			// 라이브러리가 있으면 메모리로 실행코드 로딩
			Class.forName(db_driver_name);
			// DB 드라이버가 있다면 디비서버로 연결 | 없으면 SQL 예외처리
			// 프로티콜 구분기호는 항상 ://
			// 127.0.0.1은 로컬네트워크 주소
			String jdbc_url = "jdbc:mariadb://localhost:3306/muse";
			String user = "root";
			String passwd = "mariadb";
			
			conn = DriverManager.getConnection(jdbc_url, user, passwd);
			
		} catch(SQLException se) {
			
		} catch(Exception e) {
			// 배우는 중이므로 어떤 에러가 나는지 확인
			e.printStackTrace();
		}
		
		return conn; // 참조변수라서 객체의 주소를 넘김
		
	}

- getConnection은 Connection의 conn이라는 변수를 가짐 -> mariadb와 연결
- return conn; -> 컨넥션객체의 주소를 변수이름(conn)으로 리턴

* 메인의 참조변수와 메소드(메인 밖)의 참조변수가 다른 이름을 가져도 같은 힙의 주소를 가르키면
매소드가 종료되어도 메인이 가르키고 있기 때문에 힙의 객체는 사라지지 않는다 메인까지 종료되면 가비지콜랙터에 의해 사라진다

 

▷ 자원반납 - 닫기

	public static void close(Statement stmt, Connection conn) {
		// stmt 닫기
		try {
			stmt.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 	
		// conn 닫기
		try {
			conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 
	}
	
	public static void close(Connection conn) {
		try {
			conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} finally {
			try {
				if (conn != null) {	
					conn.close();
				}
			} catch (SQLException se) {
			
			}
		}
	}

 

▷ selectSongOne() : 레코드 1개 가져오기 
- try 위치
stmt 선언 전에 하여야 stmt를 내부에서 사용할 수 있음
-> 지역변수

- mariadb로 Select * from song limit 1; 문을 전송하여 결과레코드를 가져옴

	public static void selectSongOne(Connection conn) {
		Statement stmt = null;
		ResultSet rs = null; // 결과를 가지고 있는 2차원 배열
		
		try {
			String sql = "SELECT * FROM song limit 1;";
			
			stmt = conn.createStatement(); // factory pattern
			rs = stmt.executeQuery(sql);
			if (rs.next()) { // 값을 하나만 리턴하는 경우(limit 1에 의해 보장)
				// 레코드가 있다면
				int songId = rs.getInt("song_id");
				String songName = rs.getString("song_name");
				String songContent = rs.getString("song_content");
				String singer = rs.getString("singer");
				
				p("song_id = " + songId);
				p("\nsong_name = " + songName);
				p("\nsong_content = " + songContent);
				p("\nsinger = " + singer);
				pl("");
				
			} else {
				// 자료가 없다면
				pl("찾으시는 자료가 없습니다.");
			}
		} catch(SQLException se) {
			se.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

* rs.next()
- read only cursor
-> 메모리 방이 미리 만들어져야해서 배열 활용

 

rs의 데이터는 리턴값을 가지면 안되고
반환을 하고 메모리에 복사해야함
사용 완료하면 자원 반납

 

- 실행(main)

	public static void main(String[] args) { 
		// getConnection()을 호출하여 마리아디비 서버에 연결
		Connection con = getConnection();
        
        // 실행
		selectSongOne(con);
	}


▷ selectSong() : 전체값 가져오기 
- sql 외부에서 가져오기 (main내부에 작성 | 클래스 변수로 작성)
-> 매개변수 2개 필요

- 마리아디비 song테이블에 자료가져오기(풀쿼리이름)
-> SELECT * FROM `muse`.`song`
-> 현재는 JDBC_URL에 디비명이 들어가있으므로(`muse`를 가르킴) 짧은 이름으로 해도 된다

//	public static String sql = "SELECT * FROM `muse`.`song`;"; // 클래스변수
	public static void selectSong(Connection con, String selectSQL) {
		// SQL명령을 실행할 stmt 객체 필요
		Statement stmt = null;
		ResultSet rs = null; // 데이터베이스와 연결되어있음
		
		try {
			stmt = con.createStatement();
			rs = stmt.executeQuery(selectSQL);
			
			// 노래가 있는 경우
			if (rs.next()) { // 데이터가 있는지 확인
				do { // 값이 여러개가 오기 때문에 반복문 사용(do-while)
					int songId = rs.getInt("song_id");
					String songName = rs.getString("song_name");
					String songContent = rs.getString("song_content");
					String singer = rs.getString("singer");
					
					pl("");
					p("song_id = " + songId);
					p("\nsong_name = " + songName);
					p("\nsong_content = " + songContent);
					p("\nsinger = " + singer);
				} while(rs.next());
					
			} else {
				pl("등록된 노래가 없습니다.");
			}
		} catch(SQLException se) {
			se.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
		} 
	}

- 실행(main)

	public static void main(String[] args) { 
		// getConnection()을 호출하여 마리아디비 서버에 연결
		Connection con = getConnection();

		// 복잡한 비즈니스 로직 처리
		String sql = "SELECT * FROM `muse`.`song`;"; // 외부에서 sql쿼리 실행(지역변수)
		selectSong(con, sql);

* arraylist : 배열이 필요할때 사용

 


▷ insert / update / delete 실행

 

※ insert into - song 데이터 추가하기

	public static void insertSong(Connection con, String insertSQL) {
		Statement stmt = null;
		
		try {
			stmt = con.createStatement(); // factory pattern
			int insertCount = stmt.executeUpdate(insertSQL);
			
			if(insertCount > 0) {
				pl("" + insertCount + "자료를 추가했습니다.");
			} 
				
		} catch(SQLException se) {
			se.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

 update - song 수정

	public static void updateSong(Connection con, String updateSQL) {
		Statement stmt = null;
		
		try {
			stmt = con.createStatement(); 
			int updateCount = stmt.executeUpdate(updateSQL);
			
			if(updateCount > 0) {
				pl("" + updateCount + "자료를 수정했습니다.");
			} 
				
		} catch(SQLException se) {
			se.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

※  delete - song 삭제

	public static void deleteSong(Connection con, String deleteSQL) {
		Statement stmt = null;
		
		try {
			stmt = con.createStatement(); 
			int deleteCount = stmt.executeUpdate(deleteSQL);
			
			if(deleteCount > 0) {
				pl("" + deleteCount + "자료를 삭제했습니다.");
			} 
		} catch(SQLException se) {
			se.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}

- 실행(main)

 

	public static void main(String[] args) {
		// getConnection()을 호출하여 마리아디비 서버에 연결
		Connection con = getConnection();
		
		// insert 추가 
		String insertSQL = """
				INSERT INTO song
					(song_id, song_name, song_content, singer)
					VALUES
					(200, '좋은 날', '', '아이유');	""";
		// 호출(실행)
		insertSong(con, insertSQL);
		
		// update 수정
		String updateSQL = """
				UPDATE 
						`muse`.`song`
					SET 
						song_name = '아이유의 좋은 날',
						song_content = '좋은 날 2023ver',
						singer = 'IU'
					WHERE singer = '아이유';
				""";
		// 호출
		updateSong(con, updateSQL);
		
		// delete 삭제
		String deleteSQL = """
				DELETE FROM song
					WHERE singer = 'IU';
				"""; 
		// 호출
		deleteSong(con, deleteSQL);
		
		// 처리 후 DB연결 닫기
		// 연결된 연결객체 conn을 close 메소드로 닫기
		close(con);
		
	}

● SongDAODemoApp 분리


* 데이터 관련끼리 묶기
- package : song.model 만들기
- class : SongDAO, SongDTO 만들기

▷ SongDAO

- 마리아디비의 song테이블과 JDBC로 SQL실행 관리 클래스
- 하나의 클래스에 합친것과 달리 분리한 클래스는 내부에 변수를 선언해주어서 
- 매개변수를 넘겨줄 필요 없음 불필요한 변수선언을 하지 않아도 됨 

 

* SongDAO에 SongDAODemoApp 의 
- 컨넥션 객체 옮기기

package song.model;

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

public class SongDAO {

	// 멤버변수(전역변수)
	Connection conn;
	Statement stmt;
	ResultSet rs;
	
	// 데이터베이스 테이블의 레코드를 저장
	SongDTO songDTO;
	
	// 생성자
	public SongDAO() {
		this.conn = null;
		this.stmt = null;
		this.rs = null;
	}
	
	// 기능 메소드
	// 디비와 연결해서 연결객체를 만들어 반환하는 메소드
	public Connection getConnection() {
		// 실행되기전에 반드시 초기화(0에 가까운 값) 해야함
		Connection conn = null; // 캡슐화
		
		// 디비작업은 예외가정
		try {
			String db_driver_name = "org.mariadb.jdbc.Driver";
			// 해당 이름의 라이브러리가 있는지 검사
			// 라이브러리가 있으면 메모리로 실행코드 로딩
			Class.forName(db_driver_name);
			// DB 드라이버가 있다면 디비서버로 연결 | 없으면 SQL 예외처리
			// 프로티콜 구분기호는 항상 ://
			// 127.0.0.1은 로컬네트워크 주소
			String jdbc_url = "jdbc:mariadb://localhost:3306/muse";
			String user = "root";
			String passwd = "mariadb";
			
			conn = DriverManager.getConnection(jdbc_url, user, passwd);
			
		} catch(SQLException se) {
			
		} catch(Exception e) {
			// 배우는 중이므로 어떤 에러가 나는지 확인
			e.printStackTrace();
		}
		
		return conn; // 참조변수라서 객체의 주소를 넘김
		
	}

}


- close 옮기기

public class SongDAO {

	// 멤버변수(전역변수)
	Connection conn;
	Statement stmt;
	ResultSet rs;
	
	// 데이터베이스 테이블의 레코드를 저장
	SongDTO songDTO;
	
	// 생성자
	public SongDAO() {
		this.conn = null;
		this.stmt = null;
		this.rs = null;
	}
	
	// 기능 메소드
	public void close(ResultSet rs, Statement stmt, Connection conn) {
		// rs 닫기
		try {
			rs.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 
		// stmt 닫기
		try {
			stmt.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 	
		// conn 닫기
		try {
			conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		}  
	}
	
	public void close(Statement stmt, Connection conn) {
		// stmt 닫기
		try {
			stmt.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 
		// conn 닫기
		try {
			conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} 
	}
	
	public void close(Connection conn) {
		try {
			conn.close();
		} catch (SQLException se) {
			se.printStackTrace();
		} finally {
			try {
				if (conn != null) {	
					conn.close();
				}
			} catch (SQLException se) {
			
			}
		}
	}	
}

 

▷ SongDTO 

- SongDAO에서 사용하는 마리아디비의 song테이블의 데이터(열의 구조) 클래스 - 레코드와 다름
- 자바 데이터 클래스와 song테이블 사이의 1 : 1 관련성을 연결해주는 클래스 = 엔티티(Entity) 클래스
- 엔티티 클래스는 테이블의 열의 구조와 비슷하게 하면 됨
- 이름 규칙은 자바는 카멜, 데이터베이스는 스네이크 표기법을 권장

- 멤버변수는 private 처리하기

package song.model;

public class SongDTO {
	// 변수는 외부 접근 불허
	private int songId;
	private String songName;
	private String songContent;
	private String singer;
	
	// 생성자 
	public SongDTO() { // 기본생성자
		
	}
	public SongDTO(
		int songId,
		String songName,
		String songContent,
		String singer) {
			setAllMember(songId, songName, songContent, singer);
	}
	// 외부 접근 허용하기 위한 getter/setter메소드 생성(인가)
	public int getSongId() {
		return this.songId;
	}
	public String getSongName() {
		return this.songName;
	}
	public String getSongContent() {
		return this.songContent;
	}
	public String getSinger() {
		return this.singer;
	}
	public void setSongId(int songId) {
		this.songId = songId;
	}
	public void setSongName(String songName) {
		this.songName = songName;
	}
	public void setSongContent(String songContent) {
		this.songContent = songContent;
	}
	public void setSinger(String singer) {
		this.singer = singer;
	}
	
	// 모든 멤버를 한번에 추가하는 메소드 setAllMember()
	public void setAllMember(
			int songId, 
			String songName,
			String songContent,
			String singer ) {
		this.songId = songId;
		this.songName = songName;
		this.songContent = songContent;
		this.singer = singer;
	}
	
	@Override
	public String toString() {
		return " songId=" + songId + ", "
				+ "songName=" + songName 
				+ ", songContent=" + songContent 
				+ ", singer=" + singer;
	}
	@Override // 새로만들거나 개선
	public boolean equals(Object obj) {
		SongDTO other = (SongDTO)obj; // 다운캐스팅 
		// 모든 내용이 일치하게 하고 싶으니까 논리곱 && 활용
		// 정수형(int)은 == 사용 | String은 equals() 사용
		if ((this.songId == other.songId)&&  
			(this.songName.equals(other.songName)) &&
			(this.songContent.equals(other.songContent)) &&
			(this.singer.equals(other.singer))) { 
			// this == other
			return true;
		} else {
			// SongDTO가 다른 대상이다
			return false;
		}
	}

}


* 테이블은 열이 만드는 것
- 레코드가 만드는 것이 아님

** 의문점 
1. SongDAODemoApp
Q sql 외부에서 가져올때 외부로 빼야하는 것은 이해가 되는데 위치를 메인에서 선언해야하는 이유가 ?
A그냥 클래스 변수로 선언해도 문제 없음
Q 하나는 내부 하나는 외부에서 가져오기 가능한가
A 불가능, 내부에서 가져올거면 매개변수로 적을필요 없음

* 보안
인증이란?
- 유저가 누구인지 확인하는 절차, 회원가입하고 로그인 하는 것.
인가란?
- 유저에 대한 권한을 허락하는 것.
-> 동시에 진행되어야함

* DAO는 db로 접속, 질의, 레코드저장, 레코드를 저장할 메모리장소(DTO) 능력이 생김
- 레코드가 여러개 들어올 경우 저장하기 위한 장소(테이블)가 필요(DTO는 하나만 가능) 

- 테이블 생성
1) Array 배열활용(내가 정의한 범위만큼 활용) 
-> 넉넉하게 배열을 정하거나, 매번 수정이 필요 효율성이 떨어짐
2) ArrayList(데이터에 따라 사이즈가 변함) - 테이블의 역할을 함

 

* list map set의 실제구현
arraylist
hashmap
hashset



▷ SongDAOExamApp 만들기 (미완성)

1. 데이터베이스 관리객체를 이용하여 db관련 처리를 할 것.
SongDAO songDAO = new SongDAO();

2. songDAO객체가 마리아디비 서버로 연결해야함
songDAO.getConnection();


3. 명령객체를 생성해야함(stmt객체-insert, update등 에서 생성됨)
명령객체를 이용해서 원하는 SQL처리를 하면 됨
명령처리 결과로 ResultSet을 반환하면 화면에 보여줌(view 기능)

.

4. 명령수행이 마무리되었으면 연결객체 종료
songDAO객체는 디비와 연결을 정리함
프로그램이 종료된다.
songDAO.close();

 


* 상속
is 관계

- 부모 클래스(일반적인 개념)를 자식 클래스(구체적인 개념)에서 구체화할 때, 상속을 사용

 

has 관계 
- A와 B는 사용관계, 포함관계에 있을 때, 생성자 사용
- 부모객체는 B에 종속된다


 

 

'백엔드 > 자바' 카테고리의 다른 글

자바 - 23  (0) 2023.06.01
자바 - 22 (List - 리스트)  (0) 2023.05.31
자바 - 20 (노래 관리 프로그램 만들기)  (0) 2023.05.25
자바 - 19 (데이터베이스 연결)  (0) 2023.05.25
자바 - 18 (데이터베이스 연결)  (0) 2023.05.22