백엔드/자바

자바 - 18 (데이터베이스 연결)

study_yeon 2023. 5. 22. 22:11

2023.05.22 수업

 

* ctrl shift F : 들여쓰기
* ctrl shift Y : WordWrap

 


● 클래스 : H2ConnectionDemoApp

▶ 멤버 변수 (main 밖의 영역 : 전역변수) 4개

static final String JDBC_DRIVER = "org.h2.Driver";
	static final String DB_URL = 
			"jdbc:h2:C:\\app\\database\\h2\\java2\\java2;MODE=MariaDB;DATABASE_TO_UPPER=false";
	static final String USER = "";
	static final String PASS = "";

- 파이널(상수)은  생성자를 이용하여 초기화하지 않아도 됨
- 스네이크 표기법은 상수이름에 주로 사용 나머지는 카멜

※ JDBC(Java DataBase Connectivity)
· JDBC : 자바로 데이터베이스에 연결하여 데이터를 다루는 기술 스펙
· 데이터베이스 회사에서 JDBC라이브러리를 제작 제공, 또는 전문소프트웨어 회사에서 라이브러리로 판매
· DRIVER : 소프트웨어 라이브러리
· JDBC Driver의 이름은 최상위도메인.데이터베이스명.Driver의 작명 을 권장
ex) org.h2.Driver
· JDBC 프로토콜 : 자바와 데이터베이스가 JDBC라이브러리를 통하여 통신하는 통신규약을 가르킴
· JDBC프로토콜 이름은 JDBC:서버주소:포트명:데이터베이스
ex) jdbc:h2:C:\\app\\database\\h2\\java2
· 유저 ID / PW : 아무것도 안적으면 누구나 접근 가능
- 관리자 등의 ID / PW가 설정되어야함

* 포트명
웹 80
오라클 1521
MySQL 3306
H2 : 파일DB라 없음

 

▶ 데이터베이스에 접근하는 순서 (mian함수 내용)
1. 데이터베이스 연결(접근)
- 네트워크를 통해 데이터베이스 서버와 연결

public static void main(String[] args) {
	// 1. 데이터베이스 연결(접근)
	Connection conn = null;
}

 

2. Statement 명령객체 생성
- Statement 보안문제 위험
- PreparedStatement : 보안문제 해결

Statement stmt = null;

 

3. 데이터베이스 프로그래밍은 항상 에러에 대비해야함(try catch)

try {
	// 실행부분(예외가 발생할 가능성이 있는 문장)
} catch(SQLException se) {
	// SQLException예외가 발생한 경우 처리하기 위한 문장
} catch(Exception ex) {
	// Exception예외가 발생한 경우 처리하기 위한 문장
} finally {  // 무조건 실행(캐치하지 못한 에러 처리부분)
	try {
    } catch(SQLException se) {

	} catch(Exception ex) {

	} 
   
}

 

▷ try 실행부분

 

3-1. JDBC_DRIVER연결

- JDBC_DRIVER라이브러리가 있는가?

Class.forName(JDBC_DRIVER);
pl("JDBC드라이버 로딩 성공...");

 

3-2. DB로 연결생성
- JDBC 드라이버는연결관리를 DriverManager클래스에서 함.
- DriverManager.gerConnection() 함수이용 : DB로 연결하여 연결객체생성(서버와 클라이언트를 연결)

pl("DB서버로 연결중...");
conn = DriverManager.getConnection(DB_URL, USER, PASS); // ()의 정보를 넘겨줌
pl("데이터베이스 서버에 접속 성공!");

 

3-3. DB연결 객체를 이용하여 createStatement() 메소드를 DB서버에 SQL명령어를 전송하는 객체 생성(정적쿼리)

stmt = conn.createStatement(); // DB에 SQL쿼리를 보낼 Statement(운송수단) 만들기

 

conn : 도로
stmt : 운송차량

 

3-4. SQL명령어 문자열 입력하여 데이터베이스 전송실행

- statement에 실어보낼 SQL쿼리 문자열 생성

 

3.4.1) executeUpdate()
insert, update, delete등 리턴 값이 필요 없는 쿼리문일 때 사용(명령을 실행한 결과가 나올때)
  stmt.executeUpdate(SQL);

3.4.2) executeQuery()
select등 리턴 값이 필요한 쿼리문일 때 사용
  stmt.executeQuery(SQL);

3.4.3) 쿼리실행 후 값을 받아올 경우
  ResultSet rs = null;
  rs = stmt.executeQuery();

 

4. 레코드처리(선택적)


5. DB 연결종료

- 생성한 역순으로 반납

stmt.close();
conn.close();

● 디버그
중단점 Connection conn = null;

오류1) 프로젝트를 변경하여 라이브러리가 연결되지 않았음 



* 라이브러리는 공통으로 사용하기 때문에 프로젝트 밖에 만든다 

- lib 폴더를 만들기(경로 - C:\app\java\lib)

- 프로젝트명 > 오른쪽마우스 > BuildPath > libraries

  오른쪽탭 > Add ExternalJARs...

 


오류2) NullPointerException 

Statement stmt = null; 비어있음
conn.createStatement(); 이 주소를 stmt가 받아야함
-> stmt = conn.createStatement();

* createStatement() 구문 뜻 

 


● CRUD

테이블을 만들고 데이터 집어넣기
테이블에 있는 데이터 가져오기
업데이트 및 삭제

 

○ CREATE TABLE

String createSQL =
	"DROP TABLE IF EXISTS member;" +  // stmt를 여러번 사용할 필요 없음(멀티라인)
	"CREATE TABLE member(" + 
	"id    integer not null," +
	"first varchar(100)," +
	"last  varchar(100)," +
	"age   integer," + 
	"primary key(id)" + 
	");";
stmt.executeUpdate(createSQL); // execute 내용을 실어라

* DDL은 처음 한번만 사용하는 것이 원칙
DROP : 데이터가 다 사라짐(함부로 사용하지 말 것)
- 연습중에 테이블락으로 에러가 발생하니 임시적으로 DROP을 활용함
에러문구 : Table "member" already exists; 


* stmt 여러번 사용 멀티SQL
2줄짜리 SQL (멀티라인)
stmt를 여러번 사용할 필요 없이 String createSQL = 에 추가하기

 

* SQL이 후 디버그 확인 
중단점 : String createSQL


 DML 4대명령 활용
select insert update delete

▷ 테이블에 데이터 추가(INSERT INTO)
* 1~3번 자료추가

// 1번 자료추가
String insertSQL = "" + 
	"INSERT INTO member " +
	"   (id, first, last, age) " + 
	"   VALUES (" + 
	"      101, 'Zara', 'Ali', 18); ";
stmt.executeUpdate(insertSQL);
// 2번 자료추가
insertSQL = "" +
	"INSERT INTO member " +
	"   (id, first, last, age) " + 
	"   VALUES( " +
	"      102, 'Mahnaxz', 'Fatma', 25);";
stmt.executeUpdate(insertSQL);
// 3번 자료추가
insertSQL = "" +
	"INSERT INTO member " + 
	"   (id, first, last, age) " +
	"   VALUES(" +
	"      103, 'Sumit', 'Mittal', 28);";
    
stmt.executeUpdate(insertSQL);

* INSERT SQL문 실행

- stmt.executeUpdate(insertSQL);


* 디버그

중단점 : String insertSQL 
-> primary key에 의해 다시 실행하면 오류가 발생하나 
DROP문을 작성해둬서 정상작동


▷ 입력된 결과내용 가져오기 (SELECT)


1) select 문자열 생성

String selectSQL = 
		"""
					
			SELECT * FROM member
		""";
		pl(selectSQL);


2) stmt.executeQuery()

ResultSet rs = stmt.executeQuery(selectSQL); // 결과값 리턴(result set)

 

3) 위의 실행결과 반드시 레코드 집합
4) 쿼리실행 후 값을 받아옴

while(rs.next()) { 
	// Retrieve by column name
	int id = rs.getInt("id"); // 매개변수로 칼럼명 또는 칼럼인덱스가 옴
	int age = rs.getInt("age");
	String first = rs.getString("first");
	String last = rs.getString("last");
				
	// Display values
	p("ID: " + id);
	p(", Age: " + age);
	p(", First: " + first);
	pl(", Last: " + last);
}

- 다음자료가 있으면 루프가 돌고 없으면 루프를 빠져나옴

* SQL명령 편하게 입력하는 법
- 멀티템플릿 문자열 사용(자바 버전 13이후 )
"""
"""; 
내부의 공백은 영향이 있다 (무시하면 안됨)

- 외부의 공백은 영향이 없다 

-> sql문 내에서는 공백의 영향이 없어서 상관이 없음

* 디버그

String selectSQL =  중단점

* ResultSet은 루프를 활용
커서가 제일 위에 있음
rs.next() : 1번째 자료 -> 다음자료...


* rs.next() - 오른쪽 마우스 > Watch
true -> 다음 값이 있다 




▷ 입력된 결과내용 변경하기 (UPDATE)
UPDATE 테이블명 SET 컬럼1...
WHERE ~

변경하고자 하는 내용

ID: 101, Age: 18, First: Zara, Last: Ali 
primary key 활용 : 유일성

String updateSQL = """
		UPDATE member SET
		   age = 20,
		   first = 'Sara',
		   last = 'Bli'
		WHERE 
		   id = 101;
		""";
        
stmt.executeUpdate(updateSQL);

* stmt.executeUpdate(updateSQL);


▷ 부분데이터 가져오기 SELECT ~ FROM ~ WHERE ~

- UPDATE 확인하기

selectSQL = 
		"""
				
			SELECT * FROM member 
				WHERE id = 101;
		""";
pl(selectSQL);
rs = stmt.executeQuery(selectSQL);
while(rs.next()) {
	int id = rs.getInt("id"); 
	int age = rs.getInt("age");
	String first = rs.getString("first");
	String last = rs.getString("last");
				
	p("ID: " + id);
	p(", Age: " + age);
	p(", First: " + first);
	pl(", Last: " + last);
}

* rs = stmt.executeQuery(selectSQL);

rs는 앞에서 생성해줬기 때문에 그냥 사용


▷ 부분데이터 삭제하기 DELETE
- UPDATE DELETE는 WHERE를 먼저 작성하기!

String deleteSQL = 
	"""
					
		DELETE FROM member 
			WHERE id = 101;
	""";
pl(deleteSQL);

stmt.executeUpdate(deleteSQL);

* stmt.executeUpdate(deleteSQL);

▷ DELETE SQL이 정삭 작동했는지 SELECT로 확인하기

* rs = stmt.executeQuery(selectSQL);


1) while

- 다음자료가 있으면 루프가 돌고 없으면 루프를 빠져나옴

selectSQL = 
		"""
			
			SELECT * FROM member
				WHERE id = 101;
		""";
rs = stmt.executeQuery(selectSQL);

while(rs.next()) { 
	int id = rs.getInt("id"); // 매개변수로 칼럼명 또는 칼럼인덱스가 옴
	int age = rs.getInt("age");
	String first = rs.getString("first");
	String last = rs.getString("last");
				
	// Display values
	p("ID: " + id);
	p(", Age: " + age);
	p(", First: " + first);
	pl(", Last: " + last);
}


2) if 
- getRow() : 하나도 없으면 레코드가 없는 것

selectSQL = 
		"""
			
			SELECT * FROM member
				WHERE id = 101;
		""";
rs = stmt.executeQuery(selectSQL);

// rs의 결과집합에 레코드가 있음(= DELETE SQL 실행 실패)
// rs.getRow() : 현재 rs의 첫번째 시작 record번호를 가르킴
// 따라서 마지막 레코드로 레코드 커서를 보내어 총 레코드 번호를 리턴함

rs.last(); // 레코드 커서를 가장 마지막으로 이동시킨다. 
if (rs.getRow() > 0) {
				
	while (rs.next()) { // rs.next() = true
			
	}
} else {
	// rs의 결과집합에 레코드가 삭제되어 없음(결과집합 = 0)
	pl("레코드가 삭제되었습니다.");
}

 

3) row count

* 반환한 레코드(rowcount)가 몇개인가? - while루프
- count(컬럼명) : 모든 레코드(로우)의 수를 돌려준다.
- count(*) : delete한 결과가 가 레코드 셋의 결과 
WHERE ID = 101이면 101번호는 삭제되었으므로 결과는 0이 됨

selectSQL = 
		"""
		
			SELECT count(*) rows FROM member
				WHERE id = 101;
		""";
rs = stmt.executeQuery(selectSQL);
if (rs.next()) {
	pl("row count : " + rs.getInt(1));
}
			
// Clean-up envirment 
rs.close();

- rs.close(); 사용완료 지점에서 닫아주기

 

※ if (rs.next())와 if (rs.getRow() > 0)는 함께 사용하지 못한다!

 

▷ DB 연결종료

- 생성한 역순으로 반납

stmt.close();
conn.close();

▷catch - finally 처리부분

catch(SQLException se) {
	se.printStackTrace();
} catch(Exception ex) {
	ex.printStackTrace();
} finally {
	// 처리되지 못한 자원반납을 여기서 처리
	try {
		if(stmt != null) {
			stmt.close();
		}
	} catch(SQLException se) {
		// 최종 열림을 닫기 위한 구역이므로 SQLException예외 처리하지않아도 됨
		se.printStackTrace();
	}
	try {
		if(conn != null) {
		conn.close();
		}
	} catch(SQLException se) {
		se.printStackTrace();
	}
}


// 유틸메소드
static void p(String msg) {
	System.out.print(msg);
}
static void pl(String msg) {
	p(msg + "\n");
}

● 데이터베이스 연결하는 소스코드 쪼개기

클래스 PhoneBookConnectionApp

 

1. 데이터베이스에 getConnection() 메소드 이용하여 접속하기 

- getConnection()은 로컬함수이기 때문에 1회작동하면 소멸이 된다

-> conn이 main함수가 끝날때까지 계속 작동하고 있어야하니까 conn에 메소드를 담아 호출한다

package db.model;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import static db.model.H2ConnectionDemoApp.*;

public class PhoneBookConnectionApp {
	// 변수선언(객체처리도 가능)
	static final String JDBC_DRIVER = "org.h2.Driver";
	static final String DB_URL = 
			"jdbc:h2:C:\\app\\database\\h2\\java2\\java3;" + 
			"MODE=MariaDB;" +
			"DATABASE_TO_UPPER=false;" + 
			"CASE_INSENSITIVE_IDENTIFIERS=TURE";
	static final String USER = "";
	static final String PASS = "";
	
	public static void main(String[] args) {
		
		Connection conn = null;
		conn = getConnection();  // conn이 main함수가 끝날때까지 계속 작동하고 있어야함.
	}

	
}

* 자주사용하는 기능을 메소드로 만들어 코드를 유지보수하기 쉽게하자

- 메인 밖으로 분리하여 메인코드가 간략해짐

// 데이터베이스에 접속하는 코드
public static Connection getConnection() { // 반환값 : Connection
	Connection conn = null;
	try {
		Class.forName(JDBC_DRIVER);
		pl("H2 데이터베이스 드라이버를 로딩했습니다.");
		pl("데이터베이스에 연결합니다...");
		conn = DriverManager.getConnection(DB_URL, USER, PASS);
		return conn;
	} catch(Exception e) {
		e.printStackTrace();
		return null;
	} finally {
		try {
			if(conn != null) {
				conn.close();
			}
		} catch(SQLException se) {
			
		}
	}
}


* 변수 선언시 static을 사용하지 않고 객체를 생성하여 사용할 시 객체로부터 불러와야함

-> 매번 부르기 번거로우니까 static 변수로 활용

public class 클래스명 {
	static final String JDBC_DRIVER = "org.h2.Driver";
	static final String DB_URL = 
			"jdbc:h2:C:\\app\\database\\h2\\java2;MODE=MariaDB;DATABASE_TO_UPPER=false";
	static final String USER = "";
	static final String PASS = "";

** 질의사항 
1. rs.close 위치 - rs 사용 종료 후

2. else {
pl("레코드가 삭제되었습니다.");
} 의 결과가 왜 출력되지 않는가
->  select로 확인하는 방법이 여러가지임 
SELECT * FROM member
WHERE id = 101; 일때 사용 

if (rs.next())와 if (rs.getRow() > 0)는 함께 사용하지 못한다