목차
- JDBC
- JDBC 환경 설정
2-1. Character Set 설정
2-2. 라이브러리 추가 - JDBC 사용 클래스
3-1. Connection
3-2. DriverManager
3-3. Statement
3-4. PreparedStatement
3-5. ResultSet - .properties 활용
- Template 활용
- DTO 및 ArrayList 적용
학습점검
⚡ JDBC를 이해하고 설명할 수 있다.
✅ JDBC를 하기 위한 환경 설정(ojdbc8, 문자 인코딩)을 할 수 있다.
✅ JDBC에서 사용하는 클래스들의 특징과 사용법을 이해할 수 있다.
✅ JDBC를 적용하기 위한 코딩 절차를 순서와 유의사항을 지켜가며 작성할 수 있다.
⚡ 자바 애플리케이션과 DB를 연동하기 위한 Connection 객체를 이해하고 생성할 수 있다.
✅ Oracle DBMS와 연동하기 위한 OracleDriver를 이해할 수 있다.
✅ DriverManager를 이해할 수 있다.
✅ ojdbc8 라이브러리를 활용하여 Connection 객체를 생성할 수 있다.
✅ Connection 객체를 생성하고 Connection 객체를 닫을 수 있다.
✅ Connection 객체와 관련된 기능을 가진 공통 모듈을 이해하고 생성할 수 있다.
⚡ Statement를 활용하여 sql문을 실행할 수 있다.
✅ 공통 모듈에 Statement 객체 close()메소드를 작성할 수 있다.
✅ Connection과 Statement를 활용하여 쿼리를 실행할 수 있다.
✅ ResultSet 객체를 통해 DB로부터 조회된 결과를 꺼낼 수 있다.
✅ 변수나 입력받은 값으로 쿼리를 완성하고 DB조회를 할 수 있다.
✅ DB로 부터 읽어들인 값을 한 행씩 DTO 객체에 담을 수 있다.
✅ DB로 부터 읽어들인 값을 한 행씩 DTO 객체에 담고 모든 행을 하나의 ArrayList 객체에 담을 수 있다.
⚡ PreparedStatement를 활용하여 sql문을 실행할 수 있다.
✅ 공통 모듈에 PreparedStatement 객체 close()메소드를 작성할 수 있다.
✅ PreparedStatement 객체와 ResultSet 객체를 통해 DB로부터 조회할 수 있다.
✅ placeholder(?)가 포함된 sql문을 변수의 값으로 완성하여 PreparedStatement 객체를 활용해 조회할 수 있다.
✅ placeholder(?)가 포함된 sql문을 입력 받은 값으로 완성하여 PreparedStatement 객체를 활용해 조회할 수 있다.
✅ DB로 부터 읽어들인 값을 한 행씩 DTO 객체에 담고 모든 행을 하나의 ArrayList 객체에 담을 수 있다.
1. JDBC
- JDBC(Java DataBase Connectivity)는 자바로 하여금 데이터베이스에 접근할 수 있도록 만들어주는 프로그래밍 API이다.
- 자바 어플리케이션과 오라클 DBMS 사이에서 java.sql 하위 패키지에 속한 JDBC 인터페이스-오라클 JDBC 드라이버를 가지고 연결한다.
2. JDBC 환경 설정
2-1. Character Set 설정 기본 문자 인코딩을 UTF-8로 설정
- 이클립스 환경에서 문자 인코딩 방식을 알맞게 설정해두지 않으면 문자가 제대로 출력되지 않게 된다.
- 따라서 이클립스 작업 파일에 대한 문자셋(Character Set)을 UTF-8로 일치시켜야 한다.
- 문자셋설정①. 상단 바에서 Window → Preferences 클릭한다.
왼쪽 상단 검색창에 encoding을 조회하면 쉽게 찾을 수 있다.
- 문자셋설정②. Preferences → Workspace에서 Text file encoding을 UTF-8로 설정한다.
- 'Apply' 또는 'Apply and Close'를 눌러 변경사항을 반영한다.
왼쪽 상단 검색창에서 spelling을 조회하면 쉽게 찾을 수 있다.
- 문자셋설정③. Preferences → Spelling에서 Encoding을 UTF-8로 설정한다.
- 'Apply' 또는 'Apply and Close'를 눌러 변경사항을 반영한다.
2-2. 라이브러리 추가
❗ JDBC 드라이버 사용을 위해서는 프로젝트 폴더 하위에 ① lib 폴더 생성, ② 라이브러리 ojdbc8.jar 추가, ③ Java Build Path-Libraries-Modulepath-Add JARs 작업이 선행되어야 한다.
※ 오라클 홈페이지 다운로드
※ 오라클이 C:\에 설치돼 있는 환경 : C:\app\사용자명\product\18.0.0\dbhomeXE\jdbc\lib
- 해당 라이브러리 파일은 홈페이지서 다운로드 받거나, 오라클이 설치된 환경의 경우는 특정 경로에서 파일을 찾을 수 있다.
- Drag&Drop 또는 Ctrl+C&Ctrl+V를 통해 이클립스로 ojdbc8.jar 파일을 가져온다.
- 라이브러리추가①. 프로젝트명에서 우클릭 → Properties 선택한다.
- 라이브러리추가②. Java Build Path → Libraries → Modulepath → Add JARs... 선택한다.
- 라이브러리추가③. 실행된 JAR Selection 창에서 저장해둔 lib 폴더 하위의 ojdbc8.jar 파일을 선택하고 OK 누른다.
- 끝으로 Java Build Path 하단의 'Apply' 또는 'Apply and Close'를 눌러 변경사항을 반영한다.
3. JDBC 사용 클래스
❗ Connection/Statement/PreparedStatement/ResultSet 모두 인터페이스(interface)이다.
이들을 인터페이스로 정의해 놓은 것은 추상화 및 인터페이스 사용의 이점과 맞닿아있다. DBMS 드라이버마다 실질적으로 동작시키는 코드 개념은 상이하게 마련이다. 따라서 추상화된 상위 개념으로서 인터페이스로 정의된 것이다.
3-1. Connection DB 접속, 인터페이스
Connection con = null;
- import java.sql.Connection;을 필요로 한다.
- DB 접속을 위한 Connection 인스턴스 생성용 레퍼런스 변수 선언이다.
- try 블럭 밖에 선언한다. 객체 사용 마친 뒤 close()로 자원 반납하기 위해 finally 블럭에까지 쓰여야 하기 때문이다.
stmt = con.createStatement();
또는 pstmt = con.prepareStatement("SELECT EMP_ID, EMP_NAME FROM EMPLOYEE");
- Statement 인스턴스 생성 위해 Connection 인스턴스로 createStatement() 메소드를 호출한다.
- 즉 Connection 인스턴스가 먼저 만들어져 있어야 → SQL 문장 실행으로 작업 순서가 이어지는 것이다.
- createStatement() 메소드 사용 시
SQLException에 대한 예외처리가 된다.
} finally {
if(con != null) { //유효성 검사
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 사용한 Connection 객체에 대해 close() 선언해야 한다.
3-2. DriverManager 드라이버로 데이버 원본에 커넥션 만듦
try {
/* 사용할 드라이버 등록 */
Class.forName("oracle.jdbc.driver.OracleDriver"); //오라클 드라이버 생성
- DriverManager는 Class.forName() 메소드에 의해 생성된다. 이때 반드시
ClassNotFoundException에 대해 예외처리가 치러저야 한다. - 생성된 DriverManager는 JDBC 드라이버 통해 데이터 원본으로 커넥션을 만드는 역할을 수행한다.
- 현재는 오라클(Oracle) 환경이기에 예시처럼 작성했다. MySQL/Sybase 등 다른 DBMS를 사용한다면 소괄호 안에 그에 맞는 Driver를 명시해 주어야 한다.
/* DriverManager 이용해 Connection 생성 */
con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:xe", "C##EMPLOYEE", "EMPLOYEE");
- DriverManager는 직접 인스턴스 생성이 불가하다. 따라서 getConnection() 메소드 통해 인스턴스 생성한다.
- getConnection() 매개변수로 DB 주소, 유저 ID, 유저 암호 정보를 보낸다: public static Connection getConnection(String url, String user, String password)
❗ jdbc:oracle:thin:@localhost:1521:xe
-- localhost는 개인 IP 주소 또는 서버 주소로 대신할 수 있다.
-- 1521은 포트(port) 번호이다.
-- 이처럼 접속을 위해서는 IP 및 포트(port)가 명시되어야 한다.
-- xe는 현재 사용 중인 Oracle 18c XE에서 비롯된 키워드이다.
} catch (ClassNotFoundException e) { //forName 한 드라이버가 존재하지 않을 때
e.printStackTrace();
- Class.forName()로 등록하고자 한 드라이버의 이름이 틀린 경우 등에 대해
ClassNotFoundException에 대한 예외처리가 요구된다.
} catch (SQLException e) { //getConnection 한 DB 연결 정보가 틀릴 때
e.printStackTrace();
- getConnection() 한 DB 연결 정보가 틀린 경우에 대비해
SQLException에 대한 예외처리가 요구된다.
3-3. Statement 쿼리문 저장 및 실행, 인터페이스
Statement stmt = null;
- import java.sql.Statement;을 필요로 한다.
- 쿼리문을 저장 및 실행하는 기능을 수행한다.
- try 블럭 밖에 선언한다. 객체 사용 마친 뒤 close()로 자원 반납하기 위해 finally 블럭에까지 쓰여야 하기 때문이다.
try {
stmt = con.createStatement();
- 미리 만들어둔 Connection 인스턴스로 createStatement() 선언해 Statement의 인스턴스를 생성한다.
rset = stmt.executeQuery("SELECT EMP_ID, EMP_NAME FROM EMPLOYEE");
- executeQuery() 메소드로 쿼리문을 실행시킨다. 관련 결과는 ResultSet 타입으로 반환된다.
- 해당 메소드 사용 시
SQLException에 대한 예외처리를 필요로 한다. - (String sql)을 매개인자로 받는다. 이때 문법적으로 잘못된 오류는
java.sql.SQLSyntaxErrorException으로 취급된다.
❗ 테이블명이 틀린 경우 테이블 또는 뷰가 존재하지 않습니다... 컬럼명이 틀리면 부적합한 식별자... 오라클에서 봐왔던 오류 메시지가 출력된다.
} finally {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
- 사용한 Statement 객체에 대해 close() 선언해야 한다.
3-4. PreparedStatement 쿼리문 저장 및 실행, 생성과 동시에 쿼리문 전달, 인터페이스
❗ public interface PreparedStatement extends Statement {}
PreparedStatement는 Statement를 조상으로 삼아 상속 관계에 있다.
PreparedStatement pstmt = null;
- import java.sql.PreparedStatement;을 필요로 한다.
- Statement와 마찬가지로 쿼리문을 저장 및 실행하는 기능을 수행한다.
- try 블럭 밖에 선언한다. 객체 사용 마친 뒤 close()로 자원 반납하기 위해 finally 블럭에까지 쓰여야 하기 때문이다.
try {
pstmt = con.prepareStatement("SELECT * FROM EMPLOYEE WHERE EMP_ID = ?");
- 미리 만들어둔 Connection 인스턴스로 prepareStatement() 선언해 PreparedStatement의 인스턴스를 생성한다.
- 동시에 (매개인자)로서 쿼리문을 미리 전달한다. 바로 이것이 PreparedStatemnt와 Statement간 차이점이다.
pstmt.setString(1, empId);
- PreparedStatement에서는 위치홀더(placeholder)인 물음표(?)가 쓰인다.
- String 타입의 SQL 쿼리문(질의문) 작성 시 물음표(?)로 우선 표현해놓고, setString(int parameterIndex, String x)으로 숫자 내지는 변수명을 전달해 설정 및 실행한다.
- ① WHERE절의 조건, ② INSERT 중 VALUES값, ③ UPDATE 중 SET값 등을 설정할 때 주로 활용된다.
rset = pstmt.executeQuery();
- 따라서 executeQuery() 실행 시에는 쿼리문을 전달할 필요가 없다.
- 메소드 사용 시
SQLException에 대한 예외처리를 필요로 한다.
❗ PrepareStatement(위치홀더 포함)가 Statement보다 효율적이다.
✅ 위치홀더는 인수가 많아 특정 값을 바꿔가며 여러 번 실행해야 할 때 특히 유용하다.
✅ Statement는 SQL 문장을 매번 컴파일하지만, PreparedStatement는 딱 한 번 컴파일하므로 실행속도 또한 빠르다.
✅ 쿼리문 안에서 형식에 맞춰 문자를 작은따옴표로 감싸주던 번거로움도 위치홀더로 대체할 수 있다.
✅ 미리 형식을 준비해놓고 들어가는 값만 그때그때 다르게 처리하는 것이기 때문에 SQL injection을 막을 수 있다.
-- Statement 작성 예시
String query = "SELECT * FROM EMPLOYEE WHERE EMP_ID = '" + empId + "'";
stmt = con.createStatement();
rset = stmt.executeQuery(query);
-- PreparedStatement 작성 예시
String query = "SELECT * FROM EMPLOYEE WHERE EMP_ID = ?";
pstmt = con.prepareStatement(query);
pstmt.setString(1, 특정값을저장받은변수명);
rset = pstmt.executeQuery();
-- 별도 Template.class에 작성한 메소드
public static void close(Statement stmt) {
try {
if(stmt != null && !stmt.isclosed()) {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
-- Appliacation.class
} finally {
close(pstmt);
}
- 사용한 PreparedStatement 객체에 대해 close() 선언해야 한다.
- Statement 객체에 대한 close()문을 별도 클래스에 메소드로서 작성해둔 상황이라고 가정하자.
- 이때 PreparedStatement의 레퍼런스 변수로도 Statement 타입의 close() 메소드 사용이 가능하다. 상속 받고 있기 때문이다.
3-5. ResultSet 결과집합, 인터페이스
ResultSet rset = null;
- import java.sql.ResultSet;을 필요로 한다.
- SELECT문을 사용한 질의에 성공하면 ResultSet으로 반환된다.
try {
rset = pstmt.executeQuery();
while(rset.next()) {
System.out.println(rset.getString("EMP_ID") + ", " + rset.getString("EMP_NAME"));
}
}
- Statement/PreparedStatement로 실행해 얻은 결과가 ResultSet 객체에 담긴다.
- VARCHAR2 타입의 컬럼 데이터를 String으로 받기 위해 getString()에 컬럼명을 전달한다.
- rset.getString(1);처럼 컬럼인덱스로 작성할 순 있으나 코드만 봤을 때는 의미가 불명확하기에 컬럼명 명시하는 것이 좋다.
- 결과가 몇 행인지 알 수 없으므로 다음 값이 있는 동안 반복하라는 의미에서 next() 메소드를 반복문의 조건으로 쓴다. next()는 ResultSet의 커서 위치를 하나씩 내리며 행이 존재하면 true, 존재하지 않으면 false를 반환하는 메소드이다.
- 결과값이 1개인 것이 분명할 때는 if문으로 처리해도 무방하다.
} finally {
close(rset);
close(pstmt);
close(con);
}
- 사용한 ResultSet객체에 대해 close() 선언해야 한다.
- 자원 반납 시에는 가장 최근에(마지막에) 쓴 자원부터 역순으로 작성한다.
4. .properties 활용
- Map 인터페이스 하위의 Properties를 활용하는 코드로 개선할 수 있다: 관련 게시글 - [JAVA] 12-2. 컬렉션 | Map | HashMap | Properties
- .properties 파일에 설정 정보를 별도로 기록하고, 코드상에서는 이를 읽어오도록 만드는 것이다.
- 설정 정보에 변경사항이 생길 때에 대비해 매번 코드가 바뀌도록 두는 것보다는 별도 파일로부터 수정, 관리되도록 유도하는 작업이다.
-- jdbc-config.properties
driver=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@localhost:1521:xe
user=C##EMPLOYEE
password=EMPLOYEE
- Map의 경우 key 및 value를 한 쌍으로 가지므로, .properties 파일에서 key=value 순으로 작성한다. 개행을 통해 key값별로 구분할 수 있다.
Properties prop = new Properties();
- import java.util.Properties;을 필요로 한다.
Connection con = null;
try {
prop.load(new FileReader("src/com/reminder/connection/jdbc-config.properties"));
- load() 메소드로 파일을 읽어온다.
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
String user = prop.getProperty("user");
String password = prop.getProperty("password");
- getProperty() 메소드로 사전 저장해둔 .properties 파일 속 각각의 key값을 호출한다.
Class.forName(driver);
- 사용할 드라이버를 등록한다.
con = DriverManager.getConnection(url, user, password);
- DriverManager 이용해 Connection을 생성한다.
} catch (ClassNotFoundException e) { //Class.forName()에서 발생할 수 있는 예외
e.printStackTrace();
} catch (SQLException e) { //getConnection()에서 발생할 수 있는 예외
e.printStackTrace();
} catch (FileNotFoundException e) { //입력 스트림 FileReader 사용 시 발생할 수 있는 예외
e.printStackTrace();
} catch (IOException e) { //입력 스트림 FileReader 사용 시 발생할 수 있는 예외
e.printStackTrace();
} finally {
if(con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
- 예외처리와 close()가 치러져야 한다.
5. Template 활용
-- Template.class
public static Connection getConnection() {
- 객체 생성 없이 사용될 수 있도록 static 메소드로 선언한다.
Connection con = null;
Properties prop = new Properties();
try {
prop.load(new FileReader("config/connection-info.properties"));
String driver = prop.getProperty("driver");
String url = prop.getProperty("url");
- Connection, Properties 객체 생성한다.
- 프로젝트 하위의 config 폴더에 별도로 작성해둔 properties 파일에 대해 load() 읽어온다. FileReader 스트림을 활용한다.
- getProperty() 메소드로 각각의 key값을 호출한다.
Class.forName(driver);
con = DriverManager.getConnection(url, prop);
- 드라이버를 등록한다.
- 커넥션을 생성한다. 이때 user, password 정보를 따로 변수에 담아 명시하는 대신에 매개변수 인자로 (String url, Properties info) 전달할 수 있다.
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return con;
- 커넥션 결과가 리턴될 수 있도록 명시한다.
-- Template.class
public static void close(Connection con) {
try {
if(con != null && !con.isClosed()) {
con.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
- 자주 쓰이는 JDBC 인터페이스의 close() 구문에 대해서도 메소드를 미리 만들어놓고 사용할 수 있다.
- 이때도 역시 객체 생성 없이 다른 클래스에서 쓰일 수 있도록 public static 메소드로 선언한다.
- close()는 전달 받은 인자가 null이 아니고, 이미 닫혀있지 않을 때 실행되도록 조건문에 담아 유효성 검사 후 치러지도록 한다: if(con != null && !con.isClosed())
6. DTO 및 ArrayList 적용
Connection con = getConnection();
PreparedStatement pstmt = null;
ResultSet rset = null;
- 사용할 인터페이스의 객체를 우선 선언한다.
EmployeeDTO row = null;
- 한 행 정보를 담을 DTO 객체를 선언한다.
- DTO는 Data Transfer Object의 약자이다. 프로젝트 하위에 model-dto 폴더 생성 후 EmployeeDTO.class를 별도로 만들어 쓰는 것이다.
- DTO 클래스는 EMPLOYEE 테이블의 컬럼 데이터를 옮겨담을 ① 각각의 private 필드, ② 기본 생성자와 매개변수 있는 생성자, ③ 설정자(setter)와 접근자(getter), ④ 오버라이딩 된 toString() 메소드로 구성된다.
- private 필드로 옮길 때 변수 명명 규칙에 따라 camel-case를 적용한다.
- 또, 날짜 형식을 담을 때는 java.sql.Date 타입을 사용한다. 범용적으로 사용되는 것은 java.util.Date이지만, DB에서 데이터를 조회해올 때 별도 포맷팅 없이 간편하게 가져오기 위한 데는 java.sql.Date가 적합하기 때문이다.
List<EmployeeDTO> empList = null;
- 여러 DTO를 하나의 인스턴스로 묶기 위한 List를 선언한다.
Scanner sc = new Scanner(System.in);
System.out.print("조회할 성을 입력하세요 : ");
String input = sc.nextLine();
String query = "SELECT * FROM EMPLOYEE WHERE EMP_NAME LIKE ? || '%'";
- 사용할 Scanner 및 String query를 작성한다.
- 쿼리문에서 연결연산자가 쓰인 모습을 확인할 수 있다. EMP_NAME = '하%';를 의도하려거든 위치홀더 || '%'처럼 연결연산자로 연결해 주어야 한다. 다시 말해 '%임%'을 조회하고자 한다면 '%' || ? || '%'가 되는 것이다.
try {
pstmt = con.prepareStatement(query);
pstmt.setString(1, input);
rset = pstmt.executeQuery();
empList = new ArrayList();
- EmployeeDTO 타입의 List 레퍼런스 변수에 ArrayList 객체를 담는다.
while(rset.next()) {
row = new EmployeeDTO();
row.setEmpId(rset.getString("EMP_ID"));
row.setEmpName(rset.getString("EMP_NAME"));
row.setEmpNo(rset.getString("EMP_NO");
row.setEmail(rset.getString("EMAIL"));
row.setPhone(rset.getString("PHONE"));
row.setDeptCode(rset.getString("DEPT_CODE"));
row.setJobCode(rset.getString("JOB_CODE"));
row.setSalLevel(rset.getString("SAL_LEVEL"));
row.setSalary(rset.getInt("SALARY"));
row.setBonus(rset.getDouble("BONUS"));
row.setManagerId(rset.getString("MANAGER_ID"));
row.setHireDate(rset.getDate("HIRE_DATE"));
row.setEntDate(rset.getDate("ENT_DATE"));
row.setEntYn(rset.getString("ENT_YN"));
empList.add(row);
}
- 반복할 때마다 DTO 객체가 row라는 레퍼런스 변수에 담겨 생성된다.
- 설정자(setter)와 접근자(getter) 활용해 각각에 해당되는 컬럼명을 전달한다.
- 이때 getString, getInt, getDouble, getDate 등등 데이터 타입에 맞춰 잘 작성해야 한다.
- 설정까지 마무리 된 한 행 정보는 ArrayList 객체 목록인 empList에 add() 추가된다.
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(rset);
close(pstmt);
close(con);
}
- finally 블럭에서 사용한 자원들에 대해 close() 선언한다.
for(EmployeeDTO emp : empList) {
System.out.println(emp);
}
- 향상된 for문으로 empList에 담긴 정보를 출력한다.
'Database' 카테고리의 다른 글
[Oracle/수업 과제 practice] DML (0) | 2022.01.30 |
---|---|
[Oracle/수업 과제 practice] DDL(10~15번 문항) (0) | 2022.01.30 |
[Oracle/SQL] 14. 시스템 권한 | 객체 권한 | GRANT | REVOKE | ROLE (0) | 2022.01.28 |
[Oracle/SQL] 13. 동의어 | SYNONYM | 공개 동의어 | 비공개 동의어 (0) | 2022.01.28 |
[Oracle/SQL] 12. 인덱스 | INDEX | ROWID | INDEX HINT | REBUILD (0) | 2022.01.27 |