자바 - 18 (데이터베이스 연결)
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)는 함께 사용하지 못한다