이번 포스팅에서는 크롤링을 통해 필요한 샘플데이터를 웹에서 불러온 후 TableDB에 필요한 형식에 맞춰 저장하는 방법에 대해 다루었다.
1. DB전달
: 크롤링한 데이터를 필요한 형식에 맞추어 TableDB로 불러오는 작업은 두개의 방법으로 가능하며, 각 방법마다 정리된 순서로 진행되었다.
지니에서 인기차트를 크롤링하여 제목, 가수로 구분하고, 이를 컬렉션을 통해 DB로 전달하는 실습예제이다.
두 과정에서 TableDB는 이미 생성을 해둔 후 시작한다.
크롤링의 가능여부는 최상위경로에 /robots.txt를 붙여 확인할 수 있다.
1) 방법 1
: VO가 없이 진행된다.
① 크롤링
- 밑작업 : 데이터를 가져오는 작업까지를 완료한다.
final String url="https://www.genie.co.kr/"; Document doc=null; try { doc=Jsoup.connect(url).get(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } Elements eles=doc.select("table.list-wrap > tbody > tr.list > td.info > a"); Iterator<Element> itr=eles.iterator();
- 출력을 통해 가져온 데이터 확인
//cnt를 이용하여 element가 itr에 어떻게 담겨있는지 알 수 있다. int cnt = 0; while(itr.hasNext()) { System.out.println(cnt +" "+itr.next().text()); cnt++; if(cnt==3) { cnt=0; } }
② DB전달
: 위의 과정을 포함하여 서식을 통째로 조회할 수 있도록 하였다.
package class01;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class Test01 {
public static void main(String[] args) {
//[DB전달_상단작업]
final String driverName="oracle.jdbc.driver.OracleDriver";
final String dburl="jdbc:oracle:thin:@localhost:1521:xe";
final String user="ham";
final String passwd="1234";
Connection conn=null;
PreparedStatement pstmt=null;
String sql="insert into music values((select nvl(max(id),0)+1 from music),?,?)";
try {
Class.forName(driverName);
conn=DriverManager.getConnection(dburl, user, passwd);
pstmt=conn.prepareStatement(sql);
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// [크롤링작업]
final String url="https://www.genie.co.kr/";
Document doc=null;
try {
doc=Jsoup.connect(url).get();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Elements eles=doc.select("table.list-wrap > tbody > tr.list > td.info > a");
Iterator<Element> itr=eles.iterator();
// [DB전달_하단작업]
int cnt=0;
String a = ""; //제목을 담을 공간
String b = ""; //가수를 담을 공간
while(itr.hasNext()) {
String str=itr.next().text();
System.out.println(cnt+" "+str);
if(cnt==1) { // 제목
a=str;
} else if(cnt==2) { // 가수
b=str;
System.out.println(a + " | "+b);
try {
pstmt.setString(1, a);
pstmt.setString(2, b);
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
cnt++;
if(cnt==3) {
cnt=0;
}
}
try {
conn.close();
pstmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
[출력결과]
2) 방법 2
: VO를 활용하여 값을 전달하는 방식으로 진행된다. VO의 생성은 간단하므로 생략하였다.
① 크롤링
// 1. 크롤링작업
// 최상위경로인 robot.txt로 크롤링 가능여부 확인
final String url="https://www.genie.co.kr/";
Document doc=null;
try {
doc=Jsoup.connect(url).get();
} catch (IOException e) {
e.printStackTrace();
}
Elements eles=doc.select("table.list-wrap > tbody > tr.list > td.info > a");
Iterator<Element> itr=eles.iterator(); //제네릭에 유의 : 이터레이터 안에는 element가 들어있다!
// 컬렉션을 통해 VO에 title과 singer를 전달
ArrayList<MusicVO> datas=new ArrayList<MusicVO>();
while(itr.hasNext()) {// 값 있냐?
MusicVO vo = new MusicVO();
itr.next().text(); // 빈칸은 그냥 버리고,
vo.setTitle(itr.next().text()); //다음칸은 제목
vo.setSinger(itr.next().text()); //다음칸은 가수
// vo에 전달 잘 됐으면 그걸 리스트에 담기
datas.add(vo); // 10번 add됨
// 값이 3개씩 총 10개!
// (cnt를 통해 컨트롤 가능)
}
② DB전달
: ①의 작업 하단부에서 바로 진행된다.
// 2. DB에 전달하는 작업
final String driverName="oracle.jdbc.driver.OracleDriver";
final String dburl="jdbc:oracle:thin:@localhost:1521:xe";
final String user="ham";
final String passwd="1234";
Connection conn=null;
PreparedStatement pstmt=null;
String sql="insert into music values((select nvl(max(id),0)+1 from music),?,?)";
try {
Class.forName(driverName);
conn=DriverManager.getConnection(dburl, user, passwd);
pstmt=conn.prepareStatement(sql);
// 10회 반복이 필요하다.
// 이 10개의 데이터는 datas에 존재,
// datas는 MusicVO이다.
for(MusicVO v : datas) {
pstmt.setString(1, v.getTitle());
pstmt.setString(2, v.getSinger());
pstmt.executeUpdate(); //1회에 insert 1회
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
이 방법또한 정상적으로 tableDB에 저장되는것을 확인할 수 있다.
2. 예제
: 예제를 통해 크롤링한 데이터로 MVC구현하였다. 아래는 작업한 순서로 나열한 것이다.
1) 설계
: 구상과 함께 미리 패키지와 클래스를 생성해놓았다.
CGV의 제품구매 화면에서 제품명, 가격, 상세설명을 스크래핑하여 DB에 전달하고, MVC를 통해 이 DB에 접근할 수 있도록 하였다.
- 필요한 구성 결정
- id : pk
- name : 제품명
- price : 가격
- content : 제품설명
- cnt : 재고
- 각 클래스에서 구현할 내용
- View
- 사용자화면 : 상품목록, 상품구매, 종료하기
- 관리자화면 : 상품목록, 상품등록, 재고추가, 종료하기
- Model
- DAO : 핵심로직, 크롤링
- VO : toString오버라이딩
- View
2) 크롤링
① Utill class생성
: 작업에 앞서 반복될 내용을 간편하게 관리하기 위할 목적으로 Utill클래스를 생성하였다.
package model;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 공통되는 로직을 따로 관리하기 위한 클래스
public class JDBCUtill {
//static변수를 사용하여 자원으로 선언
static final String driverName="oracle.jdbc.driver.OracleDriver";
static final String url="jdbc:oracle:thin:@localhost:1521:xe";
static final String user="ham";
static final String passwd="1234";
// DB에 연결 == connection 확보하기에 ouput으로 설정
public static Connection connect() {
Connection conn = null;
try {
Class.forName(driverName);
conn=DriverManager.getConnection(url, user, passwd);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return conn;
}
// conn, stmt 닫는 메서드
public static void disconnect(PreparedStatement pstmt, Connection conn) {
try {
pstmt.close();
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
② 크롤링_DB에 전달까지
: modelDAO생성자에서 작업하여 DAO초기화시(test화면에서 컨트롤러 작동시) 데이터가 자동으로 DB에 전달되도록 구현
// dao생성자에서 크롤링 작업!
public SoupDAO() {
final String url="http://www.cgv.co.kr/culture-event/popcorn-store/";
String sql="insert into soup values((select nvl(max(id),0)+1 from soup),?,?,?,?)";
Document doc=null;
try {
doc=Jsoup.connect(url).get();
} catch (IOException e) {
e.printStackTrace();
}
// select의 아웃풋은 elements로, 여러개의 속성을 선택해올 수 있다.(쉼표로 추가)
// 즉, span.best_product_text_wrap의 하위 요소들 3개를 불러온 것이다.
Elements eles=doc.select("span.best_product_text_wrap > span.best_product_text_title, "
+ "span.best_product_text_name, span.store_deatail_source_price");
Iterator<Element> itr=eles.iterator(); //제네릭에 유의 : 이터레이터 안에는 element가 들어있다!
// 컬렉션을 통해 VO에 전달
ArrayList<SoupVO> datas=new ArrayList<SoupVO>();
while(itr.hasNext()) {// 값 있냐?
SoupVO vo = new SoupVO();
vo.setName(itr.next().text());//제품명
vo.setContent(itr.next().text()); //상세설명
//가격
String str = itr.next().text();
if(str.equals("금액충전형")) {
vo.setPrice(0);
}else {
vo.setPrice(Integer.parseInt(str.replace(",","")));
}
// vo에 전달 잘 됐으면 그걸 리스트에 담기
datas.add(vo);
}
// 2. DB에 전달하는 작업
conn = JDBCUtill.connect();
try {
pstmt = conn.prepareStatement(sql);
// datas는 SoupVO이다.
for(SoupVO v : datas) {
pstmt.setString(1,v.getName());
pstmt.setInt(2, v.getPrice());
pstmt.setString(3, v.getContent());
pstmt.setInt(4, v.getCnt());
pstmt.executeUpdate(); //1회에 insert 1회
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtill.disconnect(pstmt, conn);
}
}
* 여러개의 요소를 가져오는 방법 *
// select의 아웃풋은 elements로, 여러개의 속성을 선택해올 수 있다.(쉼표로 추가)
// 즉, span.best_product_text_wrap의 하위 요소들 3개를 불러온 것이다.
Elements eles=doc.select("span.best_product_text_wrap > span.best_product_text_title, "
+ "span.best_product_text_name, span.store_deatail_source_price");
Iterator<Element> itr=eles.iterator(); //제네릭에 유의 : 이터레이터 안에는 element가 들어있다!
: span.best_product_text_wrap 내부에 각각 속하여있는 span들을 쉼표를 통해 3개 불러온 결과
Elements eles = doc.select("div > li");
// 받아온 정보 중 요소(태그) 선택
Iterator<Element> itr = eles.iterator(); // 이터레이터로 요소 불러오기
Iterator<Element> itr2= eles.select("dt > a").iterator();
Iterator<Element> itr3= eles.select("span.num").iterator();
Iterator<Element> itr4= eles.select("a > span.num2 > em").iterator();
: div > li 내부에 각각 속하여있는 a , span, em을 이터레이터를 통해 각각 불러오기. 이럴 경우 itr.hasnext( )를 각각에 맞게 반복하여 알맞는 값을 삽입한다.
3) MVC작업
① view
package view;
import java.util.ArrayList;
import java.util.Scanner;
import model.SoupVO;
public class SoupView {
Scanner sc = new Scanner(System.in);
public int action;
String userMainMsg = "1. 제품목록 \n2. 제품구매 \n3. 종료하기";
String adminMainMsg = "1. 재고목록 \n2. 상품추가 \n3. 재고추가 \n4. 종료하기";
//사용자 메인
public void usertView(){
System.out.println("===사용자 페이지===");
System.out.println(userMainMsg);
System.out.println("==============");
System.out.print(">>");
action = sc.nextInt();
}
//관리자 메인
public void adminView() {
System.out.println("===관리자 페이지===");
System.out.println(adminMainMsg);
System.out.println("==============");
System.out.print(">>");
action = sc.nextInt();
}
//목록페이지
public void list(ArrayList<SoupVO>datas) {
System.out.println("===목록===");
for(SoupVO v :datas) {
System.out.println(v);
}
System.out.println("==========");
}
//구매페이지
public SoupVO buy() {
SoupVO vo = new SoupVO();
System.out.print("상품번호 : ");
int id = sc.nextInt();
System.out.print("구매수량 : ");
int cnt = sc.nextInt();
vo.setId(id);
vo.setCnt(cnt);
return vo;
}
public void buySuccess() {
System.out.println("구매 성공");
}
public void buyFail() {
System.out.println("구매 실패");
}
// 상품추가페이지
public SoupVO addSoup() {
SoupVO vo = new SoupVO();
System.out.print("상품명 : ");
String name = sc.next();
System.out.print("가격 : ");
int price = sc.nextInt();
System.out.print("상세설명 : ");
String content = sc.next();
System.out.print("재고수량 : ");
int cnt = sc.nextInt();
vo.setName(name);
vo.setPrice(price);
vo.setContent(content);
vo.setCnt(cnt);
return vo;
}
public void addSoupSuccess() {
System.out.println("상품등록 성공");
}
public void addSoupFail() {
System.out.println("상품등록 실패");
}
// 재고추가페이지
public SoupVO addCnt() {
SoupVO vo = new SoupVO();
System.out.print("상품번호 : ");
int id = sc.nextInt();
System.out.print("추가재고 : ");
int cnt = sc.nextInt();
vo.setId(id);
vo.setCnt(cnt);
return vo;
}
public void addCntSuccess() {
System.out.println("재고추가 성공");
}
public void addCntFail() {
System.out.println("재고추가 실패");
}
// 종료페이지
public void end() {
System.out.println("페이지가 종료됩니다");
}
}
② model
package model;
import java.sql.ResultSet;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import model.SoupVO;
public class SoupDAO {
Connection conn;
PreparedStatement pstmt;
//비즈니스메소드가 사용할 sql문
final String selectAll = "select * from soup";
final String selectOne = "select * from soup where id=?";
final String updateUser = "update soup set cnt=cnt-? where id=?";
final String updateAdmin = "update soup set cnt=cnt+? where id=?";
final String insert = "insert into soup (id,name,price, content, cnt) values((select nvl(max(id),0)+1 from soup),?,?,?,?)";
// dao생성자에서 크롤링 작업!
public SoupDAO() {
final String url="http://www.cgv.co.kr/culture-event/popcorn-store/";
String sql="insert into soup values((select nvl(max(id),0)+1 from soup),?,?,?,?)";
Document doc=null;
try {
doc=Jsoup.connect(url).get();
} catch (IOException e) {
e.printStackTrace();
}
// select의 아웃풋은 elements로, 여러개의 속성을 선택해올 수 있다.(쉼표로 추가)
// 즉, span.best_product_text_wrap의 하위 요소들 3개를 불러온 것이다.
Elements eles=doc.select("span.best_product_text_wrap > span.best_product_text_title, "
+ "span.best_product_text_name, span.store_deatail_source_price");
Iterator<Element> itr=eles.iterator(); //제네릭에 유의 : 이터레이터 안에는 element가 들어있다!
// 컬렉션을 통해 VO에 전달
ArrayList<SoupVO> datas=new ArrayList<SoupVO>();
while(itr.hasNext()) {// 값 있냐?
SoupVO vo = new SoupVO();
vo.setName(itr.next().text());//제품명
vo.setContent(itr.next().text()); //상세설명
//가격
String str = itr.next().text();
if(str.equals("금액충전형")) {
vo.setPrice(0);
}else {
vo.setPrice(Integer.parseInt(str.replace(",","")));
}
// vo에 전달 잘 됐으면 그걸 리스트에 담기
datas.add(vo);
}
// 2. DB에 전달하는 작업
conn = JDBCUtill.connect();
try {
pstmt = conn.prepareStatement(sql);
// datas는 SoupVO이다.
for(SoupVO v : datas) {
pstmt.setString(1,v.getName());
pstmt.setInt(2, v.getPrice());
pstmt.setString(3, v.getContent());
pstmt.setInt(4, v.getCnt());
pstmt.executeUpdate(); //1회에 insert 1회
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtill.disconnect(pstmt, conn);
}
}
// 목록보기
public ArrayList<SoupVO> selectAll(SoupVO vo) {
ArrayList<SoupVO> datas = new ArrayList<SoupVO>();
conn = JDBCUtill.connect();
try {
pstmt = conn.prepareStatement(selectAll);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
SoupVO data = new SoupVO();
data.setId(rs.getInt("id"));
data.setName(rs.getString("name"));
data.setPrice(rs.getInt("price"));
data.setContent(rs.getString("content"));
data.setCnt(rs.getInt("cnt"));
datas.add(data);
}
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtill.disconnect(pstmt, conn);
}
System.out.println("dao로그 : 조회 성공");
return datas;
}
// 제품구매
public boolean updateUser(SoupVO vo) {
conn = JDBCUtill.connect();
try {
conn.setAutoCommit(false);
pstmt = conn.prepareStatement(updateUser);
pstmt.setInt(1, vo.getCnt());
pstmt.setInt(2, vo.getId());
int res = pstmt.executeUpdate();
if(res==0) {
System.out.println("dao로그 : id조회실패");
return false;
}
pstmt = conn.prepareStatement(selectOne);
pstmt.setInt(1, vo.getId());
ResultSet rs = pstmt.executeQuery();
rs.next();
if(rs.getInt("cnt") < 0) { //cnt-? < 0일때
System.out.println("dao로그 : 사용자구매실패");
conn.rollback();
} else {
System.out.println("dao로그 : 사용자구매성공");
conn.commit();
}
conn.setAutoCommit(true);
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
System.out.println("dao로그 : 사용자구매실패");
e.printStackTrace();
return false;
} finally {
JDBCUtill.disconnect(pstmt, conn);
}
System.out.println("dao로그 : 사용자구매성공");
return true;
}
// 재고추가
public boolean updateAdmin(SoupVO vo) {
conn = JDBCUtill.connect();
try {
pstmt = conn.prepareStatement(updateAdmin);
pstmt.setInt(1, vo.getCnt());
pstmt.setInt(2, vo.getId());
int res = pstmt.executeUpdate();
if(res==0) {
System.out.println("dao로그 : 관리자id조회 실패");
return false;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("dao로그 : 관리자 재고추가 실패");
return false;
}
System.out.println("dao로그 : 관리자 재고추가 성공");
return true;
}
// 상품등록
public boolean insert (SoupVO vo) {
conn = JDBCUtill.connect();
try {
pstmt = conn.prepareStatement(insert);
pstmt.setString(1, vo.getName());
pstmt.setInt(2, vo.getPrice());
pstmt.setString(3, vo.getContent());
pstmt.setInt(4, vo.getCnt());
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
System.out.println("dao로그 : 등록실패");
e.printStackTrace();
return false;
} finally {
JDBCUtill.disconnect(pstmt, conn);
}
System.out.println("dao로그 : 등록성공");
return true;
}
}
③ controller
package controller;
import java.util.ArrayList;
import model.SoupDAO;
import model.SoupVO;
import view.SoupView;
public class SoupController {
SoupDAO dao;
SoupView view;
public SoupController(){
dao = new SoupDAO();
view = new SoupView();
}
public void appStart() {
while(true) {
view.usertView();
if(view.action==1) {
SoupVO vo = new SoupVO();
ArrayList<SoupVO> datas = dao.selectAll(vo);
view.list(datas);
}else if(view.action==2) {
SoupVO vo = view.buy();
if(dao.updateUser(vo)) {
view.buySuccess();
}else {
view.buyFail();
}
}else if(view.action==3) {
view.end();
break;
// 관리자모드 전환
}else {
while(true) {
view.adminView();
if(view.action==1) {
SoupVO vo = new SoupVO();
ArrayList<SoupVO> datas = dao.selectAll(vo);
view.list(datas);
}else if(view.action==2) {
SoupVO vo = view.addSoup();
if(dao.updateAdmin(vo)) {
view.addSoupSuccess();
}else {
view.addSoupFail();
}
}else if(view.action==3) {
SoupVO vo = view.addCnt();
if(dao.updateAdmin(vo)) {
view.addCntSuccess();
}else {
view.addCntFail();
}
}else if(view.action==4) {
view.end();
break;
}
}
}
}
}
}
4) 실행
package client;
import controller.SoupController;
public class SoupClient {
public static void main(String[] args) {
SoupController ctrl = new SoupController();
ctrl.appStart();
}
}
[실행결과 : ★ -> 입력]
===사용자 페이지===
1. 제품목록
2. 제품구매
3. 종료하기
==============
>>5
===관리자 페이지===
1. 재고목록
2. 상품추가
3. 재고추가
4. 종료하기
==============
>>3
상품번호 : 15
추가재고 : 5
dao로그 : 관리자 재고추가 성공
재고추가 성공
===관리자 페이지===
1. 재고목록
2. 상품추가
3. 재고추가
4. 종료하기
==============
>>4
페이지가 종료됩니다
===사용자 페이지===
1. 제품목록
2. 제품구매
3. 종료하기
==============
>>2
상품번호 : 15
구매수량 : 2
dao로그 : 사용자구매성공
dao로그 : 사용자구매성공
구매 성공
===사용자 페이지===
1. 제품목록
2. 제품구매
3. 종료하기
==============
>>1
dao로그 : 조회 성공
===목록===
[1] 제품명: CGV 영화관람권 | 가격: 11000원 | 상세설명: 즐거운 경험은 CGV에서! | 0개
[2] 제품명: CGV 골드클래스 | 가격: 35000원 | 상세설명: 최고의 관람환경을 제공하는 프리미엄 상영관 | 0개
[3] 제품명: 4DX관람권 | 가격: 19000원 | 상세설명: 오감만족 영화 속 주인공 되기 | 0개
[4] 제품명: IMAX 관람권 | 가격: 16000원 | 상세설명: 사람이 볼 수 있는 최대 영상, IMAX | 0개
[5] 제품명: PACONNIE A형 | 가격: 0원 | 상세설명: 충전형 선불 카드 | 0개
[6] 제품명: PACONNIE B형 | 가격: 0원 | 상세설명: 충전형 선불 카드 | 0개
[7] 제품명: PACONNIE C형 | 가격: 0원 | 상세설명: 충전형 선불 카드 | 0개
[8] 제품명: CGV콤보 | 가격: 9000원 | 상세설명: CGV의 영원한 베스트셀러! | 0개
[9] 제품명: 더블콤보 | 가격: 12000원 | 상세설명: 취향별로 원하는 맛 선택하세요! | 0개
[10] 제품명: 스몰세트 | 가격: 6500원 | 상세설명: 혼영할때 필수품 | 0개
[11] 제품명: 고소팝콘(L) | 가격: 5000원 | 상세설명: 클래식 팝콘 No.1 | 0개
[12] 제품명: 달콤팝콘(L) | 가격: 6000원 | 상세설명: 달콤한 카라멜 향이 가득한 달콤팝콘 | 0개
[13] 제품명: 더블치즈팝콘(L) | 가격: 6000원 | 상세설명: 치즈매니아들 주목! | 0개
[14] 제품명: 탄산음료(M) | 가격: 2500원 | 상세설명: 시원한 탄산음료와 함께 스트레스도 날리세요 | 0개
[15] 제품명: 아메리카노(HOT) | 가격: 3500원 | 상세설명: 현대인의 필수품 | 3개
[16] 제품명: 칠리치즈나쵸 | 가격: 4900원 | 상세설명: 바삭바삭 나쵸, 얼마나 맛있게요? | 0개
==========
===사용자 페이지===
1. 제품목록
2. 제품구매
3. 종료하기
==============
>>3
페이지가 종료됩니다
'DBMS' 카테고리의 다른 글
[Oracle] 다양한 검색과 출력_Oracle 함수 (0) | 2022.01.28 |
---|---|
[Oracle] 프로젝트의 설계 & 두개의 TableDB (0) | 2022.01.26 |
[Oracle] 웹크롤링_기초 (0) | 2022.01.24 |
[Oracle] 트랜잭션 (0) | 2022.01.22 |
[Oracle] pstmt (0) | 2022.01.21 |