JAVA 웹개발 과정 국비 22일차 정리

22일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Generics (제네릭스)

클래스나 메소드에서 사용할 내부 데이터 타입을 미리 지정하는 방법

1) 사용법

ArrayList<type> 변수명 = new ArrayList<>();

ArrayList<String> al = new ArrayList<>();

2) 왜 사용하는가

ArrayList raw 타입(제네릭스를 쓰지 않는 형태)의 데이터 타입은 배운대로 Object 형이다. 그래서 모든 타입의 값을 넣을 수 있다.
따라서 배열에 있는 값들이 모두 Object형으로 꺼내지기 때문에 다운캐스팅을 해줘야하는 불편함이 있다.
이 불편함을 해소하기 위해 제네릭스를 사용하는데, <>안에 타입을 명시해주면 그 타입의 값만 넣을 수 있게 된다.

  • 장점 : 다운캐스팅이 필요 없다.
  • 단점 : 다형성이 사라진다.

2. forEach문

for ( 변수 : 컬렉션 ) { }

기본 for문과 비슷하지만 다른 기능을 가지고 있다.
컬렉션의 처음부터 마지막까지 쭉 돌면서 요소를 하나씩 변수에 담아준다.
이 기능이 장점이자 단점이 되는데, 인덱스를 지정하지 못해서 범위를 정할 수 없기 때문에 기존 for문보다 제약이 따르지만 상황에 따라 forEach문이 더 편리하게 쓰일 수 있다.

3. 실습문제 - 카페 메뉴 관리 시스템

DB를 배우다 자바로 돌아온만큼 복습하는 차원에서 학생 관리 시스템과 비슷한 카페 메뉴 관리 시스템을 만들어 보았다.
만드는 과정에서 정리할만한 것들이 있어서 기록해둔다.

1) 제네릭스 사용

카페 메뉴 객체들을 하나씩 담을 변수들의 배열 ArrayList를 만들 때, 제네릭스를 사용한다.
menu형 배열은 크기를 미리 정해주어야 하는 단점이 있고, Raw타입 ArrayList는 다운캐스팅을 해줘야하는 번거로움이 있다.
따라서 가장 적절한 것은 ArrayList<Menu> 가 된다.

// Manager Class
private ArrayList<Menu> menus = new ArrayList<>();

2) MVC 패턴 설계

메뉴 정보를 ID를 검색하여 삭제하는 기능을 추가하려고 한다.
기존에는 for문으로 인덱스 값을 받아서 기능을 만들었다면, forEach문을 배웠으므로 이제 Menu형 ArrayList menus의 값을 변수에 담아 바로 꺼낼 수 있다.

나름 Manager 클래스와 Main 클래스에 나누어서 아래와 같이 코드를 작성했다.
메소드도 잘 나눴고, 입력한 ID가 없을 때 문구도 잘 출력되었다.
그러나, 아래 코드는 좋지 않은 코드이다.
데이터를 다루는 부분이 밖(Main)으로 나와버렸기 때문이다.

// Manager class
public void delMenus(Menu m) {
	this.menus.remove(m);
}
// Main class
}else if (menu==3) {
	
	System.out.print("삭제할 ID를 입력하세요. >> ");
	int rmId = Integer.parseInt(sc.nextLine());
	
	boolean existFlag = true;
	for(Menu m : manager.getMenus()) {
		if(m.getId()==rmId) {
			manager.delMenus(m);
			existFlag = false;
			break;
		}
	}
	if(existFlag) {
		System.out.println("입력한 ID를 가진 Menu가 없습니다. 다시 확인해주세요.");
	}

따라서 아래와 같이 Manager 클래스에 데이터를 다루는 코드를 모두 넣어준다.
입력한 ID가 없다면 0을 리턴하고, 삭제가 진행되었다면 1을 리턴하는 메소드를 만들어서 입력한 ID가 없을 때의 경우도 대비해준다.

// Manager Class
public int delMenus(int id) {
	for(Menu m : menus) {
		if(id==m.getId()) {
			menus.remove(m);
			return 1;
		}
	}
	return 0;
}

// Main Class
}else if (menu==3) {
	
	System.out.print("삭제할 ID를 입력하세요. >> ");
	int rmId = Integer.parseInt(sc.nextLine());

	if(manager.delMenus(rmId)==1) {
		System.out.println(rmId + "번의 메뉴가 삭제되었습니다.");
	}else {
		System.out.println("입력한 ID를 가진 Menu가 없습니다. 다시 확인해주세요.");
	}

3) ConcurrentModificationExcetion 에러

동시성 변경 예외
for문을 도는데 ArrayList 가운데 요소를 빼버릴 때 for문이 꼬이면서 나는 에러

아래는 ID를 검색해서 삭제하는 기능을 구현하는 메소드이다. for문에서 break를 써주지 않았더니, ConcurrentModificationExcetion 에러가 생겼다.
간단히 말해서 ArrayList에서 값을 remove했을 때, 자동으로 앞으로 땡겨지는데 이때 for문이 가리키는 변수와 충돌하면서 꼬이게 되는 상황에 에러를 발생시키는 것이다.
이 에러를 없애기 위한 해결 방법은 여러가지가 있다.

public void deleteMenu(int id) {
	for(Menu m : menus) {
		if(m.getId()==id)
			menus.remove(m);
			break;
	}
}

① break 추가

가장 단순한 방법이지만 사용하는 상황에 제약이 따른다.
조건을 만족하는 한 가지 값만 삭제할 때는 편리하지만, 여러 값을 삭제하는 상황에서는 사용할 수 없다는 단점이 있다.

② for문을 돌리는 대상과 지우는 대상을 분리

임시 저장소 tmp를 만들어 두 대상을 구분시켜주는 방법이다.
동시성 변경 예외는 for문을 도는 대상과 삭제할 요소가 있는 대상이 같을 때 발생한다.
따라서 동일한 대상인 menus를 임시저장소인 tmp에 저장시키고, tmp에서 forEach문으로 검색한 후 진짜 menus에서 삭제하면 된다.

public void deleteMenu(int id) {
	ArrayList<Menu> tmp = new ArrayList<>();
	for(Menu m : this.menus) {
		if(m.getId()==id) {
			tmp.add(m);
		}
	}
	for(Menu m : tmp) {this.menus.remove(m);}
}

③ CopyOnWriteArrayList 클래스

②번과 같이 동작하는 전용 리스트

강사님은 아직 어려운 개념이라고 하셨지만 공부하기위해 나름 구글링을 해서 찾아봤는데, 역시나 아직 무슨말인지 잘 모르겠어서 패스해야겠다.
나중에는 이해할 수 있겠지..

3. JDBC 시작!

2번까지는 그동안 배웠던 java를 예제를 풀면서 복습하는 차원에서 새로운 개념을 배웠고, 이제 자바와 DB를 연결하는 JDBC에 대해서 배워볼 차례이다.

1) JDBC(OJDBC)란?

데이터베이스에 연결 및 작업을 하기 위한 자바 표준 인터페이스.
Java와 DBMS가 서로 통신할 수 있게끔 만들어주는 라이브러리.
(Oracle) Java DataBase Connectibity

JDBC를 배우기 전까지는 학생 관리 시스템 같은 프로그램을 만들면 ArrayList에 정보를 저장할 수 밖에 없었다. 그 정보는 RAM에 저장되기 때문에 프로그램을 종료하면 모두 사라지는 단점을 가지고 있었다.
이제 ArrayList에 저장하는 대신 HDD에 정보를 저장하는 DB를 배웠고 이 DB에 정보를 저장하려고 하는데, 그 역할을 JDBC가 해주는 것이다.

쉽게 말하자면 여태까지는 SQL Developer에서 쿼리를 날렸다면, 지금부터는 자바프로그램으로 쿼리를 날리려고 하는 것이고 이 프로그램을 JDBC라고 한다.
자바에서 아무렇게나 데이터베이스에 접속할 수는 없다. JDBC는 일종의 라이브러리인데 사용하려면 라이브러리를 넣어야한다.
오라클을 다운받을 때 이미 자동으로 받아졌기 때문에 위치를 알면 라이브러리를 적용할 수 있다.

2) JDBC 환경설정

① 라이브러리 파일 위치 찾기 및 추가하기

나같은 경우는 아래 경로로 들어가면 파일을 찾을 수 있었다.

내PC > C: > oraclexe > app > oracle > product > 11.2.0 > server > jdbc > lib > ojdbc6.jar

라이브러리 파일을 추가하는 방법은 이전에 포스팅했었다.
새까맣게 까먹어서 다른 조원들의 도움을 받았다.. ㅋㅋ

② OJDBC 드라이버 로딩

oracle 패키지 밑에 jdbc 패키지 밑에 driver 패키지 밑에 OracleDriver 클래스의 인스턴스를 만든다.

Class.forName("oracle.jdbc.driver.OracleDriver");

위의 코드는 이 인스턴스를 OJDBC 쓰는 동안에 언제든 사용할 수 있게끔 셋팅해놔라 라는 명령이다.
new를 쓰지 않고 인스턴스를 만드는 방식으로 많이 쓰이는 방법은 아니지만 OJDBC는 이 방식으로 인스턴스를 만드는 것을 문법으로 제시해놔서 생소하지만 어쩔 수 없다. 지금은 외우자.
이 코드를 작성하고 나면 빨간 줄로 Unhandled exception type ClassNotFoundException예외가 난다. OracleDriver가 없으면 어떻게 할 건지 정해주지 않아서 생긴 예외이다.
이 클래스가 없는 경우는 라이브러리를 추가하지 않았거나, 버전이 달라서 위치가 달라졌기 때문이다. catch문이 작동한다면 적절한 원인을 찾아 조치해주자.
배운대로 try-catch문을 이용하여 만약 드라이버가 없다면 프로그램을 종료시켜준다.

try {
	Class.forName("oracle.jdbc.driver.OracleDriver");
}catch(Exception e) {
	System.out.println("OJDBC 드라이버를 발견하지 못함.");
	System.exit(0);
}

③ DBMS(Oracle)에 접속

SQL Developer로 따지자면 접속을 하는 과정이다. Java로 DBMS에 접근해야하기 때문에 SQL Developer에서 접속을 눌렀던 행동을 코드로 만들어주어야 한다.
아래와 같이 코드를 쓰고, Connection 클래스를 임포트 해준다.

Connection con = DriverManager.getConnection();

임포트를 끝내면, getConnection() 부분에 빨간 줄이 가면서 또 예외가 난다.
SQL Developer를 생각해주면 접속만 누른다고 해결되었던 것이 아니라, ID, PW를 써주고 데이터베이스 정보까지 써주고 접속을 눌러야 비로소 접속을 할 수 있었다. 이 부분을 구현해주어야 하는 것이다.

getConnection 메소드는 url, id, pw 이렇게 세가지 파라미터를 요구한다.
만들어뒀던 ID와 PW를 그대로 써준다.
URL은 아직은 이해가 안되겠지만 그대로 외워준다.
@전까지는 고정값이라고 생각하면 되고, 그 이후는 가변값인데 localhost는 쉽게말해서 내PC이다. 나중에 프로젝트를 할 때는 이 부분에 DB를 가지고 있는 조원의 IP를 넣어준다. 일단 외우자.
그리고 getConnection 메소드에 파라미터로 넣어준다.

String url = "jdbc:oracle:thin:@localhost:1521:xe";
String username = "kh";
String password = "kh";

Connection con = DriverManager.getConnection(url, username, password);

그러면 또 ②번 상황과 똑같이 DriverManager 클래스부터 뒤쪽으로 빨간 줄로 예외가 쭉 생긴다. 마찬가지로 해당 클래스가 없을 때 어떻게 할 것인지를 정해주지 않아서이다.
동일하게 try-catch문으로 감싸준다.

String url = "jdbc:oracle:thin:@localhost:1521:xe";
String username = "kh";
String password = "kh";

try {
	Connection con = DriverManager.getConnection(url, username, password);
}catch(Exception e) {
	e.printStackTrace();
	System.out.println("접속에 실패");
	System.exit(0); 
}

그런데 예외가 생겼을 때 그냥 catch문으로 넘어가서 프로그램을 종료하고 끝내버리면 어떤 것 때문에 예외가 생겼는지 알 수가 없다.
Exception은 클래스로, 예외가 생겼을 때 변수 e는 catch문으로 점프하면서 에러 정보값들을 e에 담고 넘어온다. 변수 e에 printStackTrace메소드를 써주면 예외가 생긴 이유를 출력할 수 있다.
개발 단계에서는 e.printStackTrace()를 항상 써주도록 하자.

④ 쿼리를 전달할 수 있는 인스턴스를 생성

Statement는 쿼리를 전달하는 OJDBC 라이브러리 전용 객체이다. Statement 인스턴스를 생성해야 쿼리를 전달할 수 있는데, Connection 객체의 메소드로부터 Statement를 만들어낼 수 있다.

Statement stat = con.createStatement();

Statement 인스턴스를 생성했으므로 이제 쿼리를 전달할 수 있게 된다.
쿼리를 전달하는 방법은 여러개가 있지만 그 중 두가지를 먼저 배워보자.

ⓐ stat.executeUpdate(String sql) : int

DB 내용을 변경하는 기능 (INSERT, UPDATE, DELETE)

리턴 값이 int인 이유는, SQL Developer를 이용해서 DB 내용을 변경할 때 ‘1행이 수정/삭제/업데이트 되었습니다.’ 하고 출력되는 이 숫자 1 을 가리키는 것이다.

ⓑ stat.executeQuery(String sql) : ResultSet

DB 내용에 변화를 주지 않는 기능 (SELECT)

리턴 값이 ResultSet인 이유는 SELECT로 출력하는 데이터들을 보여주기 때문이다.

이 두 메소드는 바꿔서 써도 작동이 안되는 것은 아니지만 리턴값이 원하는대로 나오지 않을 수 있기 때문에 맞춰서 써주어야 한다.
왜 DML에 관한 메소드만 있고 DDL에 관한 메소드는 없을까?
Developer에서 DB를 셋팅해놓고 자바에서는 DML만 만지기 때문에 굳이 필요 없다.

전달할 sql문을 변수에 받아주고, 메소드를 사용한다.
메소드의 리턴값을 result로 받아주면 결과값을 출력하여 확인할 수 있다.

Statement stat = con.createStatement();
String sql = "INSERT INTO cafe_menu VALUES(cafe_menu_seq.NEXTVAL,'Cafe Mocha',3500)";
int result = stat.executeUpdate(sql);
System.out.println("결 과 : " + result);

if(result>0) {
	System.out.println("성공적으로 입력되었습니다.");
}else {
	System.out.println("입력에 실패했습니다.");
}

⑤ 커밋과 접속 종료

DML을 사용하고 난 후에는 꼭 커밋을 해주는 습관을 들이자.
또한 오라클은 커넥션을 무한대로 받을 수 없기 때문에 접속을 끊지 않으면 어느순간 접속이 되지 않는 현상이 발생한다.
접속을 끊어주지 않으면 여기저기서 접속이 누적되어 어느 순간 오라클이 받을 수 있는 접속을 초과할 수 있다.
커밋과 접속 종료 둘 다 Connection 변수 con의 메소드로 써 줄 수 있다.

con.commit();
con.close();

⑥ 예제 - Delete 쿼리문 전달해보기

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

public class Exam_02_Delete {
	public static void main(String[] args) {

		// 2.OJDBC 드라이버 로딩
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
		}catch (Exception e) {
			System.out.println("OJDBC 드라이버를 발견하지 못함");
			System.exit(0);
		}

		// 3.DBMS(Oracle)에 접속
		String url = "jdbc:oracle:thin:@localhost:1521:xe";
		String username = "kh";
		String password = "kh";

		try {
			Connection con = DriverManager.getConnection(url, username, password); 
			// 4.쿼리를 전달할 수 있는 인스턴스를 생성
			Statement stat = con.createStatement();
			String sql = "DELETE FROM cafe_menu WHERE id=1006";
			int result = stat.executeUpdate(sql);
			System.out.println("결 과 : " + result);

			if(result>0) {
				System.out.println("성공적으로 삭제되었습니다.");
			}else {
				System.out.println("삭제에 실패하였습니다.");
			}
			// 5.커밋과 접속 종료
			con.commit();
			con.close();
		}catch(Exception e) {
			e.printStackTrace();
			System.out.println("접속에 실패");
			System.exit(0);
		}
	}
}

댓글남기기